crypto/rsa: generate primes ≡ 7 mod 8 and update comments

Align with the new c2sp.org/det-keygen specification in C2SP/C2SP#197
and import the new benchmarks from C2SP/CCTV#25 since the old ones did
not have candidates ≡ 7 mod 8.

Change-Id: Ie7a36ae4458e0c113593c972d7783bd16a6a6964
Reviewed-on: https://go-review.googlesource.com/c/go/+/733440
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: Sophie Schmieg <sschmieg@google.com>
This commit is contained in:
Filippo Valsorda 2025-12-31 17:56:21 +01:00 committed by Gopher Robot
parent 2361851aa9
commit 5cd903156e
7 changed files with 3307 additions and 766 deletions

View file

@ -1211,6 +1211,16 @@ func rshift1(a *Nat, carry uint) {
}
}
// ShiftRightByOne sets x = x >> 1.
//
// The announced length of x is unchanged.
//
//go:norace
func (x *Nat) ShiftRightByOne() *Nat {
rshift1(x, 0)
return x
}
// DivShortVarTime calculates x = x / y and returns the remainder.
//
// It panics if y is zero.

View file

@ -61,11 +61,11 @@ func GenerateKey(rand io.Reader, bits int) (*PrivateKey, error) {
// However, FIPS 186-5, A.1.1(3) requires computing it as e⁻¹ mod λ(N)
// where λ(N) = lcm(p-1, q-1).
//
// This makes d smaller by 1.5 bits on average, which is irrelevant both
// This makes d smaller by 1.82 bits on average, which is irrelevant both
// because we exclusively use the CRT for private operations and because
// we use constant time windowed exponentiation. On the other hand, it
// requires computing a GCD of two values that are not coprime, and then
// a division, both complex variable-time operations.
// requires computing a GCD of two even numbers, and then a division,
// both complex variable-time operations.
λ, err := totient(P, Q)
if err == errDivisorTooLarge {
// The divisor is too large, try again with different primes.
@ -84,7 +84,7 @@ func GenerateKey(rand io.Reader, bits int) (*PrivateKey, error) {
//
// We waste a prime by retrying the whole process, since 65537 is
// probably only a factor of one of p-1 or q-1, but the probability
// of this check failing is only 1/65537, so it doesn't matter.
// of this check failing is ≈ 2⁻¹⁵, so it doesn't matter.
continue
}
@ -97,9 +97,9 @@ func GenerateKey(rand io.Reader, bits int) (*PrivateKey, error) {
// The probability of this check failing when d is derived from
// (e, p, q) is roughly
//
// 2^(nlen/2) / 2^nlen = 2^(-nlen/2)
// 2^(nlen/2) / λ(N) ≈ 2^(-nlen/2 + 1.82)
//
// so less than 2⁻¹² for keys larger than 256 bits.
// so less than 2⁻¹² for keys larger than 256 bits.
//
// We still need to check to comply with FIPS 186-5, but knowing it has
// negligible chance of failure we can defer the check to the end of key
@ -134,20 +134,27 @@ func GenerateKey(rand io.Reader, bits int) (*PrivateKey, error) {
var errDivisorTooLarge = errors.New("divisor too large")
// totient computes the Carmichael totient function λ(N) = lcm(p-1, q-1).
//
// p and q must be primes congruent to 7 mod 8, so that p-1 and q-1 are both
// even but not divisible by 4.
//
// totient returns errDivisorTooLarge if GCD(p-1, q-1) is larger than 2³²-1.
func totient(p, q *bigmod.Modulus) (*bigmod.Modulus, error) {
a, b := p.Nat().SubOne(p), q.Nat().SubOne(q)
if a.IsOdd() == 1 || b.IsOdd() == 1 {
return nil, errors.New("rsa: internal error: p and q must be 7 mod 8")
}
// lcm(a, b) = a×b / gcd(a, b) = a × (b / gcd(a, b))
// Our GCD requires at least one of the numbers to be odd. For LCM we only
// need to preserve the larger prime power of each prime factor, so we can
// right-shift the number with the fewest trailing zeros until it's odd.
// For odd a, b and m >= n, lcm(a×2ᵐ, b×2ⁿ) = lcm(a×2ᵐ, b).
az, bz := a.TrailingZeroBitsVarTime(), b.TrailingZeroBitsVarTime()
if az < bz {
a = a.ShiftRightVarTime(az)
} else {
b = b.ShiftRightVarTime(bz)
// Our GCD requires at least one of the numbers to be odd.
// We know that a / 2 and b / 2 are odd because p and q are 7 mod 8.
// For LCM we only need to preserve the larger prime power of each
// prime factor, so we can shift out 2 from either of them.
// For odd x, y and m >= n, lcm(x×2, y×2) = lcm(x×2, y).
b = b.ShiftRightByOne()
if b.IsOdd() == 0 {
return nil, errors.New("rsa: internal error: p and q must be 7 mod 8")
}
gcd, err := bigmod.NewNat().GCDVarTime(a, b)
@ -160,10 +167,11 @@ func totient(p, q *bigmod.Modulus) (*bigmod.Modulus, error) {
// To avoid implementing multiple-precision division, we just try again if
// the divisor doesn't fit in a single word. This would have a chance of
// 2⁻⁶⁴ on 64-bit platforms, and 2⁻³² on 32-bit platforms, but testing 2⁻⁶⁴
// edge cases is impractical, and we'd rather not behave differently on
// different platforms, so we reject divisors above 2³²-1.
if gcd.BitLenVarTime() > 32 {
// ~2⁻⁶⁴ on 64-bit platforms, and ~2⁻³² on 32-bit platforms, but testing
// 2⁻⁶⁴ edge cases is impractical, and we'd rather not behave differently on
// different platforms, so we reject divisors above 2³²-1. Note that we also
// add back the factor of 2 we shifted out above.
if gcd.BitLenVarTime()+1 > 32 {
return nil, errDivisorTooLarge
}
if gcd.IsZero() == 1 || gcd.Bits()[0] == 0 {
@ -205,8 +213,10 @@ func randomPrime(rand io.Reader, bits int) ([]byte, error) {
b[1] |= 0b1000_0000
}
// Make the value odd since an even number certainly isn't prime.
b[len(b)-1] |= 1
// Set the three least significant bits, which makes the value 7 mod 8
// (steps 4.3 and 5.3). Even numbers are not prime, and an odd
// (p - 1) / 2 simplifies [millerRabinIteration] and the GCD in [totient].
b[len(b)-1] |= 0b0000_0111
// We don't need to check for p >= √2 × 2^(bits-1) (steps 4.4 and 5.4)
// because we set the top two bits above, so
@ -233,7 +243,7 @@ func randomPrime(rand io.Reader, bits int) ([]byte, error) {
// isPrime runs the Miller-Rabin Probabilistic Primality Test from
// FIPS 186-5, Appendix B.3.1.
//
// w must be a random odd integer greater than three in big-endian order.
// w must be a random integer equal to 3 mod 4 in big-endian order.
// isPrime might return false positives for adversarially chosen values.
//
// isPrime is not constant-time.
@ -339,35 +349,32 @@ var primes = []uint{
type millerRabin struct {
w *bigmod.Modulus
a uint
m []byte
}
// millerRabinSetup prepares state that's reused across multiple iterations of
// the Miller-Rabin test.
//
// w must be a random integer equal to 3 mod 4 in big-endian order.
func millerRabinSetup(w []byte) (*millerRabin, error) {
mr := &millerRabin{}
// Check that w is odd, and precompute Montgomery parameters.
// Check that w is 3 mod 4, and precompute Montgomery parameters.
if len(w) == 0 || w[len(w)-1]&0b11 != 0b11 {
return nil, errors.New("candidate is not 3 mod 4")
}
wm, err := bigmod.NewModulus(w)
if err != nil {
return nil, err
}
if wm.Nat().IsOdd() == 0 {
return nil, errors.New("candidate is even")
}
mr.w = wm
// Compute m = (w-1)/2^a, where m is odd.
wMinus1 := mr.w.Nat().SubOne(mr.w)
if wMinus1.IsZero() == 1 {
return nil, errors.New("candidate is one")
}
mr.a = wMinus1.TrailingZeroBitsVarTime()
// Since w is 3 mod 4, a is always 1, so m = (w-1)/2.
m := mr.w.Nat().SubOne(mr.w).ShiftRightByOne()
// Store mr.m as a big-endian byte slice with leading zero bytes removed,
// for use with [bigmod.Nat.Exp].
m := wMinus1.ShiftRightVarTime(mr.a)
mr.m = m.Bytes(mr.w)
for mr.m[0] == 0 {
mr.m = mr.m[1:]
@ -397,23 +404,11 @@ func millerRabinIteration(mr *millerRabin, bb []byte) (bool, error) {
// If b^(m*2^i) mod w = -1 for some 0 <= i < a, b is a possible prime.
// Otherwise b is composite.
// Start by computing and checking b^m mod w (also the i = 0 case).
// Since a = 1 for our w, we only need to check b^m mod w = +/-1.
z := bigmod.NewNat().Exp(b, mr.m, mr.w)
if z.IsOne() == 1 || z.IsMinusOne(mr.w) == 1 {
return millerRabinPOSSIBLYPRIME, nil
}
// Check b^(m*2^i) mod w = -1 for 0 < i < a.
for range mr.a - 1 {
z.Mul(z, mr.w)
if z.IsMinusOne(mr.w) == 1 {
return millerRabinPOSSIBLYPRIME, nil
}
if z.IsOne() == 1 {
// Future squaring will not turn z == 1 into -1.
break
}
}
return millerRabinCOMPOSITE, nil
}

View file

@ -56,7 +56,13 @@ func TestMillerRabin(t *testing.T) {
B = "0" + B
}
mr, err := millerRabinSetup(decodeHex(t, W))
// Our Miller-Rabin assumes candidates are 3 mod 4.
w := decodeHex(t, W)
if len(w) == 0 || w[len(w)-1]%4 != 3 {
t.Skip("skipping test with W not congruent to 3 mod 4")
}
mr, err := millerRabinSetup(w)
if err != nil {
t.Logf("W = %s", W)
t.Logf("B = %s", B)
@ -133,8 +139,13 @@ func TestTotient(t *testing.T) {
}
}
// a and b must be even and a/2 and b/2 must be odd for totient to work.
if a.Bits()[0]%4 != 2 || b.Bits()[0]%4 != 2 {
t.Skip("skipping test with invalid input for totient")
}
lcm, err := totient(p, q)
if oddDivisorLargerThan32Bits(decodeHex(t, GCD)) {
if new(big.Int).SetBytes(decodeHex(t, GCD)).BitLen() > 32 {
if err != errDivisorTooLarge {
t.Fatalf("expected divisor too large error, got %v", err)
}
@ -156,12 +167,6 @@ func TestTotient(t *testing.T) {
}
}
func oddDivisorLargerThan32Bits(b []byte) bool {
x := new(big.Int).SetBytes(b)
x.Rsh(x, x.TrailingZeroBits())
return x.BitLen() > 32
}
func addOne(b []byte) []byte {
x := new(big.Int).SetBytes(b)
x.Add(x, big.NewInt(1))

View file

@ -826,6 +826,7 @@ func BenchmarkParsePKCS8PrivateKey(b *testing.B) {
func BenchmarkGenerateKey(b *testing.B) {
b.Run("2048", func(b *testing.B) {
b.Setenv("GODEBUG", "cryptocustomrand=1")
primes, err := os.ReadFile("testdata/keygen2048.txt")
if err != nil {
b.Fatal(err)
@ -837,6 +838,32 @@ func BenchmarkGenerateKey(b *testing.B) {
}
}
})
b.Run("3072", func(b *testing.B) {
b.Setenv("GODEBUG", "cryptocustomrand=1")
primes, err := os.ReadFile("testdata/keygen3072.txt")
if err != nil {
b.Fatal(err)
}
for b.Loop() {
r := &testPrimeReader{primes: string(primes)}
if _, err := GenerateKey(r, 3072); err != nil {
b.Fatal(err)
}
}
})
b.Run("4096", func(b *testing.B) {
b.Setenv("GODEBUG", "cryptocustomrand=1")
primes, err := os.ReadFile("testdata/keygen4096.txt")
if err != nil {
b.Fatal(err)
}
for b.Loop() {
r := &testPrimeReader{primes: string(primes)}
if _, err := GenerateKey(r, 4096); err != nil {
b.Fatal(err)
}
}
})
}
// testPrimeReader feeds prime candidates from a text file,

File diff suppressed because it is too large Load diff

1075
src/crypto/rsa/testdata/keygen3072.txt vendored Normal file

File diff suppressed because it is too large Load diff

1429
src/crypto/rsa/testdata/keygen4096.txt vendored Normal file

File diff suppressed because it is too large Load diff