]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/rsa: precompute moduli
authorFilippo Valsorda <filippo@golang.org>
Sun, 23 Oct 2022 18:03:45 +0000 (20:03 +0200)
committerFilippo Valsorda <filippo@golang.org>
Sat, 19 Nov 2022 16:48:51 +0000 (16:48 +0000)
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>
src/crypto/rsa/pkcs1v15.go
src/crypto/rsa/pss.go
src/crypto/rsa/rsa.go

index 59cf0e6e6c147acfd0f0467715ad4ded8d164e9b..971aee6a6d555e895406ac87a29ff2552b4b67c4 100644 (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.
index 3e01bbd8f8ee912441aa565970ed7904d3da70a8..6f1a0c12a5b9700e27c3a59f52208693494858fc 100644 (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 (
index 14500326522c4547ed70fdc4ffa67cde2e692d7c..c71d35ab5eaf523a53ba5a7edcefbedd4b933709 100644 (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
-       }
-
-       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)
+       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)
+       }
+
+       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
        }