1
0
mirror of https://github.com/golang/go synced 2024-11-17 11:14:46 -07:00

crypto/rsa: precompute moduli

This change adds some private fields to PrecomputedValues.

If applications were for some reason manually computing the
PrecomputedValues, which they can't do anymore, things will still work
but revert back to the unoptimized path.

name                    old time/op  new time/op  delta
DecryptPKCS1v15/2048-8  1.40ms ± 0%  1.24ms ± 0%  -10.98%  (p=0.000 n=10+8)
DecryptPKCS1v15/3072-8  4.14ms ± 0%  3.78ms ± 1%   -8.55%  (p=0.000 n=10+10)
DecryptPKCS1v15/4096-8  9.09ms ± 0%  8.62ms ± 0%   -5.20%  (p=0.000 n=9+8)
EncryptPKCS1v15/2048-8   139µs ± 0%   138µs ± 0%     ~     (p=0.436 n=9+9)
DecryptOAEP/2048-8      1.40ms ± 0%  1.25ms ± 0%  -11.01%  (p=0.000 n=9+9)
EncryptOAEP/2048-8       139µs ± 0%   139µs ± 0%     ~     (p=0.315 n=10+10)
SignPKCS1v15/2048-8     1.53ms ± 0%  1.29ms ± 0%  -15.93%  (p=0.000 n=9+10)
VerifyPKCS1v15/2048-8    138µs ± 0%   138µs ± 0%     ~     (p=0.052 n=10+10)
SignPSS/2048-8          1.54ms ± 0%  1.29ms ± 0%  -15.89%  (p=0.000 n=9+9)
VerifyPSS/2048-8         139µs ± 0%   139µs ± 0%     ~     (p=0.442 n=8+8)

Change-Id: I843c468db96aa75b18ddff17cec3eadfb579cd0e
Reviewed-on: https://go-review.googlesource.com/c/go/+/445020
Reviewed-by: Joedian Reid <joedian@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
This commit is contained in:
Filippo Valsorda 2022-10-23 20:03:45 +02:00
parent ee5ccc9d4a
commit 5aa6313e58
3 changed files with 67 additions and 52 deletions

View File

@ -181,7 +181,7 @@ func decryptPKCS1v15(priv *PrivateKey, ciphertext []byte) (valid int, em []byte,
return
}
} else {
em, err = decrypt(priv, ciphertext)
em, err = decrypt(priv, ciphertext, noCheck)
if err != nil {
return
}
@ -295,7 +295,7 @@ func SignPKCS1v15(random io.Reader, priv *PrivateKey, hash crypto.Hash, hashed [
copy(em[k-tLen:k-hashLen], prefix)
copy(em[k-hashLen:k], hashed)
return decryptAndCheck(priv, em)
return decrypt(priv, em, withCheck)
}
// VerifyPKCS1v15 verifies an RSA PKCS #1 v1.5 signature.

View File

@ -219,7 +219,7 @@ func signPSSWithSalt(priv *PrivateKey, hash crypto.Hash, hashed, salt []byte) ([
if err != nil {
return nil, err
}
// Note: BoringCrypto takes care of the "AndCheck" part of "decryptAndCheck".
// Note: BoringCrypto always does decrypt "withCheck".
// (It's not just decrypt.)
s, err := boring.DecryptRSANoPadding(bkey, em)
if err != nil {
@ -241,7 +241,7 @@ func signPSSWithSalt(priv *PrivateKey, hash crypto.Hash, hashed, salt []byte) ([
em = emNew
}
return decryptAndCheck(priv, em)
return decrypt(priv, em, withCheck)
}
const (

View File

@ -111,8 +111,9 @@ type PrivateKey struct {
D *big.Int // private exponent
Primes []*big.Int // prime factors of N, has >= 2 elements.
// Precomputed contains precomputed values that speed up private
// operations, if available.
// Precomputed contains precomputed values that speed up RSA operations,
// if available. It must be generated by calling PrivateKey.Precompute and
// must not be modified.
Precomputed PrecomputedValues
}
@ -207,6 +208,8 @@ type PrecomputedValues struct {
// and is implemented by this package without CRT optimizations to limit
// complexity.
CRTValues []CRTValue
n, p, q *modulus // moduli for CRT with Montgomery precomputed constants
}
// CRTValue contains the precomputed Chinese remainder theorem values.
@ -311,6 +314,9 @@ func GenerateMultiPrimeKey(random io.Reader, nprimes int, bits int) (*PrivateKey
Dq: Dq,
Qinv: Qinv,
CRTValues: make([]CRTValue, 0), // non-nil, to match Precompute
n: modulusFromNat(natFromBig(N)),
p: modulusFromNat(natFromBig(P)),
q: modulusFromNat(natFromBig(Q)),
},
}
return key, nil
@ -450,17 +456,23 @@ func encrypt(pub *PublicKey, plaintext []byte) []byte {
N := modulusFromNat(natFromBig(pub.N))
m := natFromBytes(plaintext).expandFor(N)
e := make([]byte, 8)
binary.BigEndian.PutUint64(e, uint64(pub.E))
for len(e) > 1 && e[0] == 0 {
e = e[1:]
}
e := intToBytes(pub.E)
out := make([]byte, modulusSize(N))
return new(nat).exp(m, e, N).fillBytes(out)
}
// intToBytes returns i as a big-endian slice of bytes with no leading zeroes,
// leaking only the bit size of i through timing side-channels.
func intToBytes(i int) []byte {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, uint64(i))
for len(b) > 1 && b[0] == 0 {
b = b[1:]
}
return b
}
// EncryptOAEP encrypts the given message with RSA-OAEP.
//
// OAEP is parameterised by a hash function that is used as a random oracle.
@ -540,6 +552,13 @@ var ErrVerification = errors.New("crypto/rsa: verification error")
// Precompute performs some calculations that speed up private key operations
// in the future.
func (priv *PrivateKey) Precompute() {
if priv.Precomputed.n == nil && len(priv.Primes) == 2 {
priv.Precomputed.n = modulusFromNat(natFromBig(priv.N))
priv.Precomputed.p = modulusFromNat(natFromBig(priv.Primes[0]))
priv.Precomputed.q = modulusFromNat(natFromBig(priv.Primes[1]))
}
// Fill in the backwards-compatibility *big.Int values.
if priv.Precomputed.Dp != nil {
return
}
@ -568,13 +587,21 @@ func (priv *PrivateKey) Precompute() {
}
}
// decrypt performs an RSA decryption of ciphertext into out.
func decrypt(priv *PrivateKey, ciphertext []byte) ([]byte, error) {
const withCheck = true
const noCheck = false
// decrypt performs an RSA decryption of ciphertext into out. If check is true,
// m^e is calculated and compared with ciphertext, in order to defend against
// errors in the CRT computation.
func decrypt(priv *PrivateKey, ciphertext []byte, check bool) ([]byte, error) {
if len(priv.Primes) <= 2 {
boring.Unreachable()
}
N := modulusFromNat(natFromBig(priv.N))
N := priv.Precomputed.n
if N == nil {
N = modulusFromNat(natFromBig(priv.N))
}
c := natFromBytes(ciphertext).expandFor(N)
if c.cmpGeq(N.nat) == 1 {
return nil, ErrDecryption
@ -583,49 +610,37 @@ func decrypt(priv *PrivateKey, ciphertext []byte) ([]byte, error) {
return nil, ErrDecryption
}
// Note that because our private decryption exponents are stored as big.Int,
// we potentially leak the exact number of bits of these exponents. This
// isn't great, but should be fine.
if priv.Precomputed.Dp == nil || len(priv.Primes) > 2 {
out := make([]byte, modulusSize(N))
return new(nat).exp(c, priv.D.Bytes(), N).fillBytes(out), nil
var m *nat
if priv.Precomputed.n == nil {
m = new(nat).exp(c, priv.D.Bytes(), N)
} else {
t0 := new(nat)
P, Q := priv.Precomputed.p, priv.Precomputed.q
// m = c ^ Dp mod p
m = new(nat).exp(t0.mod(c, P), priv.Precomputed.Dp.Bytes(), P)
// m2 = c ^ Dq mod q
m2 := new(nat).exp(t0.mod(c, Q), priv.Precomputed.Dq.Bytes(), Q)
// m = m - m2 mod p
m.modSub(t0.mod(m2, P), P)
// m = m * Qinv mod p
m.modMul(natFromBig(priv.Precomputed.Qinv).expandFor(P), P)
// m = m * q mod N
m.expandFor(N).modMul(t0.mod(Q.nat, N), N)
// m = m + m2 mod N
m.modAdd(m2.expandFor(N), N)
}
t0 := new(nat)
P := modulusFromNat(natFromBig(priv.Primes[0]))
Q := modulusFromNat(natFromBig(priv.Primes[1]))
// m = c ^ Dp mod p
m := new(nat).exp(t0.mod(c, P), priv.Precomputed.Dp.Bytes(), P)
// m2 = c ^ Dq mod q
m2 := new(nat).exp(t0.mod(c, Q), priv.Precomputed.Dq.Bytes(), Q)
// m = m - m2 mod p
m.modSub(t0.mod(m2, P), P)
// m = m * Qinv mod p
m.modMul(natFromBig(priv.Precomputed.Qinv).expandFor(P), P)
// m = m * q mod N
m.expandFor(N).modMul(t0.mod(Q.nat, N), N)
// m = m + m2 mod N
m.modAdd(m2.expandFor(N), N)
if check {
c1 := new(nat).exp(m, intToBytes(priv.E), N)
if c1.cmpEq(c) != 1 {
return nil, ErrDecryption
}
}
out := make([]byte, modulusSize(N))
return m.fillBytes(out), nil
}
func decryptAndCheck(priv *PrivateKey, ciphertext []byte) (m []byte, err error) {
m, err = decrypt(priv, ciphertext)
if err != nil {
return nil, err
}
// In order to defend against errors in the CRT computation, m^e is
// calculated, which should match the original ciphertext.
check := encrypt(&priv.PublicKey, m)
if subtle.ConstantTimeCompare(ciphertext, check) != 1 {
return nil, errors.New("rsa: internal error")
}
return m, nil
}
// DecryptOAEP decrypts ciphertext using RSA-OAEP.
//
// OAEP is parameterised by a hash function that is used as a random oracle.
@ -662,7 +677,7 @@ func decryptOAEP(hash, mgfHash hash.Hash, random io.Reader, priv *PrivateKey, ci
return out, nil
}
em, err := decrypt(priv, ciphertext)
em, err := decrypt(priv, ciphertext, noCheck)
if err != nil {
return nil, err
}