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

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

129 statements  

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 "SHA3-256": b"\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x08\x05\x00\x04\x20", 

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

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

53} 

54 

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

56 "MD5": hashlib.md5, 

57 "SHA-1": hashlib.sha1, 

58 "SHA-224": hashlib.sha224, 

59 "SHA-256": hashlib.sha256, 

60 "SHA-384": hashlib.sha384, 

61 "SHA-512": hashlib.sha512, 

62 "SHA3-256": hashlib.sha3_256, 

63 "SHA3-384": hashlib.sha3_384, 

64 "SHA3-512": hashlib.sha3_512, 

65} 

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

67 

68 

69class CryptoError(Exception): 

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

71 

72 

73class DecryptionError(CryptoError): 

74 """Raised when decryption fails.""" 

75 

76 

77class VerificationError(CryptoError): 

78 """Raised when verification fails.""" 

79 

80 

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

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

83 

84 :return: 00 02 RANDOM_DATA 00 MESSAGE 

85 

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

87 >>> len(block) 

88 16 

89 >>> block[0:2] 

90 b'\x00\x02' 

91 >>> block[-6:] 

92 b'\x00hello' 

93 

94 """ 

95 

96 max_msglength = target_length - 11 

97 msglength = len(message) 

98 

99 if msglength > max_msglength: 

100 raise OverflowError( 

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

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

103 ) 

104 

105 # Get random padding 

106 padding = b"" 

107 padding_length = target_length - msglength - 3 

108 

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

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

111 while len(padding) < padding_length: 

112 needed_bytes = padding_length - len(padding) 

113 

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

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

116 # enough bytes, especially when needed_bytes is small 

117 new_padding = os.urandom(needed_bytes + 5) 

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

119 padding = padding + new_padding[:needed_bytes] 

120 

121 assert len(padding) == padding_length 

122 

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

124 

125 

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

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

128 

129 The padding is always a repetition of FF bytes. 

130 

131 :return: 00 01 PADDING 00 MESSAGE 

132 

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

134 >>> len(block) 

135 16 

136 >>> block[0:2] 

137 b'\x00\x01' 

138 >>> block[-6:] 

139 b'\x00hello' 

140 >>> block[2:-6] 

141 b'\xff\xff\xff\xff\xff\xff\xff\xff' 

142 

143 """ 

144 

145 max_msglength = target_length - 11 

146 msglength = len(message) 

147 

148 if msglength > max_msglength: 

149 raise OverflowError( 

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

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

152 ) 

153 

154 padding_length = target_length - msglength - 3 

155 

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

157 

158 

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

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

161 

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

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

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

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

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

167 block. 

168 

169 >>> from rsa import key, common 

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

171 >>> message = b'hello' 

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

173 

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

175 

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

177 True 

178 

179 """ 

180 

181 keylength = common.byte_size(pub_key.n) 

182 padded = _pad_for_encryption(message, keylength) 

183 

184 payload = transform.bytes2int(padded) 

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

186 block = transform.int2bytes(encrypted, keylength) 

187 

188 return block 

189 

190 

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

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

193 

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

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

196 the message cannot be found. 

197 

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

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

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

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

202 information about the private key. 

203 

204 

205 >>> import rsa 

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

207 

208 It works with strings: 

209 

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

211 >>> decrypt(crypto, priv_key) 

212 b'hello' 

213 

214 And with binary data: 

215 

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

217 >>> decrypt(crypto, priv_key) 

218 b'\x00\x00\x00\x00\x01' 

219 

220 Altering the encrypted information will *likely* cause a 

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

222 :py:func:`rsa.sign`. 

223 

224 

225 .. warning:: 

226 

227 Never display the stack trace of a 

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

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

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

231 keys easier. 

232 

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

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

235 >>> decrypt(crypto, priv_key) 

236 Traceback (most recent call last): 

237 ... 

238 rsa.pkcs1.DecryptionError: Decryption failed 

239 

240 """ 

241 

242 blocksize = common.byte_size(priv_key.n) 

243 encrypted = transform.bytes2int(crypto) 

244 decrypted = priv_key.blinded_decrypt(encrypted) 

245 cleartext = transform.int2bytes(decrypted, blocksize) 

246 

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

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

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

250 if len(crypto) > blocksize: 

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

252 raise DecryptionError("Decryption failed") 

253 

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

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

256 

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

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

259 

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

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

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

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

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

265 sep_idx_bad = sep_idx < 10 

