Skip to content

Commit 08f2091

Browse files
FiloSottilegopherbot
authored andcommitted
crypto/ecdsa: use bigmod and nistec instead of math/big and crypto/elliptic
Ignoring custom curves, this makes the whole package constant-time. There is a slight loss in performance for P-384 and P-521 because bigmod is slower than math/big (but P-256 has an assembly scalar field inversion, so doesn't use bigmod for anything big). name old time/op new time/op delta Sign/P256-8 19.2µs ± 2% 19.1µs ± 2% ~ (p=0.268 n=9+10) Sign/P384-8 166µs ± 3% 188µs ± 2% +13.52% (p=0.000 n=10+10) Sign/P521-8 337µs ± 2% 359µs ± 2% +6.46% (p=0.000 n=10+10) Verify/P256-8 58.1µs ± 2% 58.1µs ± 2% ~ (p=0.971 n=10+10) Verify/P384-8 484µs ± 2% 569µs ±12% +17.65% (p=0.000 n=10+10) Verify/P521-8 1.03ms ± 4% 1.14ms ± 2% +11.02% (p=0.000 n=10+10) GenerateKey/P256-8 12.4µs ±12% 12.0µs ± 2% ~ (p=0.063 n=10+10) GenerateKey/P384-8 129µs ±18% 119µs ± 2% ~ (p=0.190 n=10+10) GenerateKey/P521-8 241µs ± 2% 240µs ± 2% ~ (p=0.436 n=10+10) name old alloc/op new alloc/op delta Sign/P256-8 3.08kB ± 0% 2.47kB ± 0% -19.77% (p=0.000 n=10+10) Sign/P384-8 6.16kB ± 0% 2.64kB ± 0% -57.16% (p=0.000 n=10+10) Sign/P521-8 7.87kB ± 0% 3.01kB ± 0% -61.80% (p=0.000 n=10+10) Verify/P256-8 1.29kB ± 1% 0.48kB ± 0% -62.69% (p=0.000 n=10+10) Verify/P384-8 2.49kB ± 1% 0.64kB ± 0% -74.25% (p=0.000 n=10+10) Verify/P521-8 3.31kB ± 0% 0.96kB ± 0% -71.02% (p=0.000 n=7+10) GenerateKey/P256-8 720B ± 0% 920B ± 0% +27.78% (p=0.000 n=10+10) GenerateKey/P384-8 921B ± 0% 1120B ± 0% +21.61% (p=0.000 n=9+10) GenerateKey/P521-8 1.30kB ± 0% 1.44kB ± 0% +10.45% (p=0.000 n=10+10) name old allocs/op new allocs/op delta Sign/P256-8 45.0 ± 0% 33.0 ± 0% -26.67% (p=0.000 n=10+10) Sign/P384-8 69.0 ± 0% 34.0 ± 0% -50.72% (p=0.000 n=10+10) Sign/P521-8 71.0 ± 0% 35.0 ± 0% -50.70% (p=0.000 n=10+10) Verify/P256-8 23.0 ± 0% 10.0 ± 0% -56.52% (p=0.000 n=10+10) Verify/P384-8 43.0 ± 0% 14.0 ± 0% -67.44% (p=0.000 n=10+10) Verify/P521-8 45.0 ± 0% 14.0 ± 0% -68.89% (p=0.000 n=7+10) GenerateKey/P256-8 13.0 ± 0% 14.0 ± 0% +7.69% (p=0.000 n=10+10) GenerateKey/P384-8 16.0 ± 0% 17.0 ± 0% +6.25% (p=0.000 n=10+10) GenerateKey/P521-8 16.5 ± 3% 17.0 ± 0% +3.03% (p=0.033 n=10+10) Change-Id: I4e074ef039b0f7ffbc436a4cdbe4ef90c647018d Reviewed-on: https://go-review.googlesource.com/c/go/+/353849 Auto-Submit: Filippo Valsorda <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Than McIntosh <[email protected]> Reviewed-by: David Chase <[email protected]> Run-TryBot: Filippo Valsorda <[email protected]> Reviewed-by: Roland Shoemaker <[email protected]>
1 parent d7812ab commit 08f2091

18 files changed

+1279
-760
lines changed

src/crypto/ecdsa/ecdsa.go

Lines changed: 406 additions & 220 deletions
Large diffs are not rendered by default.

src/crypto/ecdsa/ecdsa_legacy.go

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package ecdsa
6+
7+
import (
8+
"crypto/elliptic"
9+
"errors"
10+
"io"
11+
"math/big"
12+
13+
"golang.org/x/crypto/cryptobyte"
14+
"golang.org/x/crypto/cryptobyte/asn1"
15+
)
16+
17+
// This file contains a math/big implementation of ECDSA that is only used for
18+
// deprecated custom curves.
19+
20+
func generateLegacy(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) {
21+
k, err := randFieldElement(c, rand)
22+
if err != nil {
23+
return nil, err
24+
}
25+
26+
priv := new(PrivateKey)
27+
priv.PublicKey.Curve = c
28+
priv.D = k
29+
priv.PublicKey.X, priv.PublicKey.Y = c.ScalarBaseMult(k.Bytes())
30+
return priv, nil
31+
}
32+
33+
// hashToInt converts a hash value to an integer. Per FIPS 186-4, Section 6.4,
34+
// we use the left-most bits of the hash to match the bit-length of the order of
35+
// the curve. This also performs Step 5 of SEC 1, Version 2.0, Section 4.1.3.
36+
func hashToInt(hash []byte, c elliptic.Curve) *big.Int {
37+
orderBits := c.Params().N.BitLen()
38+
orderBytes := (orderBits + 7) / 8
39+
if len(hash) > orderBytes {
40+
hash = hash[:orderBytes]
41+
}
42+
43+
ret := new(big.Int).SetBytes(hash)
44+
excess := len(hash)*8 - orderBits
45+
if excess > 0 {
46+
ret.Rsh(ret, uint(excess))
47+
}
48+
return ret
49+
}
50+
51+
var errZeroParam = errors.New("zero parameter")
52+
53+
// Sign signs a hash (which should be the result of hashing a larger message)
54+
// using the private key, priv. If the hash is longer than the bit-length of the
55+
// private key's curve order, the hash will be truncated to that length. It
56+
// returns the signature as a pair of integers. Most applications should use
57+
// SignASN1 instead of dealing directly with r, s.
58+
func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) {
59+
sig, err := SignASN1(rand, priv, hash)
60+
if err != nil {
61+
return nil, nil, err
62+
}
63+
64+
r, s = new(big.Int), new(big.Int)
65+
var inner cryptobyte.String
66+
input := cryptobyte.String(sig)
67+
if !input.ReadASN1(&inner, asn1.SEQUENCE) ||
68+
!input.Empty() ||
69+
!inner.ReadASN1Integer(r) ||
70+
!inner.ReadASN1Integer(s) ||
71+
!inner.Empty() {
72+
return nil, nil, errors.New("invalid ASN.1 from SignASN1")
73+
}
74+
return r, s, nil
75+
}
76+
77+
func signLegacy(priv *PrivateKey, csprng io.Reader, hash []byte) (sig []byte, err error) {
78+
c := priv.Curve
79+
80+
// SEC 1, Version 2.0, Section 4.1.3
81+
N := c.Params().N
82+
if N.Sign() == 0 {
83+
return nil, errZeroParam
84+
}
85+
var k, kInv, r, s *big.Int
86+
for {
87+
for {
88+
k, err = randFieldElement(c, csprng)
89+
if err != nil {
90+
return nil, err
91+
}
92+
93+
kInv = new(big.Int).ModInverse(k, N)
94+
95+
r, _ = c.ScalarBaseMult(k.Bytes())
96+
r.Mod(r, N)
97+
if r.Sign() != 0 {
98+
break
99+
}
100+
}
101+
102+
e := hashToInt(hash, c)
103+
s = new(big.Int).Mul(priv.D, r)
104+
s.Add(s, e)
105+
s.Mul(s, kInv)
106+
s.Mod(s, N) // N != 0
107+
if s.Sign() != 0 {
108+
break
109+
}
110+
}
111+
112+
return encodeSignature(r.Bytes(), s.Bytes())
113+
}
114+
115+
// Verify verifies the signature in r, s of hash using the public key, pub. Its
116+
// return value records whether the signature is valid. Most applications should
117+
// use VerifyASN1 instead of dealing directly with r, s.
118+
func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
119+
sig, err := encodeSignature(r.Bytes(), s.Bytes())
120+
if err != nil {
121+
return false
122+
}
123+
return VerifyASN1(pub, hash, sig)
124+
}
125+
126+
func verifyLegacy(pub *PublicKey, hash []byte, sig []byte) bool {
127+
rBytes, sBytes, err := parseSignature(sig)
128+
if err != nil {
129+
return false
130+
}
131+
r, s := new(big.Int).SetBytes(rBytes), new(big.Int).SetBytes(sBytes)
132+
133+
c := pub.Curve
134+
N := c.Params().N
135+
136+
if r.Sign() <= 0 || s.Sign() <= 0 {
137+
return false
138+
}
139+
if r.Cmp(N) >= 0 || s.Cmp(N) >= 0 {
140+
return false
141+
}
142+
143+
// SEC 1, Version 2.0, Section 4.1.4
144+
e := hashToInt(hash, c)
145+
w := new(big.Int).ModInverse(s, N)
146+
147+
u1 := e.Mul(e, w)
148+
u1.Mod(u1, N)
149+
u2 := w.Mul(r, w)
150+
u2.Mod(u2, N)
151+
152+
x1, y1 := c.ScalarBaseMult(u1.Bytes())
153+
x2, y2 := c.ScalarMult(pub.X, pub.Y, u2.Bytes())
154+
x, y := c.Add(x1, y1, x2, y2)
155+
156+
if x.Sign() == 0 && y.Sign() == 0 {
157+
return false
158+
}
159+
x.Mod(x, N)
160+
return x.Cmp(r) == 0
161+
}
162+
163+
var one = new(big.Int).SetInt64(1)
164+
165+
// randFieldElement returns a random element of the order of the given
166+
// curve using the procedure given in FIPS 186-4, Appendix B.5.2.
167+
func randFieldElement(c elliptic.Curve, rand io.Reader) (k *big.Int, err error) {
168+
// See randomPoint for notes on the algorithm. This has to match, or s390x
169+
// signatures will come out different from other architectures, which will
170+
// break TLS recorded tests.
171+
for {
172+
N := c.Params().N
173+
b := make([]byte, (N.BitLen()+7)/8)
174+
if _, err = io.ReadFull(rand, b); err != nil {
175+
return
176+
}
177+
if excess := len(b)*8 - N.BitLen(); excess > 0 {
178+
b[0] >>= excess
179+
}
180+
k = new(big.Int).SetBytes(b)
181+
if k.Sign() != 0 && k.Cmp(N) < 0 {
182+
return
183+
}
184+
}
185+
}

src/crypto/ecdsa/ecdsa_noasm.go

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,12 @@
66

77
package ecdsa
88

9-
import (
10-
"crypto/cipher"
11-
"crypto/elliptic"
12-
"math/big"
13-
)
9+
import "io"
1410

15-
func sign(priv *PrivateKey, csprng *cipher.StreamReader, c elliptic.Curve, hash []byte) (r, s *big.Int, err error) {
16-
return signGeneric(priv, csprng, c, hash)
11+
func verifyAsm(pub *PublicKey, hash []byte, sig []byte) error {
12+
return errNoAsm
1713
}
1814

19-
func verify(pub *PublicKey, c elliptic.Curve, hash []byte, r, s *big.Int) bool {
20-
return verifyGeneric(pub, c, hash, r, s)
15+
func signAsm(priv *PrivateKey, csprng io.Reader, hash []byte) (sig []byte, err error) {
16+
return nil, errNoAsm
2117
}

0 commit comments

Comments
 (0)