Skip to content

Commit 0fb6c73

Browse files
author
gefeili
committed
Blind the private exponent before the modular exponentiation in the prime-field asymmetric primitives, and add BigIntegers.createBlindedExponent for the shared step.
1 parent e8236be commit 0fb6c73

31 files changed

Lines changed: 1457 additions & 78 deletions

File tree

‎core/src/main/java/org/bouncycastle/crypto/agreement/DHAgreement.java‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.bouncycastle.crypto.params.DHPrivateKeyParameters;
1414
import org.bouncycastle.crypto.params.DHPublicKeyParameters;
1515
import org.bouncycastle.crypto.params.ParametersWithRandom;
16+
import org.bouncycastle.util.BigIntegers;
1617

1718
/**
1819
* a Diffie-Hellman key exchange engine.
@@ -114,12 +115,24 @@ public BigInteger calculateAgreement(DHPublicKeyParameters pub, BigInteger messa
114115
? pub.getY()
115116
: new DHPublicKeyParameters(pub.getY(), dhParams).getY();
116117

117-
BigInteger result = peerY.modPow(privateValue, p);
118+
// Both bases are peer-supplied and both exponents are private - privateValue is this run's
119+
// ephemeral, key.getX() is the long-term key - so both exponents are blinded before the
120+
// variable-time modPow. The multiples are of p-1 rather than of the subgroup order: the
121+
// DHPublicKeyParameters construction above only checks the subgroup when the parameters carry
122+
// q, so a base outside the order-q subgroup is possible, and for a safe prime an odd multiple
123+
// of q would give the wrong shared secret for such a base.
124+
BigInteger pSub1 = p.subtract(ONE);
125+
126+
BigInteger blindedPrivateValue = BigIntegers.createBlindedExponent(privateValue, pSub1, random);
127+
128+
BigInteger result = peerY.modPow(blindedPrivateValue, p);
118129
if (result.equals(ONE))
119130
{
120131
throw new IllegalStateException("Shared key can't be 1");
121132
}
122133

123-
return peerMessage.modPow(key.getX(), p).multiply(result).mod(p);
134+
BigInteger blindedX = BigIntegers.createBlindedExponent(key.getX(), pSub1, random);
135+
136+
return peerMessage.modPow(blindedX, p).multiply(result).mod(p);
124137
}
125138
}

‎core/src/main/java/org/bouncycastle/crypto/agreement/DHBasicAgreement.java‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.bouncycastle.crypto.agreement;
22

33
import java.math.BigInteger;
4+
import java.security.SecureRandom;
45

56
import org.bouncycastle.crypto.BasicAgreement;
67
import org.bouncycastle.crypto.CipherParameters;
@@ -10,6 +11,7 @@
1011
import org.bouncycastle.crypto.params.DHPrivateKeyParameters;
1112
import org.bouncycastle.crypto.params.DHPublicKeyParameters;
1213
import org.bouncycastle.crypto.params.ParametersWithRandom;
14+
import org.bouncycastle.util.BigIntegers;
1315

1416
/**
1517
* a Diffie-Hellman key agreement class.
@@ -25,6 +27,7 @@ public class DHBasicAgreement
2527

2628
private DHPrivateKeyParameters key;
2729
private DHParameters dhParams;
30+
private SecureRandom random;
2831

2932
public void init(
3033
CipherParameters param)
@@ -35,10 +38,12 @@ public void init(
3538
{
3639
ParametersWithRandom rParam = (ParametersWithRandom)param;
3740
kParam = (AsymmetricKeyParameter)rParam.getParameters();
41+
this.random = CryptoServicesRegistrar.getSecureRandom(rParam.getRandom());
3842
}
3943
else
4044
{
4145
kParam = (AsymmetricKeyParameter)param;
46+
this.random = CryptoServicesRegistrar.getSecureRandom();
4247
}
4348

4449
if (!(kParam instanceof DHPrivateKeyParameters))
@@ -72,14 +77,22 @@ public BigInteger calculateAgreement(
7277
}
7378

7479
BigInteger p = dhParams.getP();
80+
BigInteger pSub1 = p.subtract(ONE);
7581

7682
BigInteger peerY = pub.getY();
77-
if (peerY == null || peerY.compareTo(ONE) <= 0 || peerY.compareTo(p.subtract(ONE)) >= 0)
83+
if (peerY == null || peerY.compareTo(ONE) <= 0 || peerY.compareTo(pSub1) >= 0)
7884
{
7985
throw new IllegalArgumentException("Diffie-Hellman public key is weak");
8086
}
8187

82-
BigInteger result = peerY.modPow(key.getX(), p);
88+
// peerY is peer-supplied and the exponent is our private value, which for static-static
89+
// agreement is long lived, so the exponent is blinded before the variable-time modPow sees
90+
// it. The multiple is of p-1 rather than of the subgroup order: peerY has only been range
91+
// checked here, so it need not lie in the order-q subgroup, and for a safe prime an odd
92+
// multiple of q would give the wrong shared secret for the values that do not.
93+
BigInteger blindedX = BigIntegers.createBlindedExponent(key.getX(), pSub1, random);
94+
95+
BigInteger result = peerY.modPow(blindedX, p);
8396
if (result.equals(ONE))
8497
{
8598
throw new IllegalStateException("Shared key can't be 1");

‎core/src/main/java/org/bouncycastle/crypto/agreement/MQVBasicAgreement.java‎

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.bouncycastle.crypto.agreement;
22

33
import java.math.BigInteger;
4+
import java.security.SecureRandom;
45

56
import org.bouncycastle.crypto.BasicAgreement;
67
import org.bouncycastle.crypto.CipherParameters;
@@ -10,18 +11,33 @@
1011
import org.bouncycastle.crypto.params.DHParameters;
1112
import org.bouncycastle.crypto.params.DHPrivateKeyParameters;
1213
import org.bouncycastle.crypto.params.DHPublicKeyParameters;
14+
import org.bouncycastle.crypto.params.ParametersWithRandom;
15+
import org.bouncycastle.util.BigIntegers;
1316

1417
public class MQVBasicAgreement
1518
implements BasicAgreement
1619
{
1720
private static final BigInteger ONE = BigInteger.valueOf(1);
1821

22+
private SecureRandom random;
23+
1924
DHMQVPrivateParameters privParams;
2025

2126
public void init(
2227
CipherParameters key)
2328
{
24-
this.privParams = (DHMQVPrivateParameters)key;
29+
if (key instanceof ParametersWithRandom)
30+
{
31+
ParametersWithRandom rParam = (ParametersWithRandom)key;
32+
33+
this.privParams = (DHMQVPrivateParameters)rParam.getParameters();
34+
this.random = CryptoServicesRegistrar.getSecureRandom(rParam.getRandom());
35+
}
36+
else
37+
{
38+
this.privParams = (DHMQVPrivateParameters)key;
39+
this.random = CryptoServicesRegistrar.getSecureRandom();
40+
}
2541

2642
CryptoServicesRegistrar.checkConstraints(Utils.getDefaultProperties("MQV", this.privParams.getStaticPrivateKey()));
2743
}
@@ -75,7 +91,21 @@ private BigInteger calculateDHMQVAgreement(
7591
BigInteger TA = tA.getY().mod(twoW).add(twoW);
7692
BigInteger SA = rA.getX().add(TA.multiply(xA.getX())).mod(q);
7793
BigInteger TB = tB.getY().mod(twoW).add(twoW);
78-
BigInteger Z = tB.getY().multiply(yB.getY().modPow(TB, parameters.getP())).modPow(SA, parameters.getP());
94+
95+
// SA carries the static private key, and the base below is built entirely from values the
96+
// other party supplied, so the exponent is blinded before the variable-time modPow sees it.
97+
// TB is derived from a public value and is left alone.
98+
//
99+
// A multiple of q is sound here, rather than of p-1, because every value in that base has
100+
// been checked to lie in the order-q subgroup before it arrives: calculateAgreement requires
101+
// q to be present and the peer's static parameters to equal ours, DHMQVPublicParameters
102+
// requires the peer's two public keys to share domain parameters, and
103+
// DHPublicKeyParameters rejects a y outside the subgroup whenever q is present. A product of
104+
// subgroup elements is a subgroup element, so base^q = 1. q also keeps the exponent the size
105+
// of SA, which is already reduced mod q - blinding with p-1 would grow it to the size of p.
106+
BigInteger blindedSA = BigIntegers.createBlindedExponent(SA, q, random);
107+
108+
BigInteger Z = tB.getY().multiply(yB.getY().modPow(TB, parameters.getP())).modPow(blindedSA, parameters.getP());
79109

80110
return Z;
81111
}

‎core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEParticipant.java‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,8 +283,8 @@ public JPAKERound1Payload createRound1PayloadToSend()
283283
this.x1 = JPAKEUtil.generateX1(q, random);
284284
this.x2 = JPAKEUtil.generateX2(q, random);
285285

286-
this.gx1 = JPAKEUtil.calculateGx(p, g, x1);
287-
this.gx2 = JPAKEUtil.calculateGx(p, g, x2);
286+
this.gx1 = JPAKEUtil.calculateGx(p, q, g, x1, random);
287+
this.gx2 = JPAKEUtil.calculateGx(p, q, g, x2, random);
288288
BigInteger[] knowledgeProofForX1 = JPAKEUtil.calculateZeroKnowledgeProof(p, q, g, gx1, x1, participantId, digest, random);
289289
BigInteger[] knowledgeProofForX2 = JPAKEUtil.calculateZeroKnowledgeProof(p, q, g, gx2, x2, participantId, digest, random);
290290

@@ -347,7 +347,7 @@ public JPAKERound2Payload createRound2PayloadToSend()
347347
BigInteger gA = JPAKEUtil.calculateGA(p, gx1, gx3, gx4);
348348
BigInteger s = calculateS();
349349
BigInteger x2s = JPAKEUtil.calculateX2s(q, x2, s);
350-
BigInteger A = JPAKEUtil.calculateA(p, q, gA, x2s);
350+
BigInteger A = JPAKEUtil.calculateA(p, q, gA, x2s, random);
351351
BigInteger[] knowledgeProofForX2s = JPAKEUtil.calculateZeroKnowledgeProof(p, q, gA, A, x2s, participantId, digest, random);
352352

353353
this.state = STATE_ROUND_2_CREATED;
@@ -437,7 +437,7 @@ public BigInteger calculateKeyingMaterial()
437437
Arrays.fill(password, (char)0);
438438
this.password = null;
439439

440-
BigInteger keyingMaterial = JPAKEUtil.calculateKeyingMaterial(p, q, gx4, x2, s, b);
440+
BigInteger keyingMaterial = JPAKEUtil.calculateKeyingMaterial(p, q, gx4, x2, s, b, random);
441441

442442
/*
443443
* Clear the ephemeral private key fields as well.

‎core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEUtil.java‎

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.security.SecureRandom;
55

66
import org.bouncycastle.crypto.CryptoException;
7+
import org.bouncycastle.crypto.CryptoServicesRegistrar;
78
import org.bouncycastle.crypto.Digest;
89
import org.bouncycastle.crypto.Mac;
910
import org.bouncycastle.crypto.macs.HMac;
@@ -27,6 +28,23 @@ public class JPAKEUtil
2728
static final BigInteger ZERO = BigInteger.valueOf(0);
2829
static final BigInteger ONE = BigInteger.valueOf(1);
2930

31+
/**
32+
* Add a random multiple of q to a private exponent, so that the variable-time
33+
* {@link BigInteger#modPow(BigInteger, BigInteger)} applied to it sees a different exponent on
34+
* each call. The result is unchanged because every base J-PAKE raises is known to have order q:
35+
* the generator g is checked with <code>g^q = 1</code> when the
36+
* {@link JPAKEPrimeOrderGroup} is built, and each value received from the other participant is
37+
* checked the same way by
38+
* {@link #validateZeroKnowledgeProof(BigInteger, BigInteger, BigInteger, BigInteger, BigInteger[], String, Digest)}
39+
* before it is ever used as a base. That is what makes a multiple of q sound here rather than
40+
* one of p-1, and it matters for cost: the exponents are the size of q, so blinding with p-1
41+
* would grow them to the size of p.
42+
*/
43+
private static BigInteger blindExponent(BigInteger e, BigInteger q, SecureRandom random)
44+
{
45+
return BigIntegers.createBlindedExponent(e, q, CryptoServicesRegistrar.getSecureRandom(random));
46+
}
47+
3048
/**
3149
* Return a value that can be used as x1 or x3 during round 1.
3250
* <p>
@@ -91,13 +109,37 @@ public static BigInteger calculateS(BigInteger q, char[] password)
91109

92110
/**
93111
* Calculate g^x mod p as done in round 1.
112+
*
113+
* @deprecated x is a private ephemeral value, and this method has no q to randomise the
114+
* exponent with, so it falls back to a multiple of p-1 - always sound, but it grows the exponent
115+
* from the size of q to the size of p. Use
116+
* {@link #calculateGx(BigInteger, BigInteger, BigInteger, BigInteger, SecureRandom)} instead.
94117
*/
95118
public static BigInteger calculateGx(
96119
BigInteger p,
97120
BigInteger g,
98121
BigInteger x)
99122
{
100-
return g.modPow(x, p);
123+
return g.modPow(blindExponent(x, p.subtract(ONE), null), p);
124+
}
125+
126+
/**
127+
* Calculate g^x mod p as done in round 1.
128+
* <p>
129+
* x is the participant's private ephemeral value; leaking it lets an attacker brute-force the
130+
* password, so the exponent is randomised with a multiple of q before it is raised.
131+
*
132+
* @param random source of the randomisation, may be null to take the default from
133+
* {@link CryptoServicesRegistrar}.
134+
*/
135+
public static BigInteger calculateGx(
136+
BigInteger p,
137+
BigInteger q,
138+
BigInteger g,
139+
BigInteger x,
140+
SecureRandom random)
141+
{
142+
return g.modPow(blindExponent(x, q, random), p);
101143
}
102144

103145

@@ -135,9 +177,28 @@ public static BigInteger calculateA(
135177
BigInteger q,
136178
BigInteger gA,
137179
BigInteger x2s)
180+
{
181+
return calculateA(p, q, gA, x2s, null);
182+
}
183+
184+
/**
185+
* Calculate A as done in round 2.
186+
* <p>
187+
* x2s carries the password, so the exponent is randomised with a multiple of q before it is
188+
* raised. gA is a product of values each checked to have order q, so it has order q too.
189+
*
190+
* @param random source of the randomisation, may be null to take the default from
191+
* {@link CryptoServicesRegistrar}.
192+
*/
193+
public static BigInteger calculateA(
194+
BigInteger p,
195+
BigInteger q,
196+
BigInteger gA,
197+
BigInteger x2s,
198+
SecureRandom random)
138199
{
139200
// A = ga^(x*s)
140-
return gA.modPow(x2s, p);
201+
return gA.modPow(blindExponent(x2s, q, random), p);
141202
}
142203

143204
/**
@@ -161,7 +222,9 @@ public static BigInteger[] calculateZeroKnowledgeProof(
161222
BigInteger vMax = q.subtract(ONE);
162223
BigInteger v = BigIntegers.createRandomInRange(vMin, vMax, random);
163224

164-
BigInteger gv = g.modPow(v, p);
225+
// r below is published, so recovering v from the timing of this call would give up x as
226+
// (v - r) / h. The exponent is randomised with a multiple of q before it is raised.
227+
BigInteger gv = g.modPow(blindExponent(v, q, random), p);
165228
BigInteger h = calculateHashForZeroKnowledgeProof(g, gv, gx, participantId, digest); // h
166229

167230
zeroKnowledgeProof[0] = gv;
@@ -282,7 +345,36 @@ public static BigInteger calculateKeyingMaterial(
282345
BigInteger s,
283346
BigInteger B)
284347
{
285-
return gx4.modPow(x2.multiply(s).negate().mod(q), p).multiply(B).modPow(x2, p);
348+
return calculateKeyingMaterial(p, q, gx4, x2, s, B, null);
349+
}
350+
351+
/**
352+
* Calculates the keying material, which can be done after round 2 has completed.
353+
* A session key must be derived from this key material using a secure key derivation function (KDF).
354+
* The KDF used to derive the key is handled externally (i.e. not by {@link JPAKEParticipant}).
355+
* <pre>
356+
* KeyingMaterial = (B/g^{x2*x4*s})^x2
357+
* </pre>
358+
* <p>
359+
* One exponent carries the password and the other the private ephemeral x2, so both are
360+
* randomised with a multiple of q before they are raised. gx4 and B have each been checked to
361+
* have order q, so the product raised by the second call does too.
362+
*
363+
* @param random source of the randomisation, may be null to take the default from
364+
* {@link CryptoServicesRegistrar}.
365+
*/
366+
public static BigInteger calculateKeyingMaterial(
367+
BigInteger p,
368+
BigInteger q,
369+
BigInteger gx4,
370+
BigInteger x2,
371+
BigInteger s,
372+
BigInteger B,
373+
SecureRandom random)
374+
{
375+
BigInteger negX2s = blindExponent(x2.multiply(s).negate().mod(q), q, random);
376+
377+
return gx4.modPow(negX2s, p).multiply(B).modPow(blindExponent(x2, q, random), p);
286378
}
287379

288380
/**

‎core/src/main/java/org/bouncycastle/crypto/agreement/srp/SRP6Client.java‎

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
import java.security.SecureRandom;
55

66
import org.bouncycastle.crypto.CryptoException;
7+
import org.bouncycastle.crypto.CryptoServicesRegistrar;
78
import org.bouncycastle.crypto.Digest;
89
import org.bouncycastle.crypto.params.SRP6GroupParameters;
10+
import org.bouncycastle.util.BigIntegers;
911

1012
/**
1113
* Implements the client side SRP-6a protocol. Note that this class is stateful, and therefore NOT threadsafe.
@@ -68,7 +70,7 @@ public BigInteger generateClientCredentials(byte[] salt, byte[] identity, byte[]
6870
{
6971
this.x = SRP6Util.calculateX(digest, N, salt, identity, password);
7072
this.a = selectPrivateValue();
71-
this.A = g.modPow(a, N);
73+
this.A = g.modPow(blindExponent(a), N);
7274

7375
return A;
7476
}
@@ -97,10 +99,26 @@ private BigInteger calculateS()
9799
{
98100
BigInteger k = SRP6Util.calculateK(digest, N, g);
99101
BigInteger exp = u.multiply(x).add(a);
100-
BigInteger tmp = g.modPow(x, N).multiply(k).mod(N);
101-
return B.subtract(tmp).mod(N).modPow(exp, N);
102+
BigInteger tmp = g.modPow(blindExponent(x), N).multiply(k).mod(N);
103+
return B.subtract(tmp).mod(N).modPow(blindExponent(exp), N);
102104
}
103-
105+
106+
/**
107+
* Add a random multiple of N-1 to a private exponent, so that the variable-time
108+
* BigInteger.modPow applied to it sees a different exponent on each call. Raising any value
109+
* coprime to the prime N to the power N-1 gives 1 by Fermat's little theorem, so the result is
110+
* unchanged; a base of 0 is likewise unaffected, since 0 to any positive power is 0. The
111+
* multiple is of N-1 rather than of the order of g because the bases blinded here include a
112+
* server-supplied value that need not lie in the subgroup g generates, and for a safe prime an
113+
* odd multiple of that order would give the wrong answer for the values that do not.
114+
*/
115+
private BigInteger blindExponent(BigInteger e)
116+
{
117+
return BigIntegers.createBlindedExponent(e, N.subtract(BigInteger.ONE),
118+
CryptoServicesRegistrar.getSecureRandom(random));
119+
}
120+
121+
104122
/**
105123
* Computes the client evidence message M1 using the previously received values.
106124
* To be called after calculating the secret S.

0 commit comments

Comments
 (0)