Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/rsa/pkcs1.py: 23%

129 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:40 +0000

1# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu> 

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# https://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. 

14 

15"""Functions for PKCS#1 version 1.5 encryption and signing 

16 

17This module implements certain functionality from PKCS#1 version 1.5. For a 

18very clear example, read http://www.di-mgt.com.au/rsa_alg.html#pkcs1schemes 

19 

20At least 8 bytes of random padding is used when encrypting a message. This makes 

21these methods much more secure than the ones in the ``rsa`` module. 

22 

23WARNING: this module leaks information when decryption fails. The exceptions 

24that are raised contain the Python traceback information, which can be used to 

25deduce where in the process the failure occurred. DO NOT PASS SUCH INFORMATION 

26to your users. 

27""" 

28 

29import hashlib 

30import os 

31import sys 

32import typing 

33from hmac import compare_digest 

34 

35from . import common, transform, core, key 

36 

37if typing.TYPE_CHECKING: 

38 HashType = hashlib._Hash 

39else: 

40 HashType = typing.Any 

41 

42# ASN.1 codes that describe the hash algorithm used. 

43HASH_ASN1 = { 

44 "MD5": b"\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10", 

45 "SHA-1": b"\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14", 

46 "SHA-224": b"\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x05\x00\x04\x1c", 

47 "SHA-256": b"\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20", 

48 "SHA-384": b"\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30", 

49 "SHA-512": b"\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40", 

50} 

51 

52HASH_METHODS: typing.Dict[str, typing.Callable[[], HashType]] = { 

53 "MD5": hashlib.md5, 

54 "SHA-1": hashlib.sha1, 

55 "SHA-224": hashlib.sha224, 

56 "SHA-256": hashlib.sha256, 

57 "SHA-384": hashlib.sha384, 

58 "SHA-512": hashlib.sha512, 

59} 

60"""Hash methods supported by this library.""" 

61 

62 

63if sys.version_info >= (3, 6): 

64 # Python 3.6 introduced SHA3 support. 

65 HASH_ASN1.update( 

66 { 

67 "SHA3-256": b"\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x08\x05\x00\x04\x20", 

68 "SHA3-384": b"\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x09\x05\x00\x04\x30", 

69 "SHA3-512": b"\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x0a\x05\x00\x04\x40", 

70 } 

71 ) 

72 

73 HASH_METHODS.update( 

74 { 

75 "SHA3-256": hashlib.sha3_256, 

76 "SHA3-384": hashlib.sha3_384, 

77 "SHA3-512": hashlib.sha3_512, 

78 } 

79 ) 

80 

81 

82class CryptoError(Exception): 

83 """Base class for all exceptions in this module.""" 

84 

85 

86class DecryptionError(CryptoError): 

87 """Raised when decryption fails.""" 

88 

89 

90class VerificationError(CryptoError): 

91 """Raised when verification fails.""" 

92 

93 

94def _pad_for_encryption(message: bytes, target_length: int) -> bytes: 

95 r"""Pads the message for encryption, returning the padded message. 

96 

97 :return: 00 02 RANDOM_DATA 00 MESSAGE 

98 

99 >>> block = _pad_for_encryption(b'hello', 16) 

100 >>> len(block) 

101 16 

102 >>> block[0:2] 

103 b'\x00\x02' 

104 >>> block[-6:] 

105 b'\x00hello' 

106 

107 """ 

108 

109 max_msglength = target_length - 11 

110 msglength = len(message) 

111 

112 if msglength > max_msglength: 

113 raise OverflowError( 

114 "%i bytes needed for message, but there is only" 

115 " space for %i" % (msglength, max_msglength) 

116 ) 

117 

118 # Get random padding 

119 padding = b"" 

120 padding_length = target_length - msglength - 3 

121 

122 # We remove 0-bytes, so we'll end up with less padding than we've asked for, 

123 # so keep adding data until we're at the correct length. 

124 while len(padding) < padding_length: 

125 needed_bytes = padding_length - len(padding) 

126 

127 # Always read at least 8 bytes more than we need, and trim off the rest 

128 # after removing the 0-bytes. This increases the chance of getting 

129 # enough bytes, especially when needed_bytes is small 

130 new_padding = os.urandom(needed_bytes + 5) 

131 new_padding = new_padding.replace(b"\x00", b"") 

132 padding = padding + new_padding[:needed_bytes] 

133 

134 assert len(padding) == padding_length 

135 

136 return b"".join([b"\x00\x02", padding, b"\x00", message]) 

137 

138 

139def _pad_for_signing(message: bytes, target_length: int) -> bytes: 

140 r"""Pads the message for signing, returning the padded message. 

141 

142 The padding is always a repetition of FF bytes. 

143 

144 :return: 00 01 PADDING 00 MESSAGE 

145 

146 >>> block = _pad_for_signing(b'hello', 16) 

147 >>> len(block) 

148 16 

149 >>> block[0:2] 

150 b'\x00\x01' 

151 >>> block[-6:] 

152 b'\x00hello' 

153 >>> block[2:-6] 

154 b'\xff\xff\xff\xff\xff\xff\xff\xff' 

155 

156 """ 

157 

158 max_msglength = target_length - 11 

159 msglength = len(message) 

160 

161 if msglength > max_msglength: 

162 raise OverflowError( 

163 "%i bytes needed for message, but there is only" 

164 " space for %i" % (msglength, max_msglength) 

165 ) 

166 

167 padding_length = target_length - msglength - 3 

168 

169 return b"".join([b"\x00\x01", padding_length * b"\xff", b"\x00", message]) 

170 

171 

172def encrypt(message: bytes, pub_key: key.PublicKey) -> bytes: 

173 """Encrypts the given message using PKCS#1 v1.5 

174 

175 :param message: the message to encrypt. Must be a byte string no longer than 

176 ``k-11`` bytes, where ``k`` is the number of bytes needed to encode 

177 the ``n`` component of the public key. 

178 :param pub_key: the :py:class:`rsa.PublicKey` to encrypt with. 

179 :raise OverflowError: when the message is too large to fit in the padded 

180 block. 

181 

182 >>> from rsa import key, common 

183 >>> (pub_key, priv_key) = key.newkeys(256) 

184 >>> message = b'hello' 

185 >>> crypto = encrypt(message, pub_key) 

186 

187 The crypto text should be just as long as the public key 'n' component: 

188 

189 >>> len(crypto) == common.byte_size(pub_key.n) 

190 True 

191 

192 """ 

193 

194 keylength = common.byte_size(pub_key.n) 

195 padded = _pad_for_encryption(message, keylength) 

196 

197 payload = transform.bytes2int(padded) 

198 encrypted = core.encrypt_int(payload, pub_key.e, pub_key.n) 

199 block = transform.int2bytes(encrypted, keylength) 

200 

201 return block 

202 

203 

204def decrypt(crypto: bytes, priv_key: key.PrivateKey) -> bytes: 

205 r"""Decrypts the given message using PKCS#1 v1.5 

206 

207 The decryption is considered 'failed' when the resulting cleartext doesn't 

208 start with the bytes 00 02, or when the 00 byte between the padding and 

209 the message cannot be found. 

210 

211 :param crypto: the crypto text as returned by :py:func:`rsa.encrypt` 

212 :param priv_key: the :py:class:`rsa.PrivateKey` to decrypt with. 

213 :raise DecryptionError: when the decryption fails. No details are given as 

214 to why the code thinks the decryption fails, as this would leak 

215 information about the private key. 

216 

217 

218 >>> import rsa 

219 >>> (pub_key, priv_key) = rsa.newkeys(256) 

220 

221 It works with strings: 

222 

223 >>> crypto = encrypt(b'hello', pub_key) 

224 >>> decrypt(crypto, priv_key) 

225 b'hello' 

226 

227 And with binary data: 

228 

229 >>> crypto = encrypt(b'\x00\x00\x00\x00\x01', pub_key) 

230 >>> decrypt(crypto, priv_key) 

231 b'\x00\x00\x00\x00\x01' 

232 

233 Altering the encrypted information will *likely* cause a 

234 :py:class:`rsa.pkcs1.DecryptionError`. If you want to be *sure*, use 

235 :py:func:`rsa.sign`. 

236 

237 

238 .. warning:: 

239 

240 Never display the stack trace of a 

241 :py:class:`rsa.pkcs1.DecryptionError` exception. It shows where in the 

242 code the exception occurred, and thus leaks information about the key. 

243 It's only a tiny bit of information, but every bit makes cracking the 

244 keys easier. 

245 

246 >>> crypto = encrypt(b'hello', pub_key) 

247 >>> crypto = crypto[0:5] + b'X' + crypto[6:] # change a byte 

248 >>> decrypt(crypto, priv_key) 

249 Traceback (most recent call last): 

250 ... 

251 rsa.pkcs1.DecryptionError: Decryption failed 

252 

253 """ 

