Skip to content

Commit

Permalink
Fix #19: Implemented blinding when decrypting.
Browse files Browse the repository at this point in the history
This prevents side-channel (such as timing) attacks, see:
https://en.wikipedia.org/wiki/Blinding_%28cryptography%29
  • Loading branch information
sybrenstuvel committed Jan 22, 2016
1 parent 15b69b3 commit 2310b34
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.txt
Expand Up @@ -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


Expand Down
36 changes: 36 additions & 0 deletions rsa/key.py
Expand Up @@ -321,6 +321,42 @@ def __eq__(self, other):
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.
Expand Down
10 changes: 8 additions & 2 deletions rsa/pkcs1.py
Expand Up @@ -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'):
Expand Down
30 changes: 30 additions & 0 deletions 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)

0 comments on commit 2310b34

Please sign in to comment.