1# Copyright 2013 Donald Stufft and individual contributors
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14from typing import ClassVar, Generic, Optional, Type, TypeVar
15
16import nacl.bindings
17from nacl import encoding
18from nacl import exceptions as exc
19from nacl.encoding import Encoder
20from nacl.utils import EncryptedMessage, StringFixer, random
21
22
23class PublicKey(encoding.Encodable, StringFixer):
24 """
25 The public key counterpart to an Curve25519 :class:`nacl.public.PrivateKey`
26 for encrypting messages.
27
28 :param public_key: [:class:`bytes`] Encoded Curve25519 public key
29 :param encoder: A class that is able to decode the `public_key`
30
31 :cvar SIZE: The size that the public key is required to be
32 """
33
34 SIZE: ClassVar[int] = nacl.bindings.crypto_box_PUBLICKEYBYTES
35
36 def __init__(
37 self,
38 public_key: bytes,
39 encoder: encoding.Encoder = encoding.RawEncoder,
40 ):
41 self._public_key = encoder.decode(public_key)
42 if not isinstance(self._public_key, bytes):
43 raise exc.TypeError("PublicKey must be created from 32 bytes")
44
45 if len(self._public_key) != self.SIZE:
46 raise exc.ValueError(
47 "The public key must be exactly {} bytes long".format(
48 self.SIZE
49 )
50 )
51
52 def __bytes__(self) -> bytes:
53 return self._public_key
54
55 def __hash__(self) -> int:
56 return hash(bytes(self))
57
58 def __eq__(self, other: object) -> bool:
59 if not isinstance(other, self.__class__):
60 return False
61 return nacl.bindings.sodium_memcmp(bytes(self), bytes(other))
62
63 def __ne__(self, other: object) -> bool:
64 return not (self == other)
65
66
67class PrivateKey(encoding.Encodable, StringFixer):
68 """
69 Private key for decrypting messages using the Curve25519 algorithm.
70
71 .. warning:: This **must** be protected and remain secret. Anyone who
72 knows the value of your :class:`~nacl.public.PrivateKey` can decrypt
73 any message encrypted by the corresponding
74 :class:`~nacl.public.PublicKey`
75
76 :param private_key: The private key used to decrypt messages
77 :param encoder: The encoder class used to decode the given keys
78
79 :cvar SIZE: The size that the private key is required to be
80 :cvar SEED_SIZE: The size that the seed used to generate the
81 private key is required to be
82 """
83
84 SIZE: ClassVar[int] = nacl.bindings.crypto_box_SECRETKEYBYTES
85 SEED_SIZE: ClassVar[int] = nacl.bindings.crypto_box_SEEDBYTES
86
87 def __init__(
88 self,
89 private_key: bytes,
90 encoder: encoding.Encoder = encoding.RawEncoder,
91 ):
92 # Decode the secret_key
93 private_key = encoder.decode(private_key)
94 # verify the given secret key type and size are correct
95 if not (
96 isinstance(private_key, bytes) and len(private_key) == self.SIZE
97 ):
98 raise exc.TypeError(
99 (
100 "PrivateKey must be created from a {} bytes long raw secret key"
101 ).format(self.SIZE)
102 )
103
104 raw_public_key = nacl.bindings.crypto_scalarmult_base(private_key)
105
106 self._private_key = private_key
107 self.public_key = PublicKey(raw_public_key)
108
109 @classmethod
110 def from_seed(
111 cls,
112 seed: bytes,
113 encoder: encoding.Encoder = encoding.RawEncoder,
114 ) -> "PrivateKey":
115 """
116 Generate a PrivateKey using a deterministic construction
117 starting from a caller-provided seed
118
119 .. warning:: The seed **must** be high-entropy; therefore,
120 its generator **must** be a cryptographic quality
121 random function like, for example, :func:`~nacl.utils.random`.
122
123 .. warning:: The seed **must** be protected and remain secret.
124 Anyone who knows the seed is really in possession of
125 the corresponding PrivateKey.
126
127 :param seed: The seed used to generate the private key
128 :rtype: :class:`~nacl.public.PrivateKey`
129 """
130 # decode the seed
131 seed = encoder.decode(seed)
132 # Verify the given seed type and size are correct
133 if not (isinstance(seed, bytes) and len(seed) == cls.SEED_SIZE):
134 raise exc.TypeError(
135 (
136 "PrivateKey seed must be a {} bytes long binary sequence"
137 ).format(cls.SEED_SIZE)
138 )
139 # generate a raw key pair from the given seed
140 raw_pk, raw_sk = nacl.bindings.crypto_box_seed_keypair(seed)
141 # construct a instance from the raw secret key
142 return cls(raw_sk)
143
144 def __bytes__(self) -> bytes:
145 return self._private_key
146
147 def __hash__(self) -> int:
148 return hash((type(self), bytes(self.public_key)))
149
150 def __eq__(self, other: object) -> bool:
151 if not isinstance(other, self.__class__):
152 return False
153 return self.public_key == other.public_key
154
155 def __ne__(self, other: object) -> bool:
156 return not (self == other)
157
158 @classmethod
159 def generate(cls) -> "PrivateKey":
160 """
161 Generates a random :class:`~nacl.public.PrivateKey` object
162
163 :rtype: :class:`~nacl.public.PrivateKey`
164 """
165 return cls(random(PrivateKey.SIZE), encoder=encoding.RawEncoder)
166
167
168_Box = TypeVar("_Box", bound="Box")
169
170
171class Box(encoding.Encodable, StringFixer):
172 """
173 The Box class boxes and unboxes messages between a pair of keys
174
175 The ciphertexts generated by :class:`~nacl.public.Box` include a 16
176 byte authenticator which is checked as part of the decryption. An invalid
177 authenticator will cause the decrypt function to raise an exception. The
178 authenticator is not a signature. Once you've decrypted the message you've
179 demonstrated the ability to create arbitrary valid message, so messages you
180 send are repudiable. For non-repudiable messages, sign them after
181 encryption.
182
183 :param private_key: :class:`~nacl.public.PrivateKey` used to encrypt and
184 decrypt messages
185 :param public_key: :class:`~nacl.public.PublicKey` used to encrypt and
186 decrypt messages
187
188 :cvar NONCE_SIZE: The size that the nonce is required to be.
189 """
190
191 NONCE_SIZE: ClassVar[int] = nacl.bindings.crypto_box_NONCEBYTES
192 _shared_key: bytes
193
194 def __init__(self, private_key: PrivateKey, public_key: PublicKey):
195 if not isinstance(private_key, PrivateKey) or not isinstance(
196 public_key, PublicKey
197 ):
198 raise exc.TypeError(
199 "Box must be created from a PrivateKey and a PublicKey"
200 )
201 self._shared_key = nacl.bindings.crypto_box_beforenm(
202 public_key.encode(encoder=encoding.RawEncoder),
203 private_key.encode(encoder=encoding.RawEncoder),
204 )
205
206 def __bytes__(self) -> bytes:
207 return self._shared_key
208
209 @classmethod
210 def decode(
211 cls: Type[_Box], encoded: bytes, encoder: Encoder = encoding.RawEncoder
212 ) -> _Box:
213 """
214 Alternative constructor. Creates a Box from an existing Box's shared key.
215 """
216 # Create an empty box
217 box: _Box = cls.__new__(cls)
218
219 # Assign our decoded value to the shared key of the box
220 box._shared_key = encoder.decode(encoded)
221
222 return box
223
224 def encrypt(
225 self,
226 plaintext: bytes,
227 nonce: Optional[bytes] = None,
228 encoder: encoding.Encoder = encoding.RawEncoder,
229 ) -> EncryptedMessage:
230 """
231 Encrypts the plaintext message using the given `nonce` (or generates
232 one randomly if omitted) and returns the ciphertext encoded with the
233 encoder.
234
235 .. warning:: It is **VITALLY** important that the nonce is a nonce,
236 i.e. it is a number used only once for any given key. If you fail
237 to do this, you compromise the privacy of the messages encrypted.
238
239 :param plaintext: [:class:`bytes`] The plaintext message to encrypt
240 :param nonce: [:class:`bytes`] The nonce to use in the encryption
241 :param encoder: The encoder to use to encode the ciphertext
242 :rtype: [:class:`nacl.utils.EncryptedMessage`]
243 """
244 if nonce is None:
245 nonce = random(self.NONCE_SIZE)
246
247 if len(nonce) != self.NONCE_SIZE:
248 raise exc.ValueError(
249 "The nonce must be exactly %s bytes long" % self.NONCE_SIZE
250 )
251
252 ciphertext = nacl.bindings.crypto_box_easy_afternm(
253 plaintext,
254 nonce,
255 self._shared_key,
256 )
257
258 encoded_nonce = encoder.encode(nonce)
259 encoded_ciphertext = encoder.encode(ciphertext)
260
261 return EncryptedMessage._from_parts(
262 encoded_nonce,
263 encoded_ciphertext,
264 encoder.encode(nonce + ciphertext),
265 )
266
267 def decrypt(
268 self,
269 ciphertext: bytes,
270 nonce: Optional[bytes] = None,
271 encoder: encoding.Encoder = encoding.RawEncoder,
272 ) -> bytes:
273 """
274 Decrypts the ciphertext using the `nonce` (explicitly, when passed as a
275 parameter or implicitly, when omitted, as part of the ciphertext) and
276 returns the plaintext message.
277
278 :param ciphertext: [:class:`bytes`] The encrypted message to decrypt
279 :param nonce: [:class:`bytes`] The nonce used when encrypting the
280 ciphertext
281 :param encoder: The encoder used to decode the ciphertext.
282 :rtype: [:class:`bytes`]
283 """
284 # Decode our ciphertext
285 ciphertext = encoder.decode(ciphertext)
286
287 if nonce is None:
288 # If we were given the nonce and ciphertext combined, split them.
289 nonce = ciphertext[: self.NONCE_SIZE]
290 ciphertext = ciphertext[self.NONCE_SIZE :]
291
292 if len(nonce) != self.NONCE_SIZE:
293 raise exc.ValueError(
294 "The nonce must be exactly %s bytes long" % self.NONCE_SIZE
295 )
296
297 plaintext = nacl.bindings.crypto_box_open_easy_afternm(
298 ciphertext,
299 nonce,
300 self._shared_key,
301 )
302
303 return plaintext
304
305 def shared_key(self) -> bytes:
306 """
307 Returns the Curve25519 shared secret, that can then be used as a key in
308 other symmetric ciphers.
309
310 .. warning:: It is **VITALLY** important that you use a nonce with your
311 symmetric cipher. If you fail to do this, you compromise the
312 privacy of the messages encrypted. Ensure that the key length of
313 your cipher is 32 bytes.
314 :rtype: [:class:`bytes`]
315 """
316
317 return self._shared_key
318
319
320_Key = TypeVar("_Key", PublicKey, PrivateKey)
321
322
323class SealedBox(Generic[_Key], encoding.Encodable, StringFixer):
324 """
325 The SealedBox class boxes and unboxes messages addressed to
326 a specified key-pair by using ephemeral sender's key pairs,
327 whose private part will be discarded just after encrypting
328 a single plaintext message.
329
330 The ciphertexts generated by :class:`~nacl.public.SecretBox` include
331 the public part of the ephemeral key before the :class:`~nacl.public.Box`
332 ciphertext.
333
334 :param recipient_key: a :class:`~nacl.public.PublicKey` used to encrypt
335 messages and derive nonces, or a :class:`~nacl.public.PrivateKey` used
336 to decrypt messages.
337
338 .. versionadded:: 1.2
339 """
340
341 _public_key: bytes
342 _private_key: Optional[bytes]
343
344 def __init__(self, recipient_key: _Key):
345 if isinstance(recipient_key, PublicKey):
346 self._public_key = recipient_key.encode(
347 encoder=encoding.RawEncoder
348 )
349 self._private_key = None
350 elif isinstance(recipient_key, PrivateKey):
351 self._private_key = recipient_key.encode(
352 encoder=encoding.RawEncoder
353 )
354 self._public_key = recipient_key.public_key.encode(
355 encoder=encoding.RawEncoder
356 )
357 else:
358 raise exc.TypeError(
359 "SealedBox must be created from a PublicKey or a PrivateKey"
360 )
361
362 def __bytes__(self) -> bytes:
363 return self._public_key
364
365 def encrypt(
366 self,
367 plaintext: bytes,
368 encoder: encoding.Encoder = encoding.RawEncoder,
369 ) -> bytes:
370 """
371 Encrypts the plaintext message using a random-generated ephemeral
372 key pair and returns a "composed ciphertext", containing both
373 the public part of the key pair and the ciphertext proper,
374 encoded with the encoder.
375
376 The private part of the ephemeral key-pair will be scrubbed before
377 returning the ciphertext, therefore, the sender will not be able to
378 decrypt the generated ciphertext.
379
380 :param plaintext: [:class:`bytes`] The plaintext message to encrypt
381 :param encoder: The encoder to use to encode the ciphertext
382 :return bytes: encoded ciphertext
383 """
384
385 ciphertext = nacl.bindings.crypto_box_seal(plaintext, self._public_key)
386
387 encoded_ciphertext = encoder.encode(ciphertext)
388
389 return encoded_ciphertext
390
391 def decrypt(
392 self: "SealedBox[PrivateKey]",
393 ciphertext: bytes,
394 encoder: encoding.Encoder = encoding.RawEncoder,
395 ) -> bytes:
396 """
397 Decrypts the ciphertext using the ephemeral public key enclosed
398 in the ciphertext and the SealedBox private key, returning
399 the plaintext message.
400
401 :param ciphertext: [:class:`bytes`] The encrypted message to decrypt
402 :param encoder: The encoder used to decode the ciphertext.
403 :return bytes: The original plaintext
404 :raises TypeError: if this SealedBox was created with a
405 :class:`~nacl.public.PublicKey` rather than a
406 :class:`~nacl.public.PrivateKey`.
407 """
408 # Decode our ciphertext
409 ciphertext = encoder.decode(ciphertext)
410
411 if self._private_key is None:
412 raise TypeError(
413 "SealedBoxes created with a public key cannot decrypt"
414 )
415 plaintext = nacl.bindings.crypto_box_seal_open(
416 ciphertext,
417 self._public_key,
418 self._private_key,
419 )
420
421 return plaintext