Skip to content

Commit 0782d68

Browse files
Legrandindlitz
authored andcommitted
Add side-channel countermeasures to DSA.
This patch strenghten the DSA signing code against side-channel attacks. The DSA signing formulae: r = (g^{k} mod p) mod q s = k^{-1} * (H(m) + r*x) mod q becomes: b = random in [1..q) r = (g^{k} mod p) mod q s = (b * k)^{-1} * (b*H(m) + r*(b*x)) mod q In this way we avoid that the secret (x) gets multiplied by a random factor (r) which is immediately disclosed to an attacker (which we assume can both collect (r) and also monitor the side-channel produced by the multiplication). See also attack DSA_2 in: "Minimum Requirements for Evaluating Side-Channel Attack Resistance of RSA, DSA and Diffie-Hellman Key Exchange Implementations", BSI
1 parent f49fd0e commit 0782d68

File tree

3 files changed

+26
-14
lines changed

3 files changed

+26
-14
lines changed

lib/Crypto/PublicKey/DSA.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@
9898

9999
from Crypto import Random
100100
from Crypto.IO import PKCS8, PEM
101-
from Crypto.Util.number import bytes_to_long, long_to_bytes
101+
from Crypto.Util.number import bytes_to_long, long_to_bytes, getRandomRange
102102
from Crypto.PublicKey import _DSA, _slowmath, pubkey
103103
from Crypto.Util.asn1 import DerObject, DerSequence,\
104104
DerInteger, DerObjectId, DerBitString, newDerSequence, newDerBitString
@@ -234,7 +234,8 @@ def _unblind(self, m, r):
234234
raise TypeError("DSA cannot unblind")
235235

236236
def _sign(self, m, k):
237-
return self.key._sign(m, k)
237+
blind_factor = getRandomRange(1, self.key.q, self._randfunc)
238+
return self.key._sign(m, k, blind_factor)
238239

239240
def _verify(self, m, sig):
240241
(r, s) = sig

lib/Crypto/PublicKey/_slowmath.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -147,15 +147,16 @@ def size(self):
147147
def has_private(self):
148148
return hasattr(self, 'x')
149149

150-
def _sign(self, m, k): # alias for _decrypt
150+
def _sign(self, m, k, blind): # alias for _decrypt
151151
# SECURITY TODO - We _should_ be computing SHA1(m), but we don't because that's the API.
152152
if not self.has_private():
153153
raise TypeError("No private key")
154154
if not (1L < k < self.q):
155155
raise ValueError("k is not between 2 and q-1")
156-
inv_k = inverse(k, self.q) # Compute k**-1 mod q
156+
inv_blind_k = inverse(blind * k, self.q) # Compute (blind * k)**-1 mod q
157+
blind_x = self.x * blind
157158
r = pow(self.g, k, self.p) % self.q # r = (g**k mod p) mod q
158-
s = (inv_k * (m + self.x * r)) % self.q
159+
s = (inv_blind_k * (m * blind + blind_x * r)) % self.q
159160
return (r, s)
160161

161162
def _verify(self, m, r, s):

src/_fastmath.c

+19-9
Original file line numberDiff line numberDiff line change
@@ -156,22 +156,28 @@ static PyObject *rsaKey_size (rsaKey *, PyObject *);
156156
static PyObject *rsaKey_has_private (rsaKey *, PyObject *);
157157

158158
static int
159-
dsaSign (dsaKey * key, mpz_t m, mpz_t k, mpz_t r, mpz_t s)
159+
dsaSign (dsaKey * key, mpz_t m, mpz_t k, mpz_t blind, mpz_t r, mpz_t s)
160160
{
161161
mpz_t temp;
162+
mpz_t temp2;
162163
if (mpz_cmp_ui (k, 2) < 0 || mpz_cmp (k, key->q) >= 0)
163164
{
164165
return 1;
165166
}
166167
mpz_init (temp);
168+
mpz_init (temp2);
167169
MPZ_POWM (r, key->g, k, key->p);
168170
mpz_mod (r, r, key->q);
169-
mpz_invert (s, k, key->q);
170-
mpz_mul (temp, key->x, r);
171-
mpz_add (temp, m, temp);
171+
mpz_mul (temp, blind, key->x);
172+
mpz_mul (temp, temp, r);
173+
mpz_mul (temp2, m, blind);
174+
mpz_add (temp, temp2, temp);
175+
mpz_mul (s, k, blind);
176+
mpz_invert (s, s, key->q);
172177
mpz_mul (s, s, temp);
173178
mpz_mod (s, s, key->q);
174179
mpz_clear (temp);
180+
mpz_clear (temp2);
175181
return 0;
176182
}
177183

@@ -490,21 +496,24 @@ dsaKey_getattro (dsaKey * key, PyObject *attr)
490496
static PyObject *
491497
dsaKey__sign (dsaKey * key, PyObject * args)
492498
{
493-
PyObject *lm, *lk, *lr, *ls, *retval;
494-
mpz_t m, k, r, s;
499+
PyObject *lm, *lk, *lblind, *lr, *ls, *retval;
500+
mpz_t m, k, blind, r, s;
495501
int result;
496-
if (!PyArg_ParseTuple (args, "O!O!", &PyLong_Type, &lm,
497-
&PyLong_Type, &lk))
502+
if (!PyArg_ParseTuple (args, "O!O!O!", &PyLong_Type, &lm,
503+
&PyLong_Type, &lk,
504+
&PyLong_Type, &lblind))
498505
{
499506
return NULL;
500507
}
501508
mpz_init (m);
502509
mpz_init (k);
510+
mpz_init (blind);
503511
mpz_init (r);
504512
mpz_init (s);
505513
longObjToMPZ (m, (PyLongObject *) lm);
506514
longObjToMPZ (k, (PyLongObject *) lk);
507-
result = dsaSign (key, m, k, r, s);
515+
longObjToMPZ (blind, (PyLongObject *) lblind);
516+
result = dsaSign (key, m, k, blind, r, s);
508517
if (result == 1)
509518
{
510519
PyErr_SetString (PyExc_ValueError, "K not between 2 and q");
@@ -515,6 +524,7 @@ dsaKey__sign (dsaKey * key, PyObject * args)
515524
if (lr == NULL || ls == NULL) goto errout;
516525
mpz_clear (m);
517526
mpz_clear (k);
527+
mpz_clear (blind);
518528
mpz_clear (r);
519529
mpz_clear (s);
520530
retval = Py_BuildValue ("(NN)", lr, ls);

0 commit comments

Comments
 (0)