266 

267 anything_bad = cleartext_marker_bad | sep_idx_bad 

268 if anything_bad: 

269 raise DecryptionError("Decryption failed") 

270 

271 return cleartext[sep_idx + 1 :] 

272 

273 

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

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

276 

277 Signs the hash with the given key. This is known as a "detached signature", 

278 because the message itself isn't altered. 

279 

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

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

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

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

284 :return: a message signature block. 

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

286 requested hash. 

287 

288 """ 

289 

290 # Get the ASN1 code for this hash method 

291 if hash_method not in HASH_ASN1: 

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

293 asn1code = HASH_ASN1[hash_method] 

294 

295 # Encrypt the hash with the private key 

296 cleartext = asn1code + hash_value 

297 keylength = common.byte_size(priv_key.n) 

298 padded = _pad_for_signing(cleartext, keylength) 

299 

300 payload = transform.bytes2int(padded) 

301 encrypted = priv_key.blinded_decrypt(payload) 

302 block = transform.int2bytes(encrypted, keylength) 

303 

304 return block 

305 

306 

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

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

309 

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

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

312 

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

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

315 file-like object. 

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

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

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

319 :return: a message signature block. 

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

321 requested hash. 

322 

323 """ 

324 

325 msg_hash = compute_hash(message, hash_method) 

326 return sign_hash(msg_hash, priv_key, hash_method) 

327 

328 

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

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

331 

332 The hash method is detected automatically from the signature. 

333 

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

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

336 file-like object. 

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

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

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

340 :returns: the name of the used hash. 

341 

342 """ 

343 

344 keylength = common.byte_size(pub_key.n) 

345 if len(signature) != keylength: 

346 raise VerificationError("Verification failed") 

347 

348 encrypted = transform.bytes2int(signature) 

349 decrypted = core.encrypt_int(encrypted, pub_key.e, pub_key.n) 

350 clearsig = transform.int2bytes(decrypted, keylength) 

351 

352 # Get the hash method 

353 method_name = _find_method_hash(clearsig) 

354 message_hash = compute_hash(message, method_name) 

355 

356 # Reconstruct the expected padded hash 

357 cleartext = HASH_ASN1[method_name] + message_hash 

358 expected = _pad_for_signing(cleartext, keylength) 

359 

360 # Compare with the signed one 

361 if expected != clearsig: 

362 raise VerificationError("Verification failed") 

363 

364 return method_name 

365 

366 

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

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

369 

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

371 It also returns the name of the used hash. 

372 

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

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

375 :returns: the name of the used hash. 

376 """ 

377 

378 keylength = common.byte_size(pub_key.n) 

379 encrypted = transform.bytes2int(signature) 

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

381 clearsig = transform.int2bytes(decrypted, keylength) 

382 

383 return _find_method_hash(clearsig) 

384 

385 

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

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

388 

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

390 :param blocksize: block size in bytes. 

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

392 """ 

393 

394 while True: 

395 block = infile.read(blocksize) 

396 

397 read_bytes = len(block) 

398 if read_bytes == 0: 

399 break 

400 

401 yield block 

402 

403 if read_bytes < blocksize: 

404 break 

405 

406 

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

408 """Returns the message digest. 

409 

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

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

412 file-like object. 

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

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

415 

416 """ 

417 

418 if method_name not in HASH_METHODS: 

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

420 

421 method = HASH_METHODS[method_name] 

422 hasher = method() 

423 

424 if isinstance(message, bytes): 

425 hasher.update(message) 

426 else: 

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

428 # read as 1K blocks 

429 for block in yield_fixedblocks(message, 1024): 

430 hasher.update(block) 

431 

432 return hasher.digest() 

433 

434 

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

436 """Finds the hash method. 

437 

438 :param clearsig: full padded ASN1 and hash. 

439 :return: the used hash method. 

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

441 """ 

442 

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

444 if asn1code in clearsig: 

445 return hashname 

446 

447 raise VerificationError("Verification failed") 

448 

449 

450__all__ = [ 

451 "encrypt", 

452 "decrypt", 

453 "sign", 

454 "verify", 

455 "DecryptionError", 

456 "VerificationError", 

457 "CryptoError", 

458] 

459 

460if __name__ == "__main__": 

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

462 import doctest 

463 

464 for count in range(1000): 

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

466 if failures: 

467 break 

468 

469 if count % 100 == 0 and count: 

470 print("%i times" % count) 

471 

472 print("Doctests done")