Commit 701982f1 authored by Luke Curley's avatar Luke Curley Committed by Adam Langley

crypto/cipher: improved cbc performance

decrypt: reduced the number of copy calls from 2n to 1.
encrypt: reduced the number of copy calls from n to 1.

Encryption is straight-forward: use dst instead of tmp when
xoring the block with the iv.

Decryption now loops backwards through the blocks abusing the
fact that the previous block's ciphertext (src) is the iv. This
means we don't need to copy the iv every time, in addition to
using dst instead of tmp like encryption.

R=golang-codereviews, agl, mikioh.mikioh
CC=golang-codereviews
https://golang.org/cl/50900043
parent a75875fc
......@@ -48,13 +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 {
xorBytes(x.iv, x.iv, src[:x.blockSize])
x.b.Encrypt(x.iv, x.iv)
copy(dst, x.iv)
// 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) {
......@@ -85,14 +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])
xorBytes(x.tmp, x.tmp, x.iv)
copy(x.iv, src)
copy(dst, x.tmp)
src = src[x.blockSize:]
dst = dst[x.blockSize:]
if len(src) == 0 {
return
}
// 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) {
......
......@@ -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)
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment