Skip to content

Commit efeeee3

Browse files
vkrasnovagl
authored andcommitted
crypto/aes: dedicated asm version of AES-GCM
The existing implementation didn't use the CLMUL instructions for fast and constant time binary-field multiplication. With this change, amd64 CPUs that support both AES and CLMUL instructions will use an optimised asm implementation. benchmark old ns/op new ns/op delta BenchmarkAESGCMSeal8K 91723 3200 -96.51% BenchmarkAESGCMOpen8K 91487 3324 -96.37% BenchmarkAESGCMSeal1K 11873 546 -95.40% BenchmarkAESGCMOpen1K 11833 594 -94.98% benchmark old MB/s new MB/s speedup BenchmarkAESGCMSeal8K 89.31 2559.62 28.66x BenchmarkAESGCMOpen8K 89.54 2463.78 27.52x BenchmarkAESGCMSeal1K 86.24 1872.49 21.71x BenchmarkAESGCMOpen1K 86.53 1721.78 19.90x Change-Id: Idd63233098356d8b353d16624747b74d0c3f193e Reviewed-on: https://go-review.googlesource.com/10484 TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Adam Langley <agl@golang.org>
1 parent bfa0161 commit efeeee3

File tree

7 files changed

+1542
-11
lines changed

7 files changed

+1542
-11
lines changed

src/crypto/aes/aes_gcm.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// Copyright 2015 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+
// +build amd64
6+
7+
package aes
8+
9+
import (
10+
"crypto/cipher"
11+
"crypto/subtle"
12+
"errors"
13+
)
14+
15+
// The following functions are defined in gcm_amd64.s.
16+
func hasGCMAsm() bool
17+
func aesEncBlock(dst, src *[16]byte, ks []uint32)
18+
func gcmAesInit(productTable *[256]byte, ks []uint32)
19+
func gcmAesData(productTable *[256]byte, data []byte, T *[16]byte)
20+
func gcmAesEnc(productTable *[256]byte, dst, src []byte, ctr, T *[16]byte, ks []uint32)
21+
func gcmAesDec(productTable *[256]byte, dst, src []byte, ctr, T *[16]byte, ks []uint32)
22+
func gcmAesFinish(productTable *[256]byte, tagMask, T *[16]byte, pLen, dLen uint64)
23+
24+
const (
25+
gcmBlockSize = 16
26+
gcmTagSize = 16
27+
gcmStandardNonceSize = 12
28+
)
29+
30+
var errOpen = errors.New("cipher: message authentication failed")
31+
32+
// aesCipherGCM implements crypto/cipher.gcmAble so that crypto/cipher.NewGCM
33+
// will use the optimised implementation in this file when possible. Instances
34+
// of this type only exist when hasGCMAsm returns true.
35+
type aesCipherGCM struct {
36+
aesCipher
37+
}
38+
39+
// NewGCM returns the AES cipher wrapped in Galois Counter Mode. This is only
40+
// called by crypto/cipher.NewGCM via the gcmAble interface.
41+
func (c *aesCipherGCM) NewGCM(nonceSize int) (cipher.AEAD, error) {
42+
g := &gcmAsm{ks: c.enc, nonceSize: nonceSize}
43+
gcmAesInit(&g.productTable, g.ks)
44+
return g, nil
45+
}
46+
47+
type gcmAsm struct {
48+
// ks is the key schedule, the length of which depends on the size of
49+
// the AES key.
50+
ks []uint32
51+
// productTable contains pre-computed multiples of the binary-field
52+
// element used in GHASH.
53+
productTable [256]byte
54+
// nonceSize contains the expected size of the nonce, in bytes.
55+
nonceSize int
56+
}
57+
58+
func (g *gcmAsm) NonceSize() int {
59+
return g.nonceSize
60+
}
61+
62+
func (*gcmAsm) Overhead() int {
63+
return gcmTagSize
64+
}
65+
66+
// sliceForAppend takes a slice and a requested number of bytes. It returns a
67+
// slice with the contents of the given slice followed by that many bytes and a
68+
// second slice that aliases into it and contains only the extra bytes. If the
69+
// original slice has sufficient capacity then no allocation is performed.
70+
func sliceForAppend(in []byte, n int) (head, tail []byte) {
71+
if total := len(in) + n; cap(in) >= total {
72+
head = in[:total]
73+
} else {
74+
head = make([]byte, total)
75+
copy(head, in)
76+
}
77+
tail = head[len(in):]
78+
return
79+
}
80+
81+
// Seal encrypts and authenticates plaintext. See the cipher.AEAD interface for
82+
// details.
83+
func (g *gcmAsm) Seal(dst, nonce, plaintext, data []byte) []byte {
84+
if len(nonce) != g.nonceSize {
85+
panic("cipher: incorrect nonce length given to GCM")
86+
}
87+
88+
var counter, tagMask [gcmBlockSize]byte
89+
90+
if len(nonce) == gcmStandardNonceSize {
91+
// Init counter to nonce||1
92+
copy(counter[:], nonce)
93+
counter[gcmBlockSize-1] = 1
94+
} else {
95+
// Otherwise counter = GHASH(nonce)
96+
gcmAesData(&g.productTable, nonce, &counter)
97+
gcmAesFinish(&g.productTable, &tagMask, &counter, uint64(len(nonce)), uint64(0))
98+
}
99+
100+
aesEncBlock(&tagMask, &counter, g.ks)
101+
102+
var tagOut [gcmTagSize]byte
103+
gcmAesData(&g.productTable, data, &tagOut)
104+
105+
ret, out := sliceForAppend(dst, len(plaintext)+gcmTagSize)
106+
if len(plaintext) > 0 {
107+
gcmAesEnc(&g.productTable, out, plaintext, &counter, &tagOut, g.ks)
108+
}
109+
gcmAesFinish(&g.productTable, &tagMask, &tagOut, uint64(len(plaintext)), uint64(len(data)))
110+
copy(out[len(plaintext):], tagOut[:])
111+
112+
return ret
113+
}
114+
115+
// Open authenticates and decrypts ciphertext. See the cipher.AEAD interface
116+
// for details.
117+
func (g *gcmAsm) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
118+
if len(nonce) != g.nonceSize {
119+
panic("cipher: incorrect nonce length given to GCM")
120+
}
121+
122+
if len(ciphertext) < gcmTagSize {
123+
return nil, errOpen
124+
}
125+
tag := ciphertext[len(ciphertext)-gcmTagSize:]
126+
ciphertext = ciphertext[:len(ciphertext)-gcmTagSize]
127+
128+
// See GCM spec, section 7.1.
129+
var counter, tagMask [gcmBlockSize]byte
130+
131+
if len(nonce) == gcmStandardNonceSize {
132+
// Init counter to nonce||1
133+
copy(counter[:], nonce)
134+
counter[gcmBlockSize-1] = 1
135+
} else {
136+
// Otherwise counter = GHASH(nonce)
137+
gcmAesData(&g.productTable, nonce, &counter)
138+
gcmAesFinish(&g.productTable, &tagMask, &counter, uint64(len(nonce)), uint64(0))
139+
}
140+
141+
aesEncBlock(&tagMask, &counter, g.ks)
142+
143+
var expectedTag [gcmTagSize]byte
144+
gcmAesData(&g.productTable, data, &expectedTag)
145+
146+
ret, out := sliceForAppend(dst, len(ciphertext))
147+
if len(ciphertext) > 0 {
148+
gcmAesDec(&g.productTable, out, ciphertext, &counter, &expectedTag, g.ks)
149+
}
150+
gcmAesFinish(&g.productTable, &tagMask, &expectedTag, uint64(len(ciphertext)), uint64(len(data)))
151+
152+
if subtle.ConstantTimeCompare(expectedTag[:], tag) != 1 {
153+
for i := range out {
154+
out[i] = 0
155+
}
156+
return nil, errOpen
157+
}
158+
159+
return ret, nil
160+
}

src/crypto/aes/cipher.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,14 @@ func NewCipher(key []byte) (cipher.Block, error) {
3838
}
3939

4040
n := k + 28
41-
c := &aesCipher{make([]uint32, n), make([]uint32, n)}
41+
c := aesCipher{make([]uint32, n), make([]uint32, n)}
4242
expandKey(key, c.enc, c.dec)
43-
return c, nil
43+
44+
if hasGCMAsm() {
45+
return &aesCipherGCM{c}, nil
46+
}
47+
48+
return &c, nil
4449
}
4550

4651
func (c *aesCipher) BlockSize() int { return BlockSize }

src/crypto/aes/cipher_generic.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,11 @@ func decryptBlock(xk []uint32, dst, src []byte) {
1717
func expandKey(key []byte, enc, dec []uint32) {
1818
expandKeyGo(key, enc, dec)
1919
}
20+
21+
func hasGCMAsm() bool {
22+
return false
23+
}
24+
25+
type aesCipherGCM struct {
26+
aesCipher
27+
}

0 commit comments

Comments
 (0)