From f271b894e4ca4c7d7013080daeb0e76307c4cfaf Mon Sep 17 00:00:00 2001 From: Richard Kettlewell Date: Thu, 2 Aug 2018 14:47:13 +0100 Subject: [PATCH] Fast CBC support re #6 --- README.md | 6 ++ symmetric.go | 138 ++++++++++++++++++++++++++++++++++++++++++++++ symmetric_test.go | 63 +++++++++++++++++---- 3 files changed, 197 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 38b45b5..6d52cdb 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,12 @@ Limitations is not implemented and an error is returned if it is nonzero. The reason for this is that it is not possible for crypto11 to guarantee the constant-time behavior in the specification. See [issue #5](https://github.com/ThalesIgnite/crypto11/issues/5) for further discussion. + * Symmetric crypto support via [cipher.Block](https://golang.org/pkg/crypto/cipher/#Block) is very slow. +You can use the `BlockModeCloser` API +(over 400 times as fast on my computer) +but you must call the Close() +interface (not found in [cipher.BlockMode](https://golang.org/pkg/crypto/cipher/#BlockMode)). +See [issue #6](https://github.com/ThalesIgnite/crypto11/issues/6) for further discussion. Wishlist ======== diff --git a/symmetric.go b/symmetric.go index 4d48339..95d64a7 100644 --- a/symmetric.go +++ b/symmetric.go @@ -22,8 +22,11 @@ package crypto11 import ( + "context" + "crypto/cipher" "fmt" "github.com/miekg/pkcs11" + "github.com/youtube/vitess/go/pools" ) // SymmetricCipher represents information about a symmetric cipher. @@ -45,6 +48,9 @@ type SymmetricCipher struct { // ECB mechanism (CKM_..._ECB) ECBMech uint + + // CBC mechanism (CKM_..._CBC) + CBCMech uint } // CipherAES describes the AES cipher. Use this with the @@ -56,6 +62,7 @@ var CipherAES = SymmetricCipher{ Encrypt: true, MAC: false, ECBMech: pkcs11.CKM_AES_ECB, + CBCMech: pkcs11.CKM_AES_CBC, } // CipherDES3 describes the three-key triple-DES cipher. Use this with the @@ -67,6 +74,7 @@ var CipherDES3 = SymmetricCipher{ Encrypt: true, MAC: false, ECBMech: pkcs11.CKM_DES3_ECB, + CBCMech: pkcs11.CKM_DES3_CBC, } // Ciphers is a map of PKCS#11 key types (CKK_...) to symmetric cipher information. @@ -207,3 +215,133 @@ func (key *PKCS11SecretKey) Encrypt(dst, src []byte) { copy(dst[:key.Cipher.BlockSize], result) } } + +// Stream encryption/decryption ----------------------------------------- + +// BlockModeCloser represents a block cipher running in a block-based mode (CBC, ECB etc). +type BlockModeCloser interface { + cipher.BlockMode + + // Close() releases resources associated with the block mode. + Close() +} + +const ( + modeEncrypt = iota // blockModeCloser is in encrypt mode + modeDecrypt // blockModeCloser is in decrypt mode +) + +// NewCBCEncrypter returns a BlockModeCloser which encrypts in cipher block chaining mode, using the given key. +// The length of iv must be the same as the key's block size. +func (key *PKCS11SecretKey) NewCBCEncrypter(iv []byte) (bmc BlockModeCloser, err error) { + return key.newBlockModeCloser(key.Cipher.CBCMech, modeEncrypt, iv) +} + +// NewCBCDecrypter returns a BlockModeCloser which decrypts in cipher block chaining mode, using the given key. +// The length of iv must be the same as the key's block size and must match the iv used to encrypt the data. +func (key *PKCS11SecretKey) NewCBCDecrypter(iv []byte) (bmc BlockModeCloser, err error) { + return key.newBlockModeCloser(key.Cipher.CBCMech, modeDecrypt, iv) +} + +// blockModeCloser is a concrete implementation of BlockModeCloser supporting CBC. +type blockModeCloser struct { + // PKCS#11 session to use + session *PKCS11Session + + // Cipher block size + blockSize int + + // modeDecrypt or modeEncrypt + mode int + + // Cleanup function + cleanup func() +} + +// newBlockModeCloser creates a new blockModeCloser for the chosen mechanism and mode. +func (key *PKCS11SecretKey) newBlockModeCloser(mech uint, mode int, iv []byte) (bmc blockModeCloser, err error) { + // TODO maybe refactor with withSession() + sessionPool := pool.Get(key.Slot) + if sessionPool == nil { + err = fmt.Errorf("crypto11: no session for slot %d", key.Slot) + return + } + ctx, cancel := context.WithTimeout(context.Background(), newSessionTimeout) + defer cancel() + var session pools.Resource + if session, err = sessionPool.Get(ctx); err != nil { + return + } + bmc.session = session.(*PKCS11Session) + bmc.blockSize = key.Cipher.BlockSize + bmc.mode = mode + bmc.cleanup = func() { + sessionPool.Put(session) + } + mechDescription := []*pkcs11.Mechanism{pkcs11.NewMechanism(mech, iv)} + switch mode { + case modeDecrypt: + err = bmc.session.Ctx.DecryptInit(bmc.session.Handle, mechDescription, key.Handle) + case modeEncrypt: + err = bmc.session.Ctx.EncryptInit(bmc.session.Handle, mechDescription, key.Handle) + default: + panic("unexpected mode") + } + if err != nil { + bmc.cleanup() + return + } + return +} + +func (bmc blockModeCloser) BlockSize() int { + return bmc.blockSize +} + +func (bmc blockModeCloser) CryptBlocks(dst, src []byte) { + if len(dst) < len(src) { + panic("destination buffer too small") + } + if len(src)%bmc.blockSize != 0 { + panic("input is not a whole number of blocks") + } + var result []byte + var err error + switch bmc.mode { + case modeDecrypt: + result, err = bmc.session.Ctx.DecryptUpdate(bmc.session.Handle, src) + case modeEncrypt: + result, err = bmc.session.Ctx.EncryptUpdate(bmc.session.Handle, src) + } + if err != nil { + panic(err) + } + // PKCS#11 2.40 s5.2 says that the operation must produce as much output + // as possible, so we should never have less than we submitted for CBC. + // This could be different for other modes but we don't implement any yet. + if len(result) != len(src) { + panic("nontrivial result from *Final operation") + } + copy(dst[:len(result)], result) +} + +func (bmc blockModeCloser) Close() { + var result []byte + var err error + switch bmc.mode { + case modeDecrypt: + result, err = bmc.session.Ctx.DecryptFinal(bmc.session.Handle) + case modeEncrypt: + result, err = bmc.session.Ctx.EncryptFinal(bmc.session.Handle) + } + bmc.cleanup() + if err != nil { + panic(err) + } + // PKCS#11 2.40 s5.2 says that the operation must produce as much output + // as possible, so we should never have any left over for CBC. + // This could be different for other modes but we don't implement any yet. + if len(result) > 0 { + panic("nontrivial result from *Final operation") + } +} diff --git a/symmetric_test.go b/symmetric_test.go index 2ba0ce8..2a1a9de 100644 --- a/symmetric_test.go +++ b/symmetric_test.go @@ -25,7 +25,6 @@ import ( "bytes" "crypto" "crypto/cipher" - "crypto/rand" "github.com/miekg/pkcs11" "testing" ) @@ -41,7 +40,7 @@ func TestHardSymmetric(t *testing.T) { func testHardSymmetric(t *testing.T, keytype int, bits int) { var err error - var key *PKCS11SecretKey + var key, key2 *PKCS11SecretKey var id []byte t.Run("Generate", func(t *testing.T) { if key, err = GenerateSecretKey(bits, Ciphers[keytype]); err != nil { @@ -57,23 +56,36 @@ func testHardSymmetric(t *testing.T, keytype int, bits int) { return } }) - var key2 crypto.PrivateKey + var key2gen crypto.PrivateKey t.Run("Find", func(t *testing.T) { - if key2, err = FindKey(id, nil); err != nil { + if key2gen, err = FindKey(id, nil); err != nil { t.Errorf("crypto11.FindKey by id: %v", err) return } + key2 = key2gen.(*PKCS11SecretKey) }) - t.Run("Block", func(t *testing.T) { testSymmetricBlock(t, key, key2.(*PKCS11SecretKey)) }) + t.Run("Block", func(t *testing.T) { testSymmetricBlock(t, key, key2) }) iv := make([]byte, key.BlockSize()) - if _, err = rand.Read(iv); err != nil { - t.Errorf("rand.Read: %v", err) - return + for i := 0; i < len(iv); i++ { + iv[i] = 0xF0 } t.Run("CBC", func(t *testing.T) { - testSymmetricMode(t, cipher.NewCBCEncrypter(key2.(*PKCS11SecretKey), iv), cipher.NewCBCDecrypter(key2.(*PKCS11SecretKey), iv)) + testSymmetricMode(t, cipher.NewCBCEncrypter(key2, iv), cipher.NewCBCDecrypter(key2, iv)) + }) + t.Run("CBCClose", func(t *testing.T) { + var enc, dec BlockModeCloser + if enc, err = key2.NewCBCEncrypter(iv); err != nil { + t.Errorf("NewCBCEncrypter: %v", err) + return + } + if dec, err = key2.NewCBCDecrypter(iv); err != nil { + t.Errorf("NewCBCDecrypter: %v", err) + return + } + testSymmetricMode(t, enc, dec) + enc.Close() + dec.Close() }) - // TODO perf test native CBC vs idiomatic PKCS#11 CBC // TODO CFB // TODO OFB // TODO CTR @@ -166,3 +178,34 @@ func testSymmetricMode(t *testing.T, encrypt cipher.BlockMode, decrypt cipher.Bl return } } + +func BenchmarkCBC(b *testing.B) { + ConfigureFromFile("config") + var err error + var key *PKCS11SecretKey + if key, err = GenerateSecretKey(128, Ciphers[pkcs11.CKK_AES]); err != nil { + b.Errorf("crypto11.GenerateSecretKey: %v", err) + return + } + iv := make([]byte, 16) + plaintext := make([]byte, 65536) + ciphertext := make([]byte, 65536) + b.Run("Native", func(b *testing.B) { + for i := 0; i < b.N; i++ { + mode := cipher.NewCBCEncrypter(key, iv) + mode.CryptBlocks(ciphertext, plaintext) + } + }) + b.Run("Idiomatic", func(b *testing.B) { + for i := 0; i < b.N; i++ { + mode, err := key.NewCBCEncrypter(iv) + if err != nil { + panic(err) + } + mode.CryptBlocks(ciphertext, plaintext) + mode.Close() + } + + }) + Close() +}