aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/crypto/cipher
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/crypto/cipher')
-rw-r--r--libgo/go/crypto/cipher/benchmark_test.go139
-rw-r--r--libgo/go/crypto/cipher/cbc.go17
-rw-r--r--libgo/go/crypto/cipher/cfb.go60
-rw-r--r--libgo/go/crypto/cipher/cfb_test.go8
-rw-r--r--libgo/go/crypto/cipher/cipher.go10
-rw-r--r--libgo/go/crypto/cipher/ctr.go55
-rw-r--r--libgo/go/crypto/cipher/gcm.go21
-rw-r--r--libgo/go/crypto/cipher/gcm_test.go16
-rw-r--r--libgo/go/crypto/cipher/ofb.go42
-rw-r--r--libgo/go/crypto/cipher/xor.go84
-rw-r--r--libgo/go/crypto/cipher/xor_test.go28
11 files changed, 369 insertions, 111 deletions
diff --git a/libgo/go/crypto/cipher/benchmark_test.go b/libgo/go/crypto/cipher/benchmark_test.go
new file mode 100644
index 0000000..0b173a4
--- /dev/null
+++ b/libgo/go/crypto/cipher/benchmark_test.go
@@ -0,0 +1,139 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cipher_test
+
+import (
+ "crypto/aes"
+ "crypto/cipher"
+ "testing"
+)
+
+func BenchmarkAESGCMSeal1K(b *testing.B) {
+ buf := make([]byte, 1024)
+ b.SetBytes(int64(len(buf)))
+
+ var key [16]byte
+ var nonce [12]byte
+ aes, _ := aes.NewCipher(key[:])
+ aesgcm, _ := cipher.NewGCM(aes)
+ var out []byte
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ out = aesgcm.Seal(out[:0], nonce[:], buf, nonce[:])
+ }
+}
+
+func BenchmarkAESGCMOpen1K(b *testing.B) {
+ buf := make([]byte, 1024)
+ b.SetBytes(int64(len(buf)))
+
+ var key [16]byte
+ var nonce [12]byte
+ aes, _ := aes.NewCipher(key[:])
+ aesgcm, _ := cipher.NewGCM(aes)
+ var out []byte
+ out = aesgcm.Seal(out[:0], nonce[:], buf, nonce[:])
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, err := aesgcm.Open(buf[:0], nonce[:], out, nonce[:])
+ if err != nil {
+ b.Errorf("Open: %v", err)
+ }
+ }
+}
+
+// If we test exactly 1K blocks, we would generate exact multiples of
+// the cipher's block size, and and the cipher stream fragments would
+// always be wordsize aligned, whereas non-aligned is a more typical
+// use-case.
+const almost1K = 1024 - 5
+
+func BenchmarkAESCFBEncrypt1K(b *testing.B) {
+ buf := make([]byte, almost1K)
+ b.SetBytes(int64(len(buf)))
+
+ var key [16]byte
+ var iv [16]byte
+ aes, _ := aes.NewCipher(key[:])
+ ctr := cipher.NewCFBEncrypter(aes, iv[:])
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ ctr.XORKeyStream(buf, buf)
+ }
+}
+
+func BenchmarkAESCFBDecrypt1K(b *testing.B) {
+ buf := make([]byte, almost1K)
+ b.SetBytes(int64(len(buf)))
+
+ var key [16]byte
+ var iv [16]byte
+ aes, _ := aes.NewCipher(key[:])
+ ctr := cipher.NewCFBDecrypter(aes, iv[:])
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ ctr.XORKeyStream(buf, buf)
+ }
+}
+
+func BenchmarkAESOFB1K(b *testing.B) {
+ buf := make([]byte, almost1K)
+ b.SetBytes(int64(len(buf)))
+
+ var key [16]byte
+ var iv [16]byte
+ aes, _ := aes.NewCipher(key[:])
+ ctr := cipher.NewOFB(aes, iv[:])
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ ctr.XORKeyStream(buf, buf)
+ }
+}
+
+func BenchmarkAESCTR1K(b *testing.B) {
+ buf := make([]byte, almost1K)
+ b.SetBytes(int64(len(buf)))
+
+ var key [16]byte
+ var iv [16]byte
+ aes, _ := aes.NewCipher(key[:])
+ ctr := cipher.NewCTR(aes, iv[:])
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ ctr.XORKeyStream(buf, buf)
+ }
+}
+
+func BenchmarkAESCBCEncrypt1K(b *testing.B) {
+ buf := make([]byte, 1024)
+ b.SetBytes(int64(len(buf)))
+
+ var key [16]byte
+ var iv [16]byte
+ aes, _ := aes.NewCipher(key[:])
+ cbc := cipher.NewCBCEncrypter(aes, iv[:])
+ for i := 0; i < b.N; i++ {
+ cbc.CryptBlocks(buf, buf)
+ }
+}
+
+func BenchmarkAESCBCDecrypt1K(b *testing.B) {
+ buf := make([]byte, 1024)
+ b.SetBytes(int64(len(buf)))
+
+ var key [16]byte
+ var iv [16]byte
+ aes, _ := aes.NewCipher(key[:])
+ cbc := cipher.NewCBCDecrypter(aes, iv[:])
+ for i := 0; i < b.N; i++ {
+ cbc.CryptBlocks(buf, buf)
+ }
+}
diff --git a/libgo/go/crypto/cipher/cbc.go b/libgo/go/crypto/cipher/cbc.go
index 4189677..9a2aece 100644
--- a/libgo/go/crypto/cipher/cbc.go
+++ b/libgo/go/crypto/cipher/cbc.go
@@ -49,13 +49,9 @@ func (x *cbcEncrypter) CryptBlocks(dst, src []byte) {
panic("crypto/cipher: output smaller than input")
}
for len(src) > 0 {
- for i := 0; i < x.blockSize; i++ {
- x.iv[i] ^= src[i]
- }
+ xorBytes(x.iv, x.iv, src[:x.blockSize])
x.b.Encrypt(x.iv, x.iv)
- for i := 0; i < x.blockSize; i++ {
- dst[i] = x.iv[i]
- }
+ copy(dst, x.iv)
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
@@ -91,12 +87,9 @@ func (x *cbcDecrypter) CryptBlocks(dst, src []byte) {
}
for len(src) > 0 {
x.b.Decrypt(x.tmp, src[:x.blockSize])
- for i := 0; i < x.blockSize; i++ {
- x.tmp[i] ^= x.iv[i]
- x.iv[i] = src[i]
- dst[i] = x.tmp[i]
- }
-
+ xorBytes(x.tmp, x.tmp, x.iv)
+ copy(x.iv, src)
+ copy(dst, x.tmp)
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
diff --git a/libgo/go/crypto/cipher/cfb.go b/libgo/go/crypto/cipher/cfb.go
index 99006b5..9b4eebf 100644
--- a/libgo/go/crypto/cipher/cfb.go
+++ b/libgo/go/crypto/cipher/cfb.go
@@ -8,18 +8,41 @@ package cipher
type cfb struct {
b Block
+ next []byte
out []byte
outUsed int
+
decrypt bool
}
+func (x *cfb) XORKeyStream(dst, src []byte) {
+ for len(src) > 0 {
+ if x.outUsed == len(x.out) {
+ x.b.Encrypt(x.out, x.next)
+ x.outUsed = 0
+ }
+
+ if x.decrypt {
+ // We can precompute a larger segment of the
+ // keystream on decryption. This will allow
+ // larger batches for xor, and we should be
+ // able to match CTR/OFB performance.
+ copy(x.next[x.outUsed:], src)
+ }
+ n := xorBytes(dst, src, x.out[x.outUsed:])
+ if !x.decrypt {
+ copy(x.next[x.outUsed:], dst)
+ }
+ dst = dst[n:]
+ src = src[n:]
+ x.outUsed += n
+ }
+}
+
// NewCFBEncrypter returns a Stream which encrypts with cipher feedback mode,
// using the given Block. The iv must be the same length as the Block's block
// size.
func NewCFBEncrypter(block Block, iv []byte) Stream {
- if len(iv) != block.BlockSize() {
- panic("cipher.NewCBFEncrypter: IV length must equal block size")
- }
return newCFB(block, iv, false)
}
@@ -27,44 +50,23 @@ func NewCFBEncrypter(block Block, iv []byte) Stream {
// using the given Block. The iv must be the same length as the Block's block
// size.
func NewCFBDecrypter(block Block, iv []byte) Stream {
- if len(iv) != block.BlockSize() {
- panic("cipher.NewCBFEncrypter: IV length must equal block size")
- }
return newCFB(block, iv, true)
}
func newCFB(block Block, iv []byte, decrypt bool) Stream {
blockSize := block.BlockSize()
if len(iv) != blockSize {
- return nil
+ // stack trace will indicate whether it was de or encryption
+ panic("cipher.newCFB: IV length must equal block size")
}
-
x := &cfb{
b: block,
out: make([]byte, blockSize),
- outUsed: 0,
+ next: make([]byte, blockSize),
+ outUsed: blockSize,
decrypt: decrypt,
}
- block.Encrypt(x.out, iv)
+ copy(x.next, iv)
return x
}
-
-func (x *cfb) XORKeyStream(dst, src []byte) {
- for i := 0; i < len(src); i++ {
- if x.outUsed == len(x.out) {
- x.b.Encrypt(x.out, x.out)
- x.outUsed = 0
- }
-
- if x.decrypt {
- t := src[i]
- dst[i] = src[i] ^ x.out[x.outUsed]
- x.out[x.outUsed] = t
- } else {
- x.out[x.outUsed] ^= src[i]
- dst[i] = x.out[x.outUsed]
- }
- x.outUsed++
- }
-}
diff --git a/libgo/go/crypto/cipher/cfb_test.go b/libgo/go/crypto/cipher/cfb_test.go
index f704b33..ec708ab 100644
--- a/libgo/go/crypto/cipher/cfb_test.go
+++ b/libgo/go/crypto/cipher/cfb_test.go
@@ -19,16 +19,18 @@ func TestCFB(t *testing.T) {
return
}
- plaintext := []byte("this is the plaintext")
+ plaintext := []byte("this is the plaintext. this is the plaintext.")
iv := make([]byte, block.BlockSize())
rand.Reader.Read(iv)
cfb := cipher.NewCFBEncrypter(block, iv)
ciphertext := make([]byte, len(plaintext))
- cfb.XORKeyStream(ciphertext, plaintext)
+ copy(ciphertext, plaintext)
+ cfb.XORKeyStream(ciphertext, ciphertext)
cfbdec := cipher.NewCFBDecrypter(block, iv)
plaintextCopy := make([]byte, len(plaintext))
- cfbdec.XORKeyStream(plaintextCopy, ciphertext)
+ copy(plaintextCopy, ciphertext)
+ cfbdec.XORKeyStream(plaintextCopy, plaintextCopy)
if !bytes.Equal(plaintextCopy, plaintext) {
t.Errorf("got: %x, want: %x", plaintextCopy, plaintext)
diff --git a/libgo/go/crypto/cipher/cipher.go b/libgo/go/crypto/cipher/cipher.go
index 1ffaa8c..67afdb1 100644
--- a/libgo/go/crypto/cipher/cipher.go
+++ b/libgo/go/crypto/cipher/cipher.go
@@ -46,16 +46,6 @@ type BlockMode interface {
// Utility routines
-func shift1(dst, src []byte) byte {
- var b byte
- for i := len(src) - 1; i >= 0; i-- {
- bb := src[i] >> 7
- dst[i] = src[i]<<1 | b
- b = bb
- }
- return b
-}
-
func dup(p []byte) []byte {
q := make([]byte, len(p))
copy(q, p)
diff --git a/libgo/go/crypto/cipher/ctr.go b/libgo/go/crypto/cipher/ctr.go
index d9ee9d8..70ac40f 100644
--- a/libgo/go/crypto/cipher/ctr.go
+++ b/libgo/go/crypto/cipher/ctr.go
@@ -19,37 +19,58 @@ type ctr struct {
outUsed int
}
+const streamBufferSize = 512
+
// NewCTR returns a Stream which encrypts/decrypts using the given Block in
// counter mode. The length of iv must be the same as the Block's block size.
func NewCTR(block Block, iv []byte) Stream {
if len(iv) != block.BlockSize() {
panic("cipher.NewCTR: IV length must equal block size")
}
-
+ bufSize := streamBufferSize
+ if bufSize < block.BlockSize() {
+ bufSize = block.BlockSize()
+ }
return &ctr{
b: block,
ctr: dup(iv),
- out: make([]byte, len(iv)),
- outUsed: len(iv),
+ out: make([]byte, 0, bufSize),
+ outUsed: 0,
}
}
-func (x *ctr) XORKeyStream(dst, src []byte) {
- for i := 0; i < len(src); i++ {
- if x.outUsed == len(x.ctr) {
- x.b.Encrypt(x.out, x.ctr)
- x.outUsed = 0
-
- // Increment counter
- for i := len(x.ctr) - 1; i >= 0; i-- {
- x.ctr[i]++
- if x.ctr[i] != 0 {
- break
- }
+func (x *ctr) refill() {
+ remain := len(x.out) - x.outUsed
+ if remain > x.outUsed {
+ return
+ }
+ copy(x.out, x.out[x.outUsed:])
+ x.out = x.out[:cap(x.out)]
+ bs := x.b.BlockSize()
+ for remain < len(x.out)-bs {
+ x.b.Encrypt(x.out[remain:], x.ctr)
+ remain += bs
+
+ // Increment counter
+ for i := len(x.ctr) - 1; i >= 0; i-- {
+ x.ctr[i]++
+ if x.ctr[i] != 0 {
+ break
}
}
+ }
+ x.out = x.out[:remain]
+ x.outUsed = 0
+}
- dst[i] = src[i] ^ x.out[x.outUsed]
- x.outUsed++
+func (x *ctr) XORKeyStream(dst, src []byte) {
+ for len(src) > 0 {
+ if x.outUsed >= len(x.out)-x.b.BlockSize() {
+ x.refill()
+ }
+ n := xorBytes(dst, src, x.out[x.outUsed:])
+ dst = dst[n:]
+ src = src[n:]
+ x.outUsed += n
}
}
diff --git a/libgo/go/crypto/cipher/gcm.go b/libgo/go/crypto/cipher/gcm.go
index 2bcb469..2f748f0 100644
--- a/libgo/go/crypto/cipher/gcm.go
+++ b/libgo/go/crypto/cipher/gcm.go
@@ -258,11 +258,11 @@ func (g *gcm) update(y *gcmFieldElement, data []byte) {
// gcmInc32 treats the final four bytes of counterBlock as a big-endian value
// and increments it.
func gcmInc32(counterBlock *[16]byte) {
- c := 1
for i := gcmBlockSize - 1; i >= gcmBlockSize-4; i-- {
- c += int(counterBlock[i])
- counterBlock[i] = byte(c)
- c >>= 8
+ counterBlock[i]++
+ if counterBlock[i] != 0 {
+ break
+ }
}
}
@@ -289,9 +289,7 @@ func (g *gcm) counterCrypt(out, in []byte, counter *[gcmBlockSize]byte) {
g.cipher.Encrypt(mask[:], counter[:])
gcmInc32(counter)
- for i := range mask {
- out[i] = in[i] ^ mask[i]
- }
+ xorWords(out, in, mask[:])
out = out[gcmBlockSize:]
in = in[gcmBlockSize:]
}
@@ -299,10 +297,7 @@ func (g *gcm) counterCrypt(out, in []byte, counter *[gcmBlockSize]byte) {
if len(in) > 0 {
g.cipher.Encrypt(mask[:], counter[:])
gcmInc32(counter)
-
- for i := range in {
- out[i] = in[i] ^ mask[i]
- }
+ xorBytes(out, in, mask[:])
}
}
@@ -321,9 +316,7 @@ func (g *gcm) auth(out, ciphertext, additionalData []byte, tagMask *[gcmTagSize]
putUint64(out, y.low)
putUint64(out[8:], y.high)
- for i := range tagMask {
- out[i] ^= tagMask[i]
- }
+ xorWords(out, out, tagMask[:])
}
func getUint64(data []byte) uint64 {
diff --git a/libgo/go/crypto/cipher/gcm_test.go b/libgo/go/crypto/cipher/gcm_test.go
index 02d4215..0c502ce 100644
--- a/libgo/go/crypto/cipher/gcm_test.go
+++ b/libgo/go/crypto/cipher/gcm_test.go
@@ -157,19 +157,3 @@ func TestAESGCM(t *testing.T) {
ct[0] ^= 0x80
}
}
-
-func BenchmarkAESGCM(b *testing.B) {
- buf := make([]byte, 1024)
- b.SetBytes(int64(len(buf)))
-
- var key [16]byte
- var nonce [12]byte
- aes, _ := aes.NewCipher(key[:])
- aesgcm, _ := cipher.NewGCM(aes)
- var out []byte
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- out = aesgcm.Seal(out[:0], nonce[:], buf, nonce[:])
- }
-}
diff --git a/libgo/go/crypto/cipher/ofb.go b/libgo/go/crypto/cipher/ofb.go
index 85e5f02..e86ebcb 100644
--- a/libgo/go/crypto/cipher/ofb.go
+++ b/libgo/go/crypto/cipher/ofb.go
@@ -8,6 +8,7 @@ package cipher
type ofb struct {
b Block
+ cipher []byte
out []byte
outUsed int
}
@@ -20,25 +21,46 @@ func NewOFB(b Block, iv []byte) Stream {
if len(iv) != blockSize {
return nil
}
-
+ bufSize := streamBufferSize
+ if bufSize < blockSize {
+ bufSize = blockSize
+ }
x := &ofb{
b: b,
- out: make([]byte, blockSize),
+ cipher: make([]byte, blockSize),
+ out: make([]byte, 0, bufSize),
outUsed: 0,
}
- b.Encrypt(x.out, iv)
+ copy(x.cipher, iv)
return x
}
+func (x *ofb) refill() {
+ bs := x.b.BlockSize()
+ remain := len(x.out) - x.outUsed
+ if remain > x.outUsed {
+ return
+ }
+ copy(x.out, x.out[x.outUsed:])
+ x.out = x.out[:cap(x.out)]
+ for remain < len(x.out)-bs {
+ x.b.Encrypt(x.cipher, x.cipher)
+ copy(x.out[remain:], x.cipher)
+ remain += bs
+ }
+ x.out = x.out[:remain]
+ x.outUsed = 0
+}
+
func (x *ofb) XORKeyStream(dst, src []byte) {
- for i, s := range src {
- if x.outUsed == len(x.out) {
- x.b.Encrypt(x.out, x.out)
- x.outUsed = 0
+ for len(src) > 0 {
+ if x.outUsed >= len(x.out)-x.b.BlockSize() {
+ x.refill()
}
-
- dst[i] = s ^ x.out[x.outUsed]
- x.outUsed++
+ n := xorBytes(dst, src, x.out[x.outUsed:])
+ dst = dst[n:]
+ src = src[n:]
+ x.outUsed += n
}
}
diff --git a/libgo/go/crypto/cipher/xor.go b/libgo/go/crypto/cipher/xor.go
new file mode 100644
index 0000000..f88dc89
--- /dev/null
+++ b/libgo/go/crypto/cipher/xor.go
@@ -0,0 +1,84 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cipher
+
+import (
+ "runtime"
+ "unsafe"
+)
+
+const wordSize = int(unsafe.Sizeof(uintptr(0)))
+const supportsUnaligned = runtime.GOARCH == "386" || runtime.GOARCH == "amd64"
+
+// fastXORBytes xors in bulk. It only works on architectures that
+// support unaligned read/writes.
+func fastXORBytes(dst, a, b []byte) int {
+ n := len(a)
+ if len(b) < n {
+ n = len(b)
+ }
+
+ w := n / wordSize
+ if w > 0 {
+ dw := *(*[]uintptr)(unsafe.Pointer(&dst))
+ aw := *(*[]uintptr)(unsafe.Pointer(&a))
+ bw := *(*[]uintptr)(unsafe.Pointer(&b))
+ for i := 0; i < w; i++ {
+ dw[i] = aw[i] ^ bw[i]
+ }
+ }
+
+ for i := (n - n%wordSize); i < n; i++ {
+ dst[i] = a[i] ^ b[i]
+ }
+
+ return n
+}
+
+func safeXORBytes(dst, a, b []byte) int {
+ n := len(a)
+ if len(b) < n {
+ n = len(b)
+ }
+ for i := 0; i < n; i++ {
+ dst[i] = a[i] ^ b[i]
+ }
+ return n
+}
+
+// xorBytes xors the bytes in a and b. The destination is assumed to have enough
+// space. Returns the number of bytes xor'd.
+func xorBytes(dst, a, b []byte) int {
+ if supportsUnaligned {
+ return fastXORBytes(dst, a, b)
+ } else {
+ // TODO(hanwen): if (dst, a, b) have common alignment
+ // we could still try fastXORBytes. It is not clear
+ // how often this happens, and it's only worth it if
+ // the block encryption itself is hardware
+ // accelerated.
+ return safeXORBytes(dst, a, b)
+ }
+}
+
+// fastXORWords XORs multiples of 4 or 8 bytes (depending on architecture.)
+// The arguments are assumed to be of equal length.
+func fastXORWords(dst, a, b []byte) {
+ dw := *(*[]uintptr)(unsafe.Pointer(&dst))
+ aw := *(*[]uintptr)(unsafe.Pointer(&a))
+ bw := *(*[]uintptr)(unsafe.Pointer(&b))
+ n := len(b) / wordSize
+ for i := 0; i < n; i++ {
+ dw[i] = aw[i] ^ bw[i]
+ }
+}
+
+func xorWords(dst, a, b []byte) {
+ if supportsUnaligned {
+ fastXORWords(dst, a, b)
+ } else {
+ safeXORBytes(dst, a, b)
+ }
+}
diff --git a/libgo/go/crypto/cipher/xor_test.go b/libgo/go/crypto/cipher/xor_test.go
new file mode 100644
index 0000000..cc1c9d7
--- /dev/null
+++ b/libgo/go/crypto/cipher/xor_test.go
@@ -0,0 +1,28 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cipher
+
+import (
+ "bytes"
+ "testing"
+)
+
+func TestXOR(t *testing.T) {
+ for alignP := 0; alignP < 2; alignP++ {
+ for alignQ := 0; alignQ < 2; alignQ++ {
+ for alignD := 0; alignD < 2; alignD++ {
+ p := make([]byte, 1024)[alignP:]
+ q := make([]byte, 1024)[alignQ:]
+ d1 := make([]byte, 1024+alignD)[alignD:]
+ d2 := make([]byte, 1024+alignD)[alignD:]
+ xorBytes(d1, p, q)
+ safeXORBytes(d2, p, q)
+ if bytes.Compare(d1, d2) != 0 {
+ t.Error("not equal")
+ }
+ }
+ }
+ }
+}