Skip to content

Commit 8cecfad

Browse files
FiloSottilederekparker
authored andcommitted
crypto/rsa: port Validate to bigmod
This is quite a bit slower (almost entirely in the e * d reductions, which could be optimized), but the slowdown is only 12% of a signature operation. Also, call Validate at the end of GenerateKey as a backstop. Key generation is so incredibly slow that the extra time is negligible. goos: darwin goarch: arm64 pkg: crypto/rsa cpu: Apple M2 │ ec9643bbed │ ec9643bbed-dirty │ │ sec/op │ sec/op vs base │ SignPSS/2048-8 869.8µ ± 1% 870.2µ ± 0% ~ (p=0.937 n=6) GenerateKey/2048-8 104.2m ± 17% 106.9m ± 10% ~ (p=0.589 n=6) ParsePKCS8PrivateKey/2048-8 28.54µ ± 2% 136.78µ ± 8% +379.23% (p=0.002 n=6) Fixes #57751 Co-authored-by: Derek Parker <[email protected]> Change-Id: Ifb476859207925a018b433c16dd62fb767afd2d5 Reviewed-on: https://go-review.googlesource.com/c/go/+/630517 Auto-Submit: Filippo Valsorda <[email protected]> Reviewed-by: Roland Shoemaker <[email protected]> Reviewed-by: Russ Cox <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 3b42687 commit 8cecfad

File tree

3 files changed

+76
-27
lines changed

3 files changed

+76
-27
lines changed

src/crypto/internal/fips140/bigmod/nat.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,19 @@ func (x *Nat) setBytes(b []byte) error {
202202
return nil
203203
}
204204

205+
// SetUint assigns x = y, and returns an error if y >= m.
206+
//
207+
// The output will be resized to the size of m and overwritten.
208+
func (x *Nat) SetUint(y uint, m *Modulus) (*Nat, error) {
209+
x.resetFor(m)
210+
// Modulus is never zero, so always at least one limb.
211+
x.limbs[0] = y
212+
if x.cmpGeq(m.nat) == yes {
213+
return nil, errors.New("input overflows the modulus")
214+
}
215+
return x, nil
216+
}
217+
205218
// Equal returns 1 if x == y, and 0 otherwise.
206219
//
207220
// Both operands must have the same announced length.

src/crypto/rsa/rsa.go

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020
// Decrypter and Signer interfaces from the crypto package.
2121
//
2222
// Operations involving private keys are implemented using constant-time
23-
// algorithms, except for [GenerateKey], [PrivateKey.Precompute], and
24-
// [PrivateKey.Validate].
23+
// algorithms, except for [GenerateKey] and [PrivateKey.Precompute].
2524
//
2625
// # Minimum key size
2726
//
@@ -236,34 +235,67 @@ func (priv *PrivateKey) Validate() error {
236235
return errors.New("crypto/rsa: public exponent too large")
237236
}
238237

239-
// Check that Πprimes == n.
240-
modulus := new(big.Int).Set(bigOne)
241-
for _, prime := range priv.Primes {
242-
// Any primes ≤ 1 will cause divide-by-zero panics later.
243-
if prime.Cmp(bigOne) <= 0 {
244-
return errors.New("crypto/rsa: invalid prime value")
245-
}
246-
modulus.Mul(modulus, prime)
238+
N, err := bigmod.NewModulus(pub.N.Bytes())
239+
if err != nil {
240+
return fmt.Errorf("crypto/rsa: invalid public modulus: %v", err)
247241
}
248-
if modulus.Cmp(priv.N) != 0 {
249-
return errors.New("crypto/rsa: invalid modulus")
242+
d, err := bigmod.NewNat().SetBytes(priv.D.Bytes(), N)
243+
if err != nil {
244+
return fmt.Errorf("crypto/rsa: invalid private exponent: %v", err)
245+
}
246+
one, err := bigmod.NewNat().SetUint(1, N)
247+
if err != nil {
248+
return fmt.Errorf("crypto/rsa: internal error: %v", err)
250249
}
251250

252-
// Check that de ≡ 1 mod p-1, for each prime.
253-
// This implies that e is coprime to each p-1 as e has a multiplicative
254-
// inverse. Therefore e is coprime to lcm(p-1,q-1,r-1,...) =
255-
// exponent(ℤ/nℤ). It also implies that a^de ≡ a mod p as a^(p-1) ≡ 1
256-
// mod p. Thus a^de ≡ a mod n for all a coprime to n, as required.
257-
congruence := new(big.Int)
258-
de := new(big.Int).SetInt64(int64(priv.E))
259-
de.Mul(de, priv.D)
251+
Π := bigmod.NewNat().ExpandFor(N)
260252
for _, prime := range priv.Primes {
261-
pminus1 := new(big.Int).Sub(prime, bigOne)
262-
congruence.Mod(de, pminus1)
263-
if congruence.Cmp(bigOne) != 0 {
253+
p, err := bigmod.NewNat().SetBytes(prime.Bytes(), N)
254+
if err != nil {
255+
return fmt.Errorf("crypto/rsa: invalid prime: %v", err)
256+
}
257+
if p.IsZero() == 1 {
258+
return errors.New("crypto/rsa: invalid prime")
259+
}
260+
Π.Mul(p, N)
261+
262+
// Check that de ≡ 1 mod p-1, for each prime.
263+
// This implies that e is coprime to each p-1 as e has a multiplicative
264+
// inverse. Therefore e is coprime to lcm(p-1,q-1,r-1,...) =
265+
// exponent(ℤ/nℤ). It also implies that a^de ≡ a mod p as a^(p-1) ≡ 1
266+
// mod p. Thus a^de ≡ a mod n for all a coprime to n, as required.
267+
268+
p.Sub(one, N)
269+
if p.IsZero() == 1 {
270+
return errors.New("crypto/rsa: invalid prime")
271+
}
272+
pMinus1, err := bigmod.NewModulus(p.Bytes(N))
273+
if err != nil {
274+
return fmt.Errorf("crypto/rsa: internal error: %v", err)
275+
}
276+
277+
e, err := bigmod.NewNat().SetUint(uint(pub.E), pMinus1)
278+
if err != nil {
279+
return fmt.Errorf("crypto/rsa: invalid public exponent: %v", err)
280+
}
281+
one, err := bigmod.NewNat().SetUint(1, pMinus1)
282+
if err != nil {
283+
return fmt.Errorf("crypto/rsa: internal error: %v", err)
284+
}
285+
286+
de := bigmod.NewNat()
287+
de.Mod(d, pMinus1)
288+
de.Mul(e, pMinus1)
289+
de.Sub(one, pMinus1)
290+
if de.IsZero() != 1 {
264291
return errors.New("crypto/rsa: invalid exponents")
265292
}
266293
}
294+
// Check that Πprimes == n.
295+
if Π.IsZero() != 1 {
296+
return errors.New("crypto/rsa: invalid modulus")
297+
}
298+
267299
return nil
268300
}
269301

@@ -450,6 +482,10 @@ NextSetOfPrimes:
450482
}
451483

452484
priv.Precompute()
485+
if err := priv.Validate(); err != nil {
486+
return nil, err
487+
}
488+
453489
return priv, nil
454490
}
455491

src/crypto/rsa/rsa_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,10 @@ func TestNPrimeKeyGeneration(t *testing.T) {
9898
}
9999

100100
func TestImpossibleKeyGeneration(t *testing.T) {
101-
// This test ensures that trying to generate toy RSA keys doesn't enter
102-
// an infinite loop.
101+
// This test ensures that trying to generate or validate toy RSA keys
102+
// doesn't enter an infinite loop or panic.
103103
t.Setenv("GODEBUG", "rsa1024min=0")
104-
for i := 0; i < 32; i++ {
104+
for i := 0; i < 128; i++ {
105105
GenerateKey(rand.Reader, i)
106106
GenerateMultiPrimeKey(rand.Reader, 3, i)
107107
GenerateMultiPrimeKey(rand.Reader, 4, i)
@@ -184,7 +184,7 @@ func TestEverything(t *testing.T) {
184184
}
185185

186186
t.Setenv("GODEBUG", "rsa1024min=0")
187-
min := 32
187+
min := 128
188188
max := 560 // any smaller than this and not all tests will run
189189
if *allFlag {
190190
max = 2048

0 commit comments

Comments
 (0)