mirror of
https://github.com/golang/go
synced 2024-11-23 22:30:05 -07:00
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
This commit is contained in:
parent
a75875fc08
commit
701982f173
@ -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)
|
||||
|
||||
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)
|
||||
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.CryptBlocks(data, data)
|
||||
if !bytes.Equal(test.in, data) {
|
||||
t.Errorf("%s: CBCDecrypter\nhave %x\nwant %x", test.name, data, test.in)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user