Skip to content
This repository was archived by the owner on Apr 20, 2025. It is now read-only.

Commit 93af6f2

Browse files
committed
Fix CVE-2020-13757: detect cyphertext modifications by prepending zero bytes
Reject cyphertexts that have been modified by prepending zero bytes, by checking the cyphertext length against the expected size (given the decryption key). This resolves CVE-2020-13757. The same approach is used when verifying a signature. Thanks Carnil for pointing this out on #146
1 parent ae1a906 commit 93af6f2

File tree

3 files changed

+56
-0
lines changed

3 files changed

+56
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
is natively supported by Python 3.6+ and supported via a third-party
1616
library on Python 3.5.
1717
- Choose blinding factor relatively prime to N. Thanks Christian Heimes for pointing this out.
18+
- Reject cyphertexts (when decrypting) and signatures (when verifying) that have
19+
been modified by prepending zero bytes. This resolves CVE-2020-13757. Thanks
20+
Carnil for pointing this out.
1821

1922

2023
## Version 4.0 - released 2018-09-16

rsa/pkcs1.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,12 @@ def decrypt(crypto: bytes, priv_key: key.PrivateKey) -> bytes:
245245
decrypted = priv_key.blinded_decrypt(encrypted)
246246
cleartext = transform.int2bytes(decrypted, blocksize)
247247

248+
# Detect leading zeroes in the crypto. These are not reflected in the
249+
# encrypted value (as leading zeroes do not influence the value of an
250+
# integer). This fixes CVE-2020-13757.
251+
if len(crypto) > blocksize:
252+
raise DecryptionError('Decryption failed')
253+
248254
# If we can't find the cleartext marker, decryption failed.
249255
if cleartext[0:2] != b'\x00\x02':
250256
raise DecryptionError('Decryption failed')
@@ -341,6 +347,9 @@ def verify(message: bytes, signature: bytes, pub_key: key.PublicKey) -> str:
341347
cleartext = HASH_ASN1[method_name] + message_hash
342348
expected = _pad_for_signing(cleartext, keylength)
343349

350+
if len(signature) != keylength:
351+
raise VerificationError('Verification failed')
352+
344353
# Compare with the signed one
345354
if expected != clearsig:
346355
raise VerificationError('Verification failed')

tests/test_pkcs1.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,32 @@ def test_randomness(self):
6464
self.assertNotEqual(encrypted1, encrypted2)
6565

6666

67+
class ExtraZeroesTest(unittest.TestCase):
68+
def setUp(self):
69+
# Key, cyphertext, and plaintext taken from https://github.com/sybrenstuvel/python-rsa/issues/146
70+
self.private_key = rsa.PrivateKey.load_pkcs1(
71+
"-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAs1EKK81M5kTFtZSuUFnhKy8FS2WNXaWVmi/fGHG4CLw98+Yo\n0nkuUarVwSS0O9pFPcpc3kvPKOe9Tv+6DLS3Qru21aATy2PRqjqJ4CYn71OYtSwM\n/ZfSCKvrjXybzgu+sBmobdtYm+sppbdL+GEHXGd8gdQw8DDCZSR6+dPJFAzLZTCd\nB+Ctwe/RXPF+ewVdfaOGjkZIzDoYDw7n+OHnsYCYozkbTOcWHpjVevipR+IBpGPi\n1rvKgFnlcG6d/tj0hWRl/6cS7RqhjoiNEtxqoJzpXs/Kg8xbCxXbCchkf11STA8u\ndiCjQWuWI8rcDwl69XMmHJjIQAqhKvOOQ8rYTQIDAQABAoIBABpQLQ7qbHtp4h1Y\nORAfcFRW7Q74UvtH/iEHH1TF8zyM6wZsYtcn4y0mxYE3Mp+J0xlTJbeVJkwZXYVH\nL3UH29CWHSlR+TWiazTwrCTRVJDhEoqbcTiRW8fb+o/jljVxMcVDrpyYUHNo2c6w\njBxhmKPtp66hhaDpds1Cwi0A8APZ8Z2W6kya/L/hRBzMgCz7Bon1nYBMak5PQEwV\nF0dF7Wy4vIjvCzO6DSqA415DvJDzUAUucgFudbANNXo4HJwNRnBpymYIh8mHdmNJ\n/MQ0YLSqUWvOB57dh7oWQwe3UsJ37ZUorTugvxh3NJ7Tt5ZqbCQBEECb9ND63gxo\n/a3YR/0CgYEA7BJc834xCi/0YmO5suBinWOQAF7IiRPU+3G9TdhWEkSYquupg9e6\nK9lC5k0iP+t6I69NYF7+6mvXDTmv6Z01o6oV50oXaHeAk74O3UqNCbLe9tybZ/+F\ndkYlwuGSNttMQBzjCiVy0+y0+Wm3rRnFIsAtd0RlZ24aN3bFTWJINIsCgYEAwnQq\nvNmJe9SwtnH5c/yCqPhKv1cF/4jdQZSGI6/p3KYNxlQzkHZ/6uvrU5V27ov6YbX8\nvKlKfO91oJFQxUD6lpTdgAStI3GMiJBJIZNpyZ9EWNSvwUj28H34cySpbZz3s4Xd\nhiJBShgy+fKURvBQwtWmQHZJ3EGrcOI7PcwiyYcCgYEAlql5jSUCY0ALtidzQogW\nJ+B87N+RGHsBuJ/0cxQYinwg+ySAAVbSyF1WZujfbO/5+YBN362A/1dn3lbswCnH\nK/bHF9+fZNqvwprPnceQj5oK1n4g6JSZNsy6GNAhosT+uwQ0misgR8SQE4W25dDG\nkdEYsz+BgCsyrCcu8J5C+tUCgYAFVPQbC4f2ikVyKzvgz0qx4WUDTBqRACq48p6e\n+eLatv7nskVbr7QgN+nS9+Uz80ihR0Ev1yCAvnwmM/XYAskcOea87OPmdeWZlQM8\nVXNwINrZ6LMNBLgorfuTBK1UoRo1pPUHCYdqxbEYI2unak18mikd2WB7Fp3h0YI4\nVpGZnwKBgBxkAYnZv+jGI4MyEKdsQgxvROXXYOJZkWzsKuKxVkVpYP2V4nR2YMOJ\nViJQ8FUEnPq35cMDlUk4SnoqrrHIJNOvcJSCqM+bWHAioAsfByLbUPM8sm3CDdIk\nXVJl32HuKYPJOMIWfc7hIfxLRHnCN+coz2M6tgqMDs0E/OfjuqVZ\n-----END RSA PRIVATE KEY-----",
72+
format='PEM')
73+
self.cyphertext = bytes.fromhex(
74+
"4501b4d669e01b9ef2dc800aa1b06d49196f5a09fe8fbcd037323c60eaf027bfb98432be4e4a26c567ffec718bcbea977dd26812fa071c33808b4d5ebb742d9879806094b6fbeea63d25ea3141733b60e31c6912106e1b758a7fe0014f075193faa8b4622bfd5d3013f0a32190a95de61a3604711bc62945f95a6522bd4dfed0a994ef185b28c281f7b5e4c8ed41176d12d9fc1b837e6a0111d0132d08a6d6f0580de0c9eed8ed105531799482d1e466c68c23b0c222af7fc12ac279bc4ff57e7b4586d209371b38c4c1035edd418dc5f960441cb21ea2bedbfea86de0d7861e81021b650a1de51002c315f1e7c12debe4dcebf790caaa54a2f26b149cf9e77d"
75+
)
76+
self.plaintext = bytes.fromhex("54657374")
77+
78+
def test_unmodified(self):
79+
message = rsa.decrypt(self.cyphertext, self.private_key)
80+
self.assertEqual(message, self.plaintext)
81+
82+
def test_prepend_zeroes(self):
83+
cyphertext = bytes.fromhex("0000") + self.cyphertext
84+
with self.assertRaises(rsa.DecryptionError):
85+
rsa.decrypt(cyphertext, self.private_key)
86+
87+
def test_append_zeroes(self):
88+
cyphertext = self.cyphertext + bytes.fromhex("0000")
89+
with self.assertRaises(rsa.DecryptionError):
90+
rsa.decrypt(cyphertext, self.private_key)
91+
92+
6793
class SignatureTest(unittest.TestCase):
6894
def setUp(self):
6995
(self.pub, self.priv) = rsa.newkeys(512)
@@ -132,3 +158,21 @@ def test_hash_sign_verify(self):
132158
signature = pkcs1.sign_hash(msg_hash, self.priv, 'SHA-224')
133159

134160
self.assertTrue(pkcs1.verify(message, signature, self.pub))
161+
162+
def test_prepend_zeroes(self):
163+
"""Prepending the signature with zeroes should be detected."""
164+
165+
message = b'je moeder'
166+
signature = pkcs1.sign(message, self.priv, 'SHA-256')
167+
signature = bytes.fromhex('0000') + signature
168+
with self.assertRaises(rsa.VerificationError):
169+
pkcs1.verify(message, signature, self.pub)
170+
171+
def test_apppend_zeroes(self):
172+
"""Apppending the signature with zeroes should be detected."""
173+
174+
message = b'je moeder'
175+
signature = pkcs1.sign(message, self.priv, 'SHA-256')
176+
signature = signature + bytes.fromhex('0000')
177+
with self.assertRaises(rsa.VerificationError):
178+
pkcs1.verify(message, signature, self.pub)

0 commit comments

Comments
 (0)