32
32
"""
33
33
34
34
import logging
35
+ import threading
35
36
import typing
36
37
import warnings
37
38
49
50
class AbstractKey :
50
51
"""Abstract superclass for private and public keys."""
51
52
52
- __slots__ = ('n' , 'e' , 'blindfac' , 'blindfac_inverse' )
53
+ __slots__ = ('n' , 'e' , 'blindfac' , 'blindfac_inverse' , 'mutex' )
53
54
54
55
def __init__ (self , n : int , e : int ) -> None :
55
56
self .n = n
@@ -58,6 +59,10 @@ def __init__(self, n: int, e: int) -> None:
58
59
# These will be computed properly on the first call to blind().
59
60
self .blindfac = self .blindfac_inverse = - 1
60
61
62
+ # Used to protect updates to the blinding factor in multi-threaded
63
+ # environments.
64
+ self .mutex = threading .Lock ()
65
+
61
66
@classmethod
62
67
def _load_pkcs1_pem (cls , keyfile : bytes ) -> 'AbstractKey' :
63
68
"""Loads a key in PKCS#1 PEM format, implement in a subclass.
@@ -148,36 +153,33 @@ def save_pkcs1(self, format: str = 'PEM') -> bytes:
148
153
method = self ._assert_format_exists (format , methods )
149
154
return method ()
150
155
151
- def blind (self , message : int ) -> int :
152
- """Performs blinding on the message using random number 'r' .
156
+ def blind (self , message : int ) -> typing . Tuple [ int , int ] :
157
+ """Performs blinding on the message.
153
158
154
159
:param message: the message, as integer, to blind.
155
- :type message: int
156
160
:param r: the random number to blind with.
157
- :type r: int
158
- :return: the blinded message.
159
- :rtype: int
161
+ :return: tuple (the blinded message, the inverse of the used blinding factor)
160
162
161
163
The blinding is such that message = unblind(decrypt(blind(encrypt(message))).
162
164
163
165
See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29
164
166
"""
165
- self ._update_blinding_factor ()
166
- return (message * pow (self .blindfac , self .e , self .n )) % self .n
167
+ blindfac , blindfac_inverse = self ._update_blinding_factor ()
168
+ blinded = (message * pow (blindfac , self .e , self .n )) % self .n
169
+ return blinded , blindfac_inverse
167
170
168
- def unblind (self , blinded : int ) -> int :
169
- """Performs blinding on the message using random number 'r '.
171
+ def unblind (self , blinded : int , blindfac_inverse : int ) -> int :
172
+ """Performs blinding on the message using random number 'blindfac_inverse '.
170
173
171
174
:param blinded: the blinded message, as integer, to unblind.
172
- :param r : the random number to unblind with.
175
+ :param blindfac : the factor to unblind with.
173
176
:return: the original message.
174
177
175
178
The blinding is such that message = unblind(decrypt(blind(encrypt(message))).
176
179
177
180
See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29
178
181
"""
179
-
180
- return (self .blindfac_inverse * blinded ) % self .n
182
+ return (blindfac_inverse * blinded ) % self .n
181
183
182
184
def _initial_blinding_factor (self ) -> int :
183
185
for _ in range (1000 ):
@@ -186,18 +188,29 @@ def _initial_blinding_factor(self) -> int:
186
188
return blind_r
187
189
raise RuntimeError ('unable to find blinding factor' )
188
190
189
- def _update_blinding_factor (self ):
190
- if self .blindfac < 0 :
191
- # Compute initial blinding factor, which is rather slow to do.
192
- self .blindfac = self ._initial_blinding_factor ()
193
- self .blindfac_inverse = rsa .common .inverse (self .blindfac , self .n )
194
- else :
195
- # Reuse previous blinding factor as per section 9 of 'A Timing
196
- # Attack against RSA with the Chinese Remainder Theorem' by Werner
197
- # Schindler.
198
- # See https://tls.mbed.org/public/WSchindler-RSA_Timing_Attack.pdf
199
- self .blindfac = pow (self .blindfac , 2 , self .n )
200
- self .blindfac_inverse = pow (self .blindfac_inverse , 2 , self .n )
191
+ def _update_blinding_factor (self ) -> typing .Tuple [int , int ]:
192
+ """Update blinding factors.
193
+
194
+ Computing a blinding factor is expensive, so instead this function
195
+ does this once, then updates the blinding factor as per section 9
196
+ of 'A Timing Attack against RSA with the Chinese Remainder Theorem'
197
+ by Werner Schindler.
198
+ See https://tls.mbed.org/public/WSchindler-RSA_Timing_Attack.pdf
199
+
200
+ :return: the new blinding factor and its inverse.
201
+ """
202
+
203
+ with self .mutex :
204
+ if self .blindfac < 0 :
205
+ # Compute initial blinding factor, which is rather slow to do.
206
+ self .blindfac = self ._initial_blinding_factor ()
207
+ self .blindfac_inverse = rsa .common .inverse (self .blindfac , self .n )
208
+ else :
209
+ # Reuse previous blinding factor.
210
+ self .blindfac = pow (self .blindfac , 2 , self .n )
211
+ self .blindfac_inverse = pow (self .blindfac_inverse , 2 , self .n )
212
+
213
+ return self .blindfac , self .blindfac_inverse
201
214
202
215
class PublicKey (AbstractKey ):
203
216
"""Represents a public RSA key.
@@ -446,9 +459,10 @@ def blinded_decrypt(self, encrypted: int) -> int:
446
459
:rtype: int
447
460
"""
448
461
449
- blinded = self .blind (encrypted ) # blind before decrypting
462
+ # Blinding and un-blinding should be using the same factor
463
+ blinded , blindfac_inverse = self .blind (encrypted )
450
464
decrypted = rsa .core .decrypt_int (blinded , self .d , self .n )
451
- return self .unblind (decrypted )
465
+ return self .unblind (decrypted , blindfac_inverse )
452
466
453
467
def blinded_encrypt (self , message : int ) -> int :
454
468
"""Encrypts the message using blinding to prevent side-channel attacks.
@@ -460,9 +474,9 @@ def blinded_encrypt(self, message: int) -> int:
460
474
:rtype: int
461
475
"""
462
476
463
- blinded = self .blind (message ) # blind before encrypting
477
+ blinded , blindfac_inverse = self .blind (message )
464
478
encrypted = rsa .core .encrypt_int (blinded , self .d , self .n )
465
- return self .unblind (encrypted )
479
+ return self .unblind (encrypted , blindfac_inverse )
466
480
467
481
@classmethod
468
482
def _load_pkcs1_der (cls , keyfile : bytes ) -> 'PrivateKey' :
0 commit comments