Skip to content

Commit

Permalink
Fast CBC support
Browse files Browse the repository at this point in the history
re #6
  • Loading branch information
Richard Kettlewell authored and Richard Kettlewell committed Oct 2, 2018
1 parent 29d85db commit f271b89
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 10 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
========
Expand Down
138 changes: 138 additions & 0 deletions symmetric.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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")
}
}
63 changes: 53 additions & 10 deletions symmetric_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
"bytes"
"crypto"
"crypto/cipher"
"crypto/rand"
"github.com/miekg/pkcs11"
"testing"
)
Expand All @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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()
}

0 comments on commit f271b89

Please sign in to comment.