summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSybren A. Stüvel <sybren@stuvel.eu>2016-01-22 13:11:22 +0100
committerSybren A. Stüvel <sybren@stuvel.eu>2016-01-22 13:14:40 +0100
commit2310b34bdb530e0bad793d42f589c9f848ff181b (patch)
tree7add9af08619ac491dadef6c0a9620794e5b68bd
parent15b69b38568cfe883180c397d408207b456e0e06 (diff)
downloadrsa-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.txt1
-rw-r--r--rsa/key.py36
-rw-r--r--rsa/pkcs1.py10
-rw-r--r--tests/test_key.py30
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
diff --git a/rsa/key.py b/rsa/key.py
index d530f33..840407c 100644
--- a/rsa/key.py
+++ b/rsa/key.py
@@ -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)