254 

255 blocksize = common.byte_size(priv_key.n) 

256 encrypted = transform.bytes2int(crypto) 

257 decrypted = priv_key.blinded_decrypt(encrypted) 

258 cleartext = transform.int2bytes(decrypted, blocksize) 

259 

260 # Detect leading zeroes in the crypto. These are not reflected in the 

261 # encrypted value (as leading zeroes do not influence the value of an 

262 # integer). This fixes CVE-2020-13757. 

263 if len(crypto) > blocksize: 

264 # This is operating on public information, so doesn't need to be constant-time. 

265 raise DecryptionError("Decryption failed") 

266 

267 # If we can't find the cleartext marker, decryption failed. 

268 cleartext_marker_bad = not compare_digest(cleartext[:2], b"\x00\x02") 

269 

270 # Find the 00 separator between the padding and the message 

271 sep_idx = cleartext.find(b"\x00", 2) 

272 

273 # sep_idx indicates the position of the `\x00` separator that separates the 

274 # padding from the actual message. The padding should be at least 8 bytes 

275 # long (see https://tools.ietf.org/html/rfc8017#section-7.2.2 step 3), which 

276 # means the separator should be at least at index 10 (because of the 

277 # `\x00\x02` marker that precedes it). 

278 sep_idx_bad = sep_idx < 10 

279 

280 anything_bad = cleartext_marker_bad | sep_idx_bad 

281 if anything_bad: 

282 raise DecryptionError("Decryption failed") 

283 

284 return cleartext[sep_idx + 1 :] 

285 

286 

287def sign_hash(hash_value: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes: 

288 """Signs a precomputed hash with the private key. 

289 

290 Hashes the message, then signs the hash with the given key. This is known 

291 as a "detached signature", because the message itself isn't altered. 

292 

293 :param hash_value: A precomputed hash to sign (ignores message). 

294 :param priv_key: the :py:class:`rsa.PrivateKey` to sign with 

295 :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1', 

296 'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'. 

297 :return: a message signature block. 

298 :raise OverflowError: if the private key is too small to contain the 

299 requested hash. 

300 

301 """ 

302 

303 # Get the ASN1 code for this hash method 

304 if hash_method not in HASH_ASN1: 

305 raise ValueError("Invalid hash method: %s" % hash_method) 

306 asn1code = HASH_ASN1[hash_method] 

307 

308 # Encrypt the hash with the private key 

309 cleartext = asn1code + hash_value 

310 keylength = common.byte_size(priv_key.n) 

311 padded = _pad_for_signing(cleartext, keylength) 

312 

313 payload = transform.bytes2int(padded) 

314 encrypted = priv_key.blinded_encrypt(payload) 

315 block = transform.int2bytes(encrypted, keylength) 

316 

317 return block 

318 

319 

320def sign(message: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes: 

321 """Signs the message with the private key. 

322 

323 Hashes the message, then signs the hash with the given key. This is known 

324 as a "detached signature", because the message itself isn't altered. 

325 

326 :param message: the message to sign. Can be an 8-bit string or a file-like 

327 object. If ``message`` has a ``read()`` method, it is assumed to be a 

328 file-like object. 

329 :param priv_key: the :py:class:`rsa.PrivateKey` to sign with 

330 :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1', 

331 'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'. 

332 :return: a message signature block. 

333 :raise OverflowError: if the private key is too small to contain the 

334 requested hash. 

335 

336 """ 

337 

338 msg_hash = compute_hash(message, hash_method) 

339 return sign_hash(msg_hash, priv_key, hash_method) 

340 

341 

342def verify(message: bytes, signature: bytes, pub_key: key.PublicKey) -> str: 

343 """Verifies that the signature matches the message. 

344 

345 The hash method is detected automatically from the signature. 

346 

347 :param message: the signed message. Can be an 8-bit string or a file-like 

348 object. If ``message`` has a ``read()`` method, it is assumed to be a 

349 file-like object. 

350 :param signature: the signature block, as created with :py:func:`rsa.sign`. 

351 :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message. 

352 :raise VerificationError: when the signature doesn't match the message. 

353 :returns: the name of the used hash. 

354 

355 """ 

356 

357 keylength = common.byte_size(pub_key.n) 

358 encrypted = transform.bytes2int(signature) 

359 decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n) 

360 clearsig = transform.int2bytes(decrypted, keylength) 

361 

362 # Get the hash method 

363 method_name = _find_method_hash(clearsig) 

364 message_hash = compute_hash(message, method_name) 

365 

366 # Reconstruct the expected padded hash 

367 cleartext = HASH_ASN1[method_name] + message_hash 

368 expected = _pad_for_signing(cleartext, keylength) 

369 

370 if len(signature) != keylength: 

371 raise VerificationError("Verification failed") 

372 

373 # Compare with the signed one 

374 if expected != clearsig: 

375 raise VerificationError("Verification failed") 

376 

377 return method_name 

378 

379 

380def find_signature_hash(signature: bytes, pub_key: key.PublicKey) -> str: 

381 """Returns the hash name detected from the signature. 

382 

383 If you also want to verify the message, use :py:func:`rsa.verify()` instead. 

384 It also returns the name of the used hash. 

385 

386 :param signature: the signature block, as created with :py:func:`rsa.sign`. 

387 :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message. 

388 :returns: the name of the used hash. 

389 """ 

390 

391 keylength = common.byte_size(pub_key.n) 

392 encrypted = transform.bytes2int(signature) 

393 decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n) 

394 clearsig = transform.int2bytes(decrypted, keylength) 

395 

396 return _find_method_hash(clearsig) 

397 

398 

399def yield_fixedblocks(infile: typing.BinaryIO, blocksize: int) -> typing.Iterator[bytes]: 

400 """Generator, yields each block of ``blocksize`` bytes in the input file. 

401 

402 :param infile: file to read and separate in blocks. 

403 :param blocksize: block size in bytes. 

404 :returns: a generator that yields the contents of each block 

405 """ 

406 

407 while True: 

408 block = infile.read(blocksize) 

409 

410 read_bytes = len(block) 

411 if read_bytes == 0: 

412 break 

413 

414 yield block 

415 

416 if read_bytes < blocksize: 

417 break 

418 

419 

420def compute_hash(message: typing.Union[bytes, typing.BinaryIO], method_name: str) -> bytes: 

421 """Returns the message digest. 

422 

423 :param message: the signed message. Can be an 8-bit string or a file-like 

424 object. If ``message`` has a ``read()`` method, it is assumed to be a 

425 file-like object. 

426 :param method_name: the hash method, must be a key of 

427 :py:const:`rsa.pkcs1.HASH_METHODS`. 

428 

429 """ 

430 

431 if method_name not in HASH_METHODS: 

432 raise ValueError("Invalid hash method: %s" % method_name) 

433 

434 method = HASH_METHODS[method_name] 

435 hasher = method() 

436 

437 if isinstance(message, bytes): 

438 hasher.update(message) 

439 else: 

440 assert hasattr(message, "read") and hasattr(message.read, "__call__") 

441 # read as 1K blocks 

442 for block in yield_fixedblocks(message, 1024): 

443 hasher.update(block) 

444 

445 return hasher.digest() 

446 

447 

448def _find_method_hash(clearsig: bytes) -> str: 

449 """Finds the hash method. 

450 

451 :param clearsig: full padded ASN1 and hash. 

452 :return: the used hash method. 

453 :raise VerificationFailed: when the hash method cannot be found 

454 """ 

455 

456 for (hashname, asn1code) in HASH_ASN1.items(): 

457 if asn1code in clearsig: 

458 return hashname 

459 

460 raise VerificationError("Verification failed") 

461 

462 

463__all__ = [ 

464 "encrypt", 

465 "decrypt", 

466 "sign", 

467 "verify", 

468 "DecryptionError", 

469 "VerificationError", 

470 "CryptoError", 

471] 

472 

473if __name__ == "__main__": 

474 print("Running doctests 1000x or until failure") 

475 import doctest 

476 

477 for count in range(1000): 

478 (failures, tests) = doctest.testmod() 

479 if failures: 

480 break 

481 

482 if count % 100 == 0 and count: 

483 print("%i times" % count) 

484 

485 print("Doctests done")