Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/nacl/public.py: 39%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

114 statements  

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 {} " 

101 "bytes long raw secret key" 

102 ).format(self.SIZE) 

103 ) 

104 

105 raw_public_key = nacl.bindings.crypto_scalarmult_base(private_key) 

106 

107 self._private_key = private_key 

108 self.public_key = PublicKey(raw_public_key) 

109 

110 @classmethod 

111 def from_seed( 

112 cls, 

113 seed: bytes, 

114 encoder: encoding.Encoder = encoding.RawEncoder, 

115 ) -> "PrivateKey": 

116 """ 

117 Generate a PrivateKey using a deterministic construction 

118 starting from a caller-provided seed 

119 

120 .. warning:: The seed **must** be high-entropy; therefore, 

121 its generator **must** be a cryptographic quality 

122 random function like, for example, :func:`~nacl.utils.random`. 

123 

124 .. warning:: The seed **must** be protected and remain secret. 

125 Anyone who knows the seed is really in possession of 

126 the corresponding PrivateKey. 

127 

128 :param seed: The seed used to generate the private key 

129 :rtype: :class:`~nacl.public.PrivateKey` 

130 """ 

131 # decode the seed 

132 seed = encoder.decode(seed) 

133 # Verify the given seed type and size are correct 

134 if not (isinstance(seed, bytes) and len(seed) == cls.SEED_SIZE): 

135 raise exc.TypeError( 

136 ( 

137 "PrivateKey seed must be a {} bytes long " 

138 "binary sequence" 

139 ).format(cls.SEED_SIZE) 

140 ) 

141 # generate a raw keypair from the given seed 

142 raw_pk, raw_sk = nacl.bindings.crypto_box_seed_keypair(seed) 

143 # construct a instance from the raw secret key 

144 return cls(raw_sk) 

145 

146 def __bytes__(self) -> bytes: 

147 return self._private_key 

148 

149 def __hash__(self) -> int: 

150 return hash((type(self), bytes(self.public_key))) 

151 

152 def __eq__(self, other: object) -> bool: 

153 if not isinstance(other, self.__class__): 

154 return False 

155 return self.public_key == other.public_key 

156 

157 def __ne__(self, other: object) -> bool: 

158 return not (self == other) 

159 

160 @classmethod 

161 def generate(cls) -> "PrivateKey": 

162 """ 

163 Generates a random :class:`~nacl.public.PrivateKey` object 

164 

165 :rtype: :class:`~nacl.public.PrivateKey` 

166 """ 

167 return cls(random(PrivateKey.SIZE), encoder=encoding.RawEncoder) 

168 

169 

170_Box = TypeVar("_Box", bound="Box") 

171 

172 

173class Box(encoding.Encodable, StringFixer): 

174 """ 

175 The Box class boxes and unboxes messages between a pair of keys 

176 

177 The ciphertexts generated by :class:`~nacl.public.Box` include a 16 

178 byte authenticator which is checked as part of the decryption. An invalid 

179 authenticator will cause the decrypt function to raise an exception. The 

180 authenticator is not a signature. Once you've decrypted the message you've 

181 demonstrated the ability to create arbitrary valid message, so messages you 

182 send are repudiable. For non-repudiable messages, sign them after 

183 encryption. 

184 

185 :param private_key: :class:`~nacl.public.PrivateKey` used to encrypt and 

186 decrypt messages 

187 :param public_key: :class:`~nacl.public.PublicKey` used to encrypt and 

188 decrypt messages 

189 

190 :cvar NONCE_SIZE: The size that the nonce is required to be. 

191 """ 

192 

193 NONCE_SIZE: ClassVar[int] = nacl.bindings.crypto_box_NONCEBYTES 

194 _shared_key: bytes 

195 

196 def __init__(self, private_key: PrivateKey, public_key: PublicKey): 

197 if not isinstance(private_key, PrivateKey) or not isinstance( 

198 public_key, PublicKey 

199 ): 

200 raise exc.TypeError( 

201 "Box must be created from a PrivateKey and a PublicKey" 

202 ) 

203 self._shared_key = nacl.bindings.crypto_box_beforenm( 

204 public_key.encode(encoder=encoding.RawEncoder), 

205 private_key.encode(encoder=encoding.RawEncoder), 

206 ) 

207 

208 def __bytes__(self) -> bytes: 

209 return self._shared_key 

210 

211 @classmethod 

212 def decode( 

213 cls: Type[_Box], encoded: bytes, encoder: Encoder = encoding.RawEncoder 

214 ) -> _Box: 

215 """ 

216 Alternative constructor. Creates a Box from an existing Box's shared key. 

217 """ 

218 # Create an empty box 

219 box: _Box = cls.__new__(cls) 

220 

221 # Assign our decoded value to the shared key of the box 

222 box._shared_key = encoder.decode(encoded) 

223 

224 return box 

225 

226 def encrypt( 

227 self, 

228 plaintext: bytes, 

229 nonce: Optional[bytes] = None, 

230 encoder: encoding.Encoder = encoding.RawEncoder, 

231 ) -> EncryptedMessage: 

232 """ 

233 Encrypts the plaintext message using the given `nonce` (or generates 

234 one randomly if omitted) and returns the ciphertext encoded with the 

235 encoder. 

236 

237 .. warning:: It is **VITALLY** important that the nonce is a nonce, 

238 i.e. it is a number used only once for any given key. If you fail 

239 to do this, you compromise the privacy of the messages encrypted. 

240 

241 :param plaintext: [:class:`bytes`] The plaintext message to encrypt 

242 :param nonce: [:class:`bytes`] The nonce to use in the encryption 

243 :param encoder: The encoder to use to encode the ciphertext 

244 :rtype: [:class:`nacl.utils.EncryptedMessage`] 

245 """ 

246 if nonce is None: 

247 nonce = random(self.NONCE_SIZE) 

248 

249 if len(nonce) != self.NONCE_SIZE: 

250 raise exc.ValueError( 

251 "The nonce must be exactly %s bytes long" % self.NONCE_SIZE 

252 ) 

253 

254 ciphertext = nacl.bindings.crypto_box_afternm( 

255 plaintext, 

256 nonce, 

257 self._shared_key, 

258 ) 

259 

260 encoded_nonce = encoder.encode(nonce) 

261 encoded_ciphertext = encoder.encode(ciphertext) 

262 

263 return EncryptedMessage._from_parts( 

264 encoded_nonce, 

265 encoded_ciphertext, 

266 encoder.encode(nonce + ciphertext), 

267 ) 

268 

269 def decrypt( 

270 self, 

271 ciphertext: bytes, 

272 nonce: Optional[bytes] = None, 

273 encoder: encoding.Encoder = encoding.RawEncoder, 

274 ) -> bytes: 

275 """ 

276 Decrypts the ciphertext using the `nonce` (explicitly, when passed as a 

277 parameter or implicitly, when omitted, as part of the ciphertext) and 

278 returns the plaintext message. 

279 

280 :param ciphertext: [:class:`bytes`] The encrypted message to decrypt 

281 :param nonce: [:class:`bytes`] The nonce used when encrypting the 

282 ciphertext 

283 :param encoder: The encoder used to decode the ciphertext. 

284 :rtype: [:class:`bytes`] 

285 """ 

286 # Decode our ciphertext 

287 ciphertext = encoder.decode(ciphertext) 

288 

289 if nonce is None: 

290 # If we were given the nonce and ciphertext combined, split them. 

291 nonce = ciphertext[: self.NONCE_SIZE] 

292 ciphertext = ciphertext[self.NONCE_SIZE :] 

293 

294 if len(nonce) != self.NONCE_SIZE: 

295 raise exc.ValueError( 

296 "The nonce must be exactly %s bytes long" % self.NONCE_SIZE 

297 ) 

298 

299 plaintext = nacl.bindings.crypto_box_open_afternm( 

300 ciphertext, 

301 nonce, 

302 self._shared_key, 

303 ) 

304 

305 return plaintext 

306 

307 def shared_key(self) -> bytes: 

308 """ 

309 Returns the Curve25519 shared secret, that can then be used as a key in 

310 other symmetric ciphers. 

311 

312 .. warning:: It is **VITALLY** important that you use a nonce with your 

313 symmetric cipher. If you fail to do this, you compromise the 

314 privacy of the messages encrypted. Ensure that the key length of 

315 your cipher is 32 bytes. 

316 :rtype: [:class:`bytes`] 

317 """ 

318 

319 return self._shared_key 

320 

321 

322_Key = TypeVar("_Key", PublicKey, PrivateKey) 

323 

324 

325class SealedBox(Generic[_Key], encoding.Encodable, StringFixer): 

326 """ 

327 The SealedBox class boxes and unboxes messages addressed to 

328 a specified key-pair by using ephemeral sender's keypairs, 

329 whose private part will be discarded just after encrypting 

330 a single plaintext message. 

331 

332 The ciphertexts generated by :class:`~nacl.public.SecretBox` include 

333 the public part of the ephemeral key before the :class:`~nacl.public.Box` 

334 ciphertext. 

335 

336 :param recipient_key: a :class:`~nacl.public.PublicKey` used to encrypt 

337 messages and derive nonces, or a :class:`~nacl.public.PrivateKey` used 

338 to decrypt messages. 

339 

340 .. versionadded:: 1.2 

341 """ 

342 

343 _public_key: bytes 

344 _private_key: Optional[bytes] 

345 

346 def __init__(self, recipient_key: _Key): 

347 if isinstance(recipient_key, PublicKey): 

348 self._public_key = recipient_key.encode( 

349 encoder=encoding.RawEncoder 

350 ) 

351 self._private_key = None 

352 elif isinstance(recipient_key, PrivateKey): 

353 self._private_key = recipient_key.encode( 

354 encoder=encoding.RawEncoder 

355 ) 

356 self._public_key = recipient_key.public_key.encode( 

357 encoder=encoding.RawEncoder 

358 ) 

359 else: 

360 raise exc.TypeError( 

361 "SealedBox must be created from a PublicKey or a PrivateKey" 

362 ) 

363 

364 def __bytes__(self) -> bytes: 

365 return self._public_key 

366 

367 def encrypt( 

368 self, 

369 plaintext: bytes, 

370 encoder: encoding.Encoder = encoding.RawEncoder, 

371 ) -> bytes: 

372 """ 

373 Encrypts the plaintext message using a random-generated ephemeral 

374 keypair and returns a "composed ciphertext", containing both 

375 the public part of the keypair and the ciphertext proper, 

376 encoded with the encoder. 

377 

378 The private part of the ephemeral key-pair will be scrubbed before 

379 returning the ciphertext, therefore, the sender will not be able to 

380 decrypt the generated ciphertext. 

381 

382 :param plaintext: [:class:`bytes`] The plaintext message to encrypt 

383 :param encoder: The encoder to use to encode the ciphertext 

384 :return bytes: encoded ciphertext 

385 """ 

386 

387 ciphertext = nacl.bindings.crypto_box_seal(plaintext, self._public_key) 

388 

389 encoded_ciphertext = encoder.encode(ciphertext) 

390 

391 return encoded_ciphertext 

392 

393 def decrypt( 

394 self: "SealedBox[PrivateKey]", 

395 ciphertext: bytes, 

396 encoder: encoding.Encoder = encoding.RawEncoder, 

397 ) -> bytes: 

398 """ 

399 Decrypts the ciphertext using the ephemeral public key enclosed 

400 in the ciphertext and the SealedBox private key, returning 

401 the plaintext message. 

402 

403 :param ciphertext: [:class:`bytes`] The encrypted message to decrypt 

404 :param encoder: The encoder used to decode the ciphertext. 

405 :return bytes: The original plaintext 

406 :raises TypeError: if this SealedBox was created with a 

407 :class:`~nacl.public.PublicKey` rather than a 

408 :class:`~nacl.public.PrivateKey`. 

409 """ 

410 # Decode our ciphertext 

411 ciphertext = encoder.decode(ciphertext) 

412 

413 if self._private_key is None: 

414 raise TypeError( 

415 "SealedBoxes created with a public key cannot decrypt" 

416 ) 

417 plaintext = nacl.bindings.crypto_box_seal_open( 

418 ciphertext, 

419 self._public_key, 

420 self._private_key, 

421 ) 

422 

423 return plaintext