Skip to content

Commit

Permalink
crypto/rsa: precompute moduli
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
FiloSottile committed Nov 19, 2022
1 parent ee5ccc9 commit 5aa6313
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 54 deletions.
4 changes: 2 additions & 2 deletions src/crypto/rsa/pkcs1v15.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions src/crypto/rsa/pss.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 (
Expand Down
115 changes: 65 additions & 50 deletions src/crypto/rsa/rsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit 5aa6313

Please sign in to comment.