summaryrefslogtreecommitdiff
path: root/src/pkg/crypto/cipher
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/crypto/cipher')
-rw-r--r--src/pkg/crypto/cipher/benchmark_test.go139
-rw-r--r--src/pkg/crypto/cipher/cbc.go55
-rw-r--r--src/pkg/crypto/cipher/cbc_aes_test.go46
-rw-r--r--src/pkg/crypto/cipher/cfb.go60
-rw-r--r--src/pkg/crypto/cipher/cfb_test.go8
-rw-r--r--src/pkg/crypto/cipher/cipher.go10
-rw-r--r--src/pkg/crypto/cipher/ctr.go55
-rw-r--r--src/pkg/crypto/cipher/gcm.go27
-rw-r--r--src/pkg/crypto/cipher/gcm_test.go16
-rw-r--r--src/pkg/crypto/cipher/ofb.go42
-rw-r--r--src/pkg/crypto/cipher/xor.go84
-rw-r--r--src/pkg/crypto/cipher/xor_test.go28
12 files changed, 436 insertions, 134 deletions
diff --git a/src/pkg/crypto/cipher/benchmark_test.go b/src/pkg/crypto/cipher/benchmark_test.go
new file mode 100644
index 000000000..027b24851
--- /dev/null
+++ b/src/pkg/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 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/src/pkg/crypto/cipher/cbc.go b/src/pkg/crypto/cipher/cbc.go
index 4189677e3..241e122ee 100644
--- a/src/pkg/crypto/cipher/cbc.go
+++ b/src/pkg/crypto/cipher/cbc.go
@@ -48,17 +48,22 @@ func (x *cbcEncrypter) CryptBlocks(dst, src []byte) {
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
+
+ iv := x.iv
+
for len(src) > 0 {
- for i := 0; i < x.blockSize; i++ {
- x.iv[i] ^= src[i]
- }
- x.b.Encrypt(x.iv, x.iv)
- for i := 0; i < x.blockSize; i++ {
- dst[i] = x.iv[i]
- }
+ // Write the xor to dst, then encrypt in place.
+ xorBytes(dst[:x.blockSize], src[:x.blockSize], iv)
+ x.b.Encrypt(dst[:x.blockSize], dst[:x.blockSize])
+
+ // Move to the next block with this block as the next iv.
+ iv = dst[:x.blockSize]
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
+
+ // Save the iv for the next CryptBlocks call.
+ copy(x.iv, iv)
}
func (x *cbcEncrypter) SetIV(iv []byte) {
@@ -89,17 +94,35 @@ func (x *cbcDecrypter) CryptBlocks(dst, src []byte) {
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
- 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]
- }
+ if len(src) == 0 {
+ return
+ }
- src = src[x.blockSize:]
- dst = dst[x.blockSize:]
+ // For each block, we need to xor the decrypted data with the previous block's ciphertext (the iv).
+ // To avoid making a copy each time, we loop over the blocks BACKWARDS.
+ end := len(src)
+ start := end - x.blockSize
+ prev := start - x.blockSize
+
+ // Copy the last block of ciphertext in preparation as the new iv.
+ copy(x.tmp, src[start:end])
+
+ // Loop over all but the first block.
+ for start > 0 {
+ x.b.Decrypt(dst[start:end], src[start:end])
+ xorBytes(dst[start:end], dst[start:end], src[prev:start])
+
+ end = start
+ start = prev
+ prev -= x.blockSize
}
+
+ // The first block is special because it uses the saved iv.
+ x.b.Decrypt(dst[start:end], src[start:end])
+ xorBytes(dst[start:end], dst[start:end], x.iv)
+
+ // Set the new iv to the first block we copied earlier.
+ x.iv, x.tmp = x.tmp, x.iv
}
func (x *cbcDecrypter) SetIV(iv []byte) {
diff --git a/src/pkg/crypto/cipher/cbc_aes_test.go b/src/pkg/crypto/cipher/cbc_aes_test.go
index cee3a784b..bf9e7ad70 100644
--- a/src/pkg/crypto/cipher/cbc_aes_test.go
+++ b/src/pkg/crypto/cipher/cbc_aes_test.go
@@ -63,28 +63,42 @@ var cbcAESTests = []struct {
},
}
-func TestCBC_AES(t *testing.T) {
- for _, tt := range cbcAESTests {
- test := tt.name
-
- c, err := aes.NewCipher(tt.key)
+func TestCBCEncrypterAES(t *testing.T) {
+ for _, test := range cbcAESTests {
+ c, err := aes.NewCipher(test.key)
if err != nil {
- t.Errorf("%s: NewCipher(%d bytes) = %s", test, len(tt.key), err)
+ t.Errorf("%s: NewCipher(%d bytes) = %s", test.name, len(test.key), err)
continue
}
- encrypter := cipher.NewCBCEncrypter(c, tt.iv)
- d := make([]byte, len(tt.in))
- encrypter.CryptBlocks(d, tt.in)
- if !bytes.Equal(tt.out, d) {
- t.Errorf("%s: CBCEncrypter\nhave %x\nwant %x", test, d, tt.out)
+ encrypter := cipher.NewCBCEncrypter(c, test.iv)
+
+ data := make([]byte, len(test.in))
+ copy(data, test.in)
+
+ encrypter.CryptBlocks(data, data)
+ if !bytes.Equal(test.out, data) {
+ t.Errorf("%s: CBCEncrypter\nhave %x\nwant %x", test.name, data, test.out)
}
+ }
+}
+
+func TestCBCDecrypterAES(t *testing.T) {
+ for _, test := range cbcAESTests {
+ c, err := aes.NewCipher(test.key)
+ if err != nil {
+ t.Errorf("%s: NewCipher(%d bytes) = %s", test.name, len(test.key), err)
+ continue
+ }
+
+ decrypter := cipher.NewCBCDecrypter(c, test.iv)
+
+ data := make([]byte, len(test.out))
+ copy(data, test.out)
- decrypter := cipher.NewCBCDecrypter(c, tt.iv)
- p := make([]byte, len(d))
- decrypter.CryptBlocks(p, d)
- if !bytes.Equal(tt.in, p) {
- t.Errorf("%s: CBCDecrypter\nhave %x\nwant %x", test, d, tt.in)
+ decrypter.CryptBlocks(data, data)
+ if !bytes.Equal(test.in, data) {
+ t.Errorf("%s: CBCDecrypter\nhave %x\nwant %x", test.name, data, test.in)
}
}
}
diff --git a/src/pkg/crypto/cipher/cfb.go b/src/pkg/crypto/cipher/cfb.go
index 99006b546..9b4eebf5b 100644
--- a/src/pkg/crypto/cipher/cfb.go
+++ b/src/pkg/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/src/pkg/crypto/cipher/cfb_test.go b/src/pkg/crypto/cipher/cfb_test.go
index f704b337e..ec708ab2b 100644
--- a/src/pkg/crypto/cipher/cfb_test.go
+++ b/src/pkg/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/src/pkg/crypto/cipher/cipher.go b/src/pkg/crypto/cipher/cipher.go
index 1ffaa8c2c..67afdb1e0 100644
--- a/src/pkg/crypto/cipher/cipher.go
+++ b/src/pkg/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/src/pkg/crypto/cipher/ctr.go b/src/pkg/crypto/cipher/ctr.go
index d9ee9d827..70ac40f6a 100644
--- a/src/pkg/crypto/cipher/ctr.go
+++ b/src/pkg/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/src/pkg/crypto/cipher/gcm.go b/src/pkg/crypto/cipher/gcm.go
index 2bcb46985..bdafd85fc 100644
--- a/src/pkg/crypto/cipher/gcm.go
+++ b/src/pkg/crypto/cipher/gcm.go
@@ -30,9 +30,9 @@ type AEAD interface {
// Open decrypts and authenticates ciphertext, authenticates the
// additional data and, if successful, appends the resulting plaintext
- // to dst, returning the updated slice and true. On error, nil and
- // false is returned. The nonce must be NonceSize() bytes long and both
- // it and the additional data must match the value passed to Seal.
+ // to dst, returning the updated slice. The nonce must be NonceSize()
+ // bytes long and both it and the additional data must match the
+ // value passed to Seal.
//
// The ciphertext and dst may alias exactly or not at all.
Open(dst, nonce, ciphertext, data []byte) ([]byte, error)
@@ -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/src/pkg/crypto/cipher/gcm_test.go b/src/pkg/crypto/cipher/gcm_test.go
index 02d421590..0c502ce40 100644
--- a/src/pkg/crypto/cipher/gcm_test.go
+++ b/src/pkg/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/src/pkg/crypto/cipher/ofb.go b/src/pkg/crypto/cipher/ofb.go
index 85e5f02b0..e86ebcb23 100644
--- a/src/pkg/crypto/cipher/ofb.go
+++ b/src/pkg/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/src/pkg/crypto/cipher/xor.go b/src/pkg/crypto/cipher/xor.go
new file mode 100644
index 000000000..f88dc8914
--- /dev/null
+++ b/src/pkg/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/src/pkg/crypto/cipher/xor_test.go b/src/pkg/crypto/cipher/xor_test.go
new file mode 100644
index 000000000..cc1c9d72d
--- /dev/null
+++ b/src/pkg/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")
+ }
+ }
+ }
+ }
+}