Skip to content

Commit 904fdb3

Browse files
committed
crypto/aes,crypto/cipher: add asm implementation for aes-gcm on ppc64le
This adds an asm implementation for aes-gcm on ppc64le to improve performance. Results on power8: name old time/op new time/op delta AESGCMSeal1K-192 13.4µs ± 0% 3.7µs ± 0% -72.48% (p=1.000 n=1+1) AESGCMOpen1K-192 10.6µs ± 0% 2.9µs ± 0% -72.97% (p=1.000 n=1+1) AESGCMSign8K-192 60.2µs ± 0% 1.3µs ± 0% -97.88% (p=1.000 n=1+1) AESGCMSeal8K-192 80.5µs ± 0% 22.9µs ± 0% -71.51% (p=1.000 n=1+1) AESGCMOpen8K-192 80.5µs ± 0% 21.5µs ± 0% -73.27% (p=1.000 n=1+1) Change-Id: I026bd4f417095a987eda0f521004af90bc964661 Reviewed-on: https://go-review.googlesource.com/c/go/+/191969 Run-TryBot: Lynn Boger <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Michael Munday <[email protected]>
1 parent eb4e5de commit 904fdb3

File tree

2 files changed

+820
-0
lines changed

2 files changed

+820
-0
lines changed

src/crypto/aes/gcm_ppc64le.go

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
// Copyright 2019 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 ppc64le
6+
7+
package aes
8+
9+
import (
10+
"crypto/cipher"
11+
"crypto/subtle"
12+
"encoding/binary"
13+
"errors"
14+
)
15+
16+
// This file implements GCM using an optimized GHASH function.
17+
18+
//go:noescape
19+
func gcmInit(productTable *[256]byte, h []byte)
20+
21+
//go:noescape
22+
func gcmHash(output []byte, productTable *[256]byte, inp []byte, len int)
23+
24+
//go:noescape
25+
func gcmMul(output []byte, productTable *[256]byte)
26+
27+
const (
28+
gcmCounterSize = 16
29+
gcmBlockSize = 16
30+
gcmTagSize = 16
31+
gcmStandardNonceSize = 12
32+
)
33+
34+
var errOpen = errors.New("cipher: message authentication failed")
35+
36+
// Assert that aesCipherGCM implements the gcmAble interface.
37+
var _ gcmAble = (*aesCipherAsm)(nil)
38+
39+
type gcmAsm struct {
40+
cipher *aesCipherAsm
41+
// ks is the key schedule, the length of which depends on the size of
42+
// the AES key.
43+
ks []uint32
44+
// productTable contains pre-computed multiples of the binary-field
45+
// element used in GHASH.
46+
productTable [256]byte
47+
// nonceSize contains the expected size of the nonce, in bytes.
48+
nonceSize int
49+
// tagSize contains the size of the tag, in bytes.
50+
tagSize int
51+
}
52+
53+
// NewGCM returns the AES cipher wrapped in Galois Counter Mode. This is only
54+
// called by crypto/cipher.NewGCM via the gcmAble interface.
55+
func (c *aesCipherAsm) NewGCM(nonceSize, tagSize int) (cipher.AEAD, error) {
56+
g := &gcmAsm{cipher: c, ks: c.enc, nonceSize: nonceSize, tagSize: tagSize}
57+
58+
hle := make([]byte, gcmBlockSize)
59+
c.Encrypt(hle, hle)
60+
61+
// Reverse the bytes in each 8 byte chunk
62+
// Load little endian, store big endian
63+
h1 := binary.LittleEndian.Uint64(hle[:8])
64+
h2 := binary.LittleEndian.Uint64(hle[8:])
65+
binary.BigEndian.PutUint64(hle[:8], h1)
66+
binary.BigEndian.PutUint64(hle[8:], h2)
67+
gcmInit(&g.productTable, hle)
68+
69+
return g, nil
70+
}
71+
72+
func (g *gcmAsm) NonceSize() int {
73+
return g.nonceSize
74+
}
75+
76+
func (g *gcmAsm) Overhead() int {
77+
return g.tagSize
78+
}
79+
80+
func sliceForAppend(in []byte, n int) (head, tail []byte) {
81+
if total := len(in) + n; cap(in) >= total {
82+
head = in[:total]
83+
} else {
84+
head = make([]byte, total)
85+
copy(head, in)
86+
}
87+
tail = head[len(in):]
88+
return
89+
}
90+
91+
// deriveCounter computes the initial GCM counter state from the given nonce.
92+
func (g *gcmAsm) deriveCounter(counter *[gcmBlockSize]byte, nonce []byte) {
93+
if len(nonce) == gcmStandardNonceSize {
94+
copy(counter[:], nonce)
95+
counter[gcmBlockSize-1] = 1
96+
} else {
97+
var hash [16]byte
98+
g.paddedGHASH(&hash, nonce)
99+
lens := gcmLengths(0, uint64(len(nonce))*8)
100+
g.paddedGHASH(&hash, lens[:])
101+
copy(counter[:], hash[:])
102+
}
103+
}
104+
105+
// counterCrypt encrypts in using AES in counter mode and places the result
106+
// into out. counter is the initial count value and will be updated with the next
107+
// count value. The length of out must be greater than or equal to the length
108+
// of in.
109+
func (g *gcmAsm) counterCrypt(out, in []byte, counter *[gcmBlockSize]byte) {
110+
var mask [gcmBlockSize]byte
111+
112+
for len(in) >= gcmBlockSize {
113+
// Hint to avoid bounds check
114+
_, _ = in[15], out[15]
115+
g.cipher.Encrypt(mask[:], counter[:])
116+
gcmInc32(counter)
117+
118+
// XOR 16 bytes each loop iteration in 8 byte chunks
119+
in0 := binary.LittleEndian.Uint64(in[0:])
120+
in1 := binary.LittleEndian.Uint64(in[8:])
121+
m0 := binary.LittleEndian.Uint64(mask[:8])
122+
m1 := binary.LittleEndian.Uint64(mask[8:])
123+
binary.LittleEndian.PutUint64(out[:8], in0^m0)
124+
binary.LittleEndian.PutUint64(out[8:], in1^m1)
125+
out = out[16:]
126+
in = in[16:]
127+
}
128+
129+
if len(in) > 0 {
130+
g.cipher.Encrypt(mask[:], counter[:])
131+
gcmInc32(counter)
132+
// XOR leftover bytes
133+
for i, inb := range in {
134+
out[i] = inb ^ mask[i]
135+
}
136+
}
137+
}
138+
139+
// increments the rightmost 32-bits of the count value by 1.
140+
func gcmInc32(counterBlock *[16]byte) {
141+
c := counterBlock[len(counterBlock)-4:]
142+
x := binary.BigEndian.Uint32(c) + 1
143+
binary.BigEndian.PutUint32(c, x)
144+
}
145+
146+
// paddedGHASH pads data with zeroes until its length is a multiple of
147+
// 16-bytes. It then calculates a new value for hash using the ghash
148+
// algorithm.
149+
func (g *gcmAsm) paddedGHASH(hash *[16]byte, data []byte) {
150+
if siz := len(data) - (len(data) % gcmBlockSize); siz > 0 {
151+
gcmHash(hash[:], &g.productTable, data[:], siz)
152+
data = data[siz:]
153+
}
154+
if len(data) > 0 {
155+
var s [16]byte
156+
copy(s[:], data)
157+
gcmHash(hash[:], &g.productTable, s[:], len(s))
158+
}
159+
}
160+
161+
// auth calculates GHASH(ciphertext, additionalData), masks the result with
162+
// tagMask and writes the result to out.
163+
func (g *gcmAsm) auth(out, ciphertext, aad []byte, tagMask *[gcmTagSize]byte) {
164+
var hash [16]byte
165+
g.paddedGHASH(&hash, aad)
166+
g.paddedGHASH(&hash, ciphertext)
167+
lens := gcmLengths(uint64(len(aad))*8, uint64(len(ciphertext))*8)
168+
g.paddedGHASH(&hash, lens[:])
169+
170+
copy(out, hash[:])
171+
for i := range out {
172+
out[i] ^= tagMask[i]
173+
}
174+
}
175+
176+
// Seal encrypts and authenticates plaintext. See the cipher.AEAD interface for
177+
// details.
178+
func (g *gcmAsm) Seal(dst, nonce, plaintext, data []byte) []byte {
179+
if len(nonce) != g.nonceSize {
180+
panic("cipher: incorrect nonce length given to GCM")
181+
}
182+
if uint64(len(plaintext)) > ((1<<32)-2)*BlockSize {
183+
panic("cipher: message too large for GCM")
184+
}
185+
186+
ret, out := sliceForAppend(dst, len(plaintext)+g.tagSize)
187+
188+
var counter, tagMask [gcmBlockSize]byte
189+
g.deriveCounter(&counter, nonce)
190+
191+
g.cipher.Encrypt(tagMask[:], counter[:])
192+
gcmInc32(&counter)
193+
194+
g.counterCrypt(out, plaintext, &counter)
195+
g.auth(out[len(plaintext):], out[:len(plaintext)], data, &tagMask)
196+
197+
return ret
198+
}
199+
200+
// Open authenticates and decrypts ciphertext. See the cipher.AEAD interface
201+
// for details.
202+
func (g *gcmAsm) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
203+
if len(nonce) != g.nonceSize {
204+
panic("cipher: incorrect nonce length given to GCM")
205+
}
206+
if len(ciphertext) < g.tagSize {
207+
return nil, errOpen
208+
}
209+
if uint64(len(ciphertext)) > ((1<<32)-2)*uint64(BlockSize)+uint64(g.tagSize) {
210+
return nil, errOpen
211+
}
212+
213+
tag := ciphertext[len(ciphertext)-g.tagSize:]
214+
ciphertext = ciphertext[:len(ciphertext)-g.tagSize]
215+
216+
var counter, tagMask [gcmBlockSize]byte
217+
g.deriveCounter(&counter, nonce)
218+
219+
g.cipher.Encrypt(tagMask[:], counter[:])
220+
gcmInc32(&counter)
221+
222+
var expectedTag [gcmTagSize]byte
223+
g.auth(expectedTag[:], ciphertext, data, &tagMask)
224+
225+
ret, out := sliceForAppend(dst, len(ciphertext))
226+
227+
if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 {
228+
for i := range out {
229+
out[i] = 0
230+
}
231+
return nil, errOpen
232+
}
233+
234+
g.counterCrypt(out, ciphertext, &counter)
235+
return ret, nil
236+
}
237+
238+
func gcmLengths(len0, len1 uint64) [16]byte {
239+
return [16]byte{
240+
byte(len0 >> 56),
241+
byte(len0 >> 48),
242+
byte(len0 >> 40),
243+
byte(len0 >> 32),
244+
byte(len0 >> 24),
245+
byte(len0 >> 16),
246+
byte(len0 >> 8),
247+
byte(len0),
248+
byte(len1 >> 56),
249+
byte(len1 >> 48),
250+
byte(len1 >> 40),
251+
byte(len1 >> 32),
252+
byte(len1 >> 24),
253+
byte(len1 >> 16),
254+
byte(len1 >> 8),
255+
byte(len1),
256+
}
257+
}

0 commit comments

Comments
 (0)