diff options
author | Sybren A. Stüvel <sybren@stuvel.eu> | 2016-01-22 13:11:22 +0100 |
---|---|---|
committer | Sybren A. Stüvel <sybren@stuvel.eu> | 2016-01-22 13:14:40 +0100 |
commit | 2310b34bdb530e0bad793d42f589c9f848ff181b (patch) | |
tree | 7add9af08619ac491dadef6c0a9620794e5b68bd | |
parent | 15b69b38568cfe883180c397d408207b456e0e06 (diff) | |
download | rsa-git-2310b34bdb530e0bad793d42f589c9f848ff181b.tar.gz |
Fix #19: Implemented blinding when decrypting.
This prevents side-channel (such as timing) attacks, see:
https://en.wikipedia.org/wiki/Blinding_%28cryptography%29
-rw-r--r-- | CHANGELOG.txt | 1 | ||||
-rw-r--r-- | rsa/key.py | 36 | ||||
-rw-r--r-- | rsa/pkcs1.py | 10 | ||||
-rw-r--r-- | tests/test_key.py | 30 |
4 files changed, 75 insertions, 2 deletions
diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 0a6226c..e9358c9 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -6,6 +6,7 @@ Version 3.4 - in development ---------------------------------------- - Moved development to Github. +- Solved side-channel vulnerability by implementing blinding, fixes #19 - Fixed bugs #14, #27, #30 @@ -321,6 +321,42 @@ class PrivateKey(AbstractKey): def __ne__(self, other): return not (self == other) + def blind(self, message, r): + """Performs blinding on the message using random number 'r'. + + @param message: the message, as integer, to blind. + @param r: the random number to blind with. + @return: the blinded message. + + The blinding is such that message = unblind(decrypt(blind(encrypt(message))). + + See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29 + + >>> pk = PrivateKey(3727264081, 65537, 3349121513, 65063, 57287) + >>> message = 12345 + >>> encrypted = rsa.core.encrypt_int(message, pk.e, pk.n) + >>> blinded = pk.blind(encrypted, 4134431) # blind before decrypting + >>> decrypted = rsa.core.decrypt_int(blinded, pk.d, pk.n) + >>> pk.unblind(decrypted, 4134431) + 12345 + """ + + return (message * pow(r, self.e, self.n)) % self.n + + def unblind(self, blinded, r): + """Performs blinding on the message using random number 'r'. + + @param blinded: the blinded message, as integer, to unblind. + @param r: the random number to unblind with. + @return: the original message. + + The blinding is such that message = unblind(decrypt(blind(encrypt(message))). + + See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29 + """ + + return (rsa.common.inverse(r, self.n) * blinded) % self.n + @classmethod def _load_pkcs1_der(cls, keyfile): """Loads a key in PKCS#1 DER format. diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index 7d6814c..0b7982c 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -229,8 +229,14 @@ def decrypt(crypto, priv_key): blocksize = common.byte_size(priv_key.n) encrypted = transform.bytes2int(crypto) - decrypted = core.decrypt_int(encrypted, priv_key.d, priv_key.n) - cleartext = transform.int2bytes(decrypted, blocksize) + + # Perform blinded decryption to prevent side-channel attacks. + # See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29 + blinded = priv_key.blind(encrypted, 4134431) # blind before decrypting + decrypted = core.decrypt_int(blinded, priv_key.d, priv_key.n) + unblinded = priv_key.unblind(decrypted, 4134431) + + cleartext = transform.int2bytes(unblinded, blocksize) # If we can't find the cleartext marker, decryption failed. if cleartext[0:2] != b('\x00\x02'): diff --git a/tests/test_key.py b/tests/test_key.py new file mode 100644 index 0000000..df35335 --- /dev/null +++ b/tests/test_key.py @@ -0,0 +1,30 @@ +""" +Some tests for the rsa/key.py file. +""" + + +import unittest + +import rsa.key +import rsa.core + + +class BlindingTest(unittest.TestCase): + + def test_blinding(self): + """Test blinding and unblinding. + + This is basically the doctest of the PrivateKey.blind method, but then + implemented as unittest to allow running on different Python versions. + """ + + pk = rsa.key.PrivateKey(3727264081, 65537, 3349121513, 65063, 57287) + + message = 12345 + encrypted = rsa.core.encrypt_int(message, pk.e, pk.n) + + blinded = pk.blind(encrypted, 4134431) # blind before decrypting + decrypted = rsa.core.decrypt_int(blinded, pk.d, pk.n) + unblinded = pk.unblind(decrypted, 4134431) + + self.assertEqual(unblinded, message) |