crypto/rsa: check PrivateKey.D for consistency with Dp and Dq

This unfortunately nearly doubles the runtime of
NewPrivateKeyWithPrecomputation. It would be nice to find an alternative
way to check it.

fips140: off
goos: darwin
goarch: arm64
pkg: crypto/rsa
cpu: Apple M2
                            │ 6aeb841faf  │             62ec3e34f3              │
                            │   sec/op    │    sec/op     vs base               │
ParsePKCS8PrivateKey/2048-8   70.28µ ± 0%   116.16µ ± 0%  +65.28% (p=0.002 n=6)

Fixes #74115

Change-Id: I6a6a6964091817d9aee359cc48932167e55184b9
Reviewed-on: https://go-review.googlesource.com/c/go/+/687836
Auto-Submit: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: Mark Freeman <markfreeman@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
This commit is contained in:
Filippo Valsorda 2025-07-11 14:28:30 +02:00 committed by Gopher Robot
parent 5d9d0513dc
commit ce39174482
3 changed files with 31 additions and 2 deletions

View file

@ -1,2 +1,5 @@
If [PrivateKey] fields are modified after calling [PrivateKey.Precompute], If [PrivateKey] fields are modified after calling [PrivateKey.Precompute],
[PrivateKey.Validate] now fails. [PrivateKey.Validate] now fails.
[PrivateKey.D] is now checked for consistency with precomputed values, even if
it is not used.

View file

@ -233,8 +233,7 @@ func checkPrivateKey(priv *PrivateKey) error {
// It also implies that a^de ≡ a mod p as a^(p-1) ≡ 1 mod p. Thus a^de ≡ a // It also implies that a^de ≡ a mod p as a^(p-1) ≡ 1 mod p. Thus a^de ≡ a
// mod n for all a coprime to n, as required. // mod n for all a coprime to n, as required.
// //
// This checks dP, dQ, and e. We don't check d because it is not actually // This checks dP, dQ, and e.
// used in the RSA private key operation.
pMinus1, err := bigmod.NewModulus(p.Nat().SubOne(p).Bytes(p)) pMinus1, err := bigmod.NewModulus(p.Nat().SubOne(p).Bytes(p))
if err != nil { if err != nil {
return errors.New("crypto/rsa: invalid prime") return errors.New("crypto/rsa: invalid prime")
@ -274,6 +273,17 @@ func checkPrivateKey(priv *PrivateKey) error {
return errors.New("crypto/rsa: invalid CRT coefficient") return errors.New("crypto/rsa: invalid CRT coefficient")
} }
// Check d against dP and dQ, even though we never actually use d,
// to make sure the key is consistent.
dP1 := bigmod.NewNat().Mod(priv.d, pMinus1)
if dP1.Equal(dP) != 1 {
return errors.New("crypto/rsa: d does not match dP")
}
dQ1 := bigmod.NewNat().Mod(priv.d, qMinus1)
if dQ1.Equal(dQ) != 1 {
return errors.New("crypto/rsa: d does not match dQ")
}
// Check that |p - q| > 2^(nlen/2 - 100). // Check that |p - q| > 2^(nlen/2 - 100).
// //
// If p and q are very close to each other, then N=pq can be trivially // If p and q are very close to each other, then N=pq can be trivially

View file

@ -1244,6 +1244,22 @@ func TestModifiedPrivateKey(t *testing.T) {
}) })
}) })
t.Run("D+2", func(t *testing.T) {
testModifiedPrivateKey(t, func(k *PrivateKey) {
k.D = new(big.Int).Add(k.D, big.NewInt(2))
})
})
t.Run("D=0", func(t *testing.T) {
testModifiedPrivateKey(t, func(k *PrivateKey) {
k.D = new(big.Int)
})
})
t.Run("D is nil", func(t *testing.T) {
testModifiedPrivateKey(t, func(k *PrivateKey) {
k.D = nil
})
})
t.Run("N+2", func(t *testing.T) { t.Run("N+2", func(t *testing.T) {
testModifiedPrivateKey(t, func(k *PrivateKey) { testModifiedPrivateKey(t, func(k *PrivateKey) {
k.N = new(big.Int).Add(k.N, big.NewInt(2)) k.N = new(big.Int).Add(k.N, big.NewInt(2))