]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/cipher: improved cbc performance
authorLuke Curley <qpingu@gmail.com>
Fri, 17 Jan 2014 16:07:04 +0000 (11:07 -0500)
committerAdam Langley <agl@golang.org>
Fri, 17 Jan 2014 16:07:04 +0000 (11:07 -0500)
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

src/pkg/crypto/cipher/cbc.go
src/pkg/crypto/cipher/cbc_aes_test.go

index 9a2aece0e1b5f33020295ad6c23d931ddbac9844..241e122ee80079e31e36d6b545ba9b00a4893fd6 100644 (file)
@@ -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) {
index cee3a784b5074949a3916912d6a9ea4ae9ff393c..bf9e7ad7018a290cbd199cd50ebd49e12cae3068 100644 (file)
@@ -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)
                }
        }
 }