From 1a92a8aeb3da1df618396f633ec66678ca1ac3a9 Mon Sep 17 00:00:00 2001 From: Saksham Sharma Date: Thu, 20 Jul 2017 10:51:20 -0700 Subject: [PATCH 1/3] Implement Envelope encryption Transformer --- .../pkg/storage/value/encrypt/envelope/BUILD | 19 +++ .../value/encrypt/envelope/envelope.go | 157 ++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/BUILD create mode 100644 staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope.go diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/BUILD b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/BUILD new file mode 100644 index 00000000000..cd50db24e6e --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/BUILD @@ -0,0 +1,19 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["envelope.go"], + tags = ["automanaged"], + deps = [ + "//vendor/github.com/hashicorp/golang-lru:go_default_library", + "//vendor/k8s.io/apiserver/pkg/storage/value:go_default_library", + "//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/aes:go_default_library", + ], +) diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope.go b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope.go new file mode 100644 index 00000000000..aa5eae120b6 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope.go @@ -0,0 +1,157 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package envelope transforms values for storage at rest using a Envelope provider +package envelope + +import ( + "crypto/aes" + "crypto/rand" + "encoding/binary" + "fmt" + + "k8s.io/apiserver/pkg/storage/value" + aestransformer "k8s.io/apiserver/pkg/storage/value/encrypt/aes" + + lru "github.com/hashicorp/golang-lru" +) + +// defaultCacheSize is the number of decrypted DEKs which would be cached by the transformer. +const defaultCacheSize = 1000 + +// Service allows encrypting and decrypting data using an external Key Management Service. +type Service interface { + // Decrypt a given data string to obtain the original byte data. + Decrypt(data string) ([]byte, error) + // Encrypt bytes to a string ciphertext. + Encrypt(data []byte) (string, error) +} + +type envelopeTransformer struct { + envelopeService Service + + // transformers is a thread-safe LRU cache which caches decrypted DEKs indexed by their encrypted form. + transformers *lru.Cache + + // cacheSize is the maximum number of DEKs that are cached. + cacheSize int +} + +// NewEnvelopeTransformer returns a transformer which implements a KEK-DEK based envelope encryption scheme. +// It uses envelopeService to encrypt and decrypt DEKs. Respective DEKs (in encrypted form) are prepended to +// the data items they encrypt. A cache (of size cacheSize) is maintained to store the most recently +// used decrypted DEKs in memory. +func NewEnvelopeTransformer(envelopeService Service, cacheSize int) (value.Transformer, error) { + if cacheSize == 0 { + cacheSize = defaultCacheSize + } + cache, err := lru.New(cacheSize) + if err != nil { + return nil, err + } + return &envelopeTransformer{ + envelopeService: envelopeService, + transformers: cache, + cacheSize: cacheSize, + }, nil +} + +// TransformFromStorage decrypts data encrypted by this transformer using envelope encryption. +func (t *envelopeTransformer) TransformFromStorage(data []byte, context value.Context) ([]byte, bool, error) { + // Read the 16 bit length-of-DEK encoded at the start of the encrypted DEK. + keyLen := int(binary.BigEndian.Uint16(data[:2])) + if keyLen+2 > len(data) { + return []byte{}, false, fmt.Errorf("invalid data encountered by genvelope transformer, length longer than available bytes: %q", data) + } + encKey := string(data[2 : keyLen+2]) + encData := data[2+keyLen:] + + var transformer value.Transformer + // Look up the decrypted DEK from cache or Envelope. + _transformer, found := t.transformers.Get(encKey) + if found { + transformer = _transformer.(value.Transformer) + } else { + key, err := t.envelopeService.Decrypt(encKey) + if err != nil { + return []byte{}, false, fmt.Errorf("error while decrypting key: %q", err) + } + transformer, err = t.addTransformer(encKey, key) + if err != nil { + return []byte{}, false, err + } + } + return transformer.TransformFromStorage(encData, context) +} + +// TransformToStorage encrypts data to be written to disk using envelope encryption. +func (t *envelopeTransformer) TransformToStorage(data []byte, context value.Context) ([]byte, error) { + newKey, err := generateKey(32) + if err != nil { + return []byte{}, err + } + + encKey, err := t.envelopeService.Encrypt(newKey) + if err != nil { + return []byte{}, err + } + + transformer, err := t.addTransformer(encKey, newKey) + if err != nil { + return []byte{}, err + } + + // Append the length of the encrypted DEK as the first 2 bytes. + encKeyLen := make([]byte, 2) + encKeyBytes := []byte(encKey) + binary.BigEndian.PutUint16(encKeyLen, uint16(len(encKeyBytes))) + + prefix := append(encKeyLen, encKeyBytes...) + + prefixedData := make([]byte, len(prefix), len(data)+len(prefix)) + copy(prefixedData, prefix) + result, err := transformer.TransformToStorage(data, context) + if err != nil { + return nil, err + } + prefixedData = append(prefixedData, result...) + return prefixedData, nil +} + +var _ value.Transformer = &envelopeTransformer{} + +// addTransformer inserts a new transformer to the Envelope cache of DEKs for future reads. +func (t *envelopeTransformer) addTransformer(encKey string, key []byte) (value.Transformer, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + transformer := aestransformer.NewCBCTransformer(block) + t.transformers.Add(encKey, transformer) + return transformer, nil +} + +// generateKey generates a random key using system randomness. +func generateKey(length int) ([]byte, error) { + key := make([]byte, length) + _, err := rand.Read(key) + if err != nil { + return []byte{}, err + } + + return key, nil +} From d23a1f135d694ef315f23299f095fd4b85421670 Mon Sep 17 00:00:00 2001 From: Saksham Sharma Date: Thu, 20 Jul 2017 19:44:11 -0700 Subject: [PATCH 2/3] Add unit tests for envelope transformer --- .../pkg/storage/value/encrypt/envelope/BUILD | 9 ++ .../value/encrypt/envelope/envelope.go | 19 +-- .../value/encrypt/envelope/envelope_test.go | 143 ++++++++++++++++++ 3 files changed, 162 insertions(+), 9 deletions(-) create mode 100644 staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope_test.go diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/BUILD b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/BUILD index cd50db24e6e..55e6204a6f1 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/BUILD @@ -5,6 +5,7 @@ licenses(["notice"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( @@ -17,3 +18,11 @@ go_library( "//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/aes:go_default_library", ], ) + +go_test( + name = "go_default_test", + srcs = ["envelope_test.go"], + library = ":go_default_library", + tags = ["automanaged"], + deps = ["//vendor/k8s.io/apiserver/pkg/storage/value:go_default_library"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope.go b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope.go index aa5eae120b6..3ae4ec2b7a0 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope.go @@ -71,10 +71,12 @@ func NewEnvelopeTransformer(envelopeService Service, cacheSize int) (value.Trans // TransformFromStorage decrypts data encrypted by this transformer using envelope encryption. func (t *envelopeTransformer) TransformFromStorage(data []byte, context value.Context) ([]byte, bool, error) { - // Read the 16 bit length-of-DEK encoded at the start of the encrypted DEK. + // Read the 16 bit length-of-DEK encoded at the start of the encrypted DEK. 16 bits can + // represent a maximum key length of 65536 bytes. We are using a 256 bit key, whose + // length cannot fit in 8 bits (1 byte). Thus, we use 16 bits (2 bytes) to store the length. keyLen := int(binary.BigEndian.Uint16(data[:2])) if keyLen+2 > len(data) { - return []byte{}, false, fmt.Errorf("invalid data encountered by genvelope transformer, length longer than available bytes: %q", data) + return nil, false, fmt.Errorf("invalid data encountered by genvelope transformer, length longer than available bytes: %q", data) } encKey := string(data[2 : keyLen+2]) encData := data[2+keyLen:] @@ -87,11 +89,11 @@ func (t *envelopeTransformer) TransformFromStorage(data []byte, context value.Co } else { key, err := t.envelopeService.Decrypt(encKey) if err != nil { - return []byte{}, false, fmt.Errorf("error while decrypting key: %q", err) + return nil, false, fmt.Errorf("error while decrypting key: %q", err) } transformer, err = t.addTransformer(encKey, key) if err != nil { - return []byte{}, false, err + return nil, false, err } } return transformer.TransformFromStorage(encData, context) @@ -101,17 +103,17 @@ func (t *envelopeTransformer) TransformFromStorage(data []byte, context value.Co func (t *envelopeTransformer) TransformToStorage(data []byte, context value.Context) ([]byte, error) { newKey, err := generateKey(32) if err != nil { - return []byte{}, err + return nil, err } encKey, err := t.envelopeService.Encrypt(newKey) if err != nil { - return []byte{}, err + return nil, err } transformer, err := t.addTransformer(encKey, newKey) if err != nil { - return []byte{}, err + return nil, err } // Append the length of the encrypted DEK as the first 2 bytes. @@ -139,7 +141,6 @@ func (t *envelopeTransformer) addTransformer(encKey string, key []byte) (value.T if err != nil { return nil, err } - transformer := aestransformer.NewCBCTransformer(block) t.transformers.Add(encKey, transformer) return transformer, nil @@ -150,7 +151,7 @@ func generateKey(length int) ([]byte, error) { key := make([]byte, length) _, err := rand.Read(key) if err != nil { - return []byte{}, err + return nil, err } return key, nil diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope_test.go b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope_test.go new file mode 100644 index 00000000000..2832dd5dc78 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope_test.go @@ -0,0 +1,143 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package envelope + +import ( + "bytes" + "encoding/base64" + "fmt" + "strconv" + "strings" + "testing" + + "k8s.io/apiserver/pkg/storage/value" +) + +const ( + testText = "abcdefghijklmnopqrstuvwxyz" + testContextText = "0123456789" + testEnvelopeCacheSize = 10 +) + +// testEnvelopeService is a mock Envelope service which can be used to simulate remote Envelope services +// for testing of Envelope based encryption providers. +type testEnvelopeService struct { + disabled bool + keyVersion string +} + +func (t *testEnvelopeService) Decrypt(data string) ([]byte, error) { + if t.disabled { + return nil, fmt.Errorf("Envelope service was disabled") + } + dataChunks := strings.SplitN(data, ":", 2) + if len(dataChunks) != 2 { + return nil, fmt.Errorf("invalid data encountered for decryption: %s. Missing key version", data) + } + return base64.StdEncoding.DecodeString(dataChunks[1]) +} + +func (t *testEnvelopeService) Encrypt(data []byte) (string, error) { + if t.disabled { + return "", fmt.Errorf("Envelope service was disabled") + } + return t.keyVersion + ":" + base64.StdEncoding.EncodeToString(data), nil +} + +func (t *testEnvelopeService) SetDisabledStatus(status bool) { + t.disabled = status +} + +func (t *testEnvelopeService) Rotate() { + i, _ := strconv.Atoi(t.keyVersion) + t.keyVersion = strconv.FormatInt(int64(i+1), 10) +} + +func newTestEnvelopeService() *testEnvelopeService { + return &testEnvelopeService{ + keyVersion: "1", + } +} + +// Throw error if Envelope transformer tries to contact Envelope without hitting cache. +func TestEnvelopeCaching(t *testing.T) { + envelopeService := newTestEnvelopeService() + envelopeTransformer, err := NewEnvelopeTransformer(envelopeService, testEnvelopeCacheSize) + if err != nil { + t.Fatalf("failed to initialize envelope transformer: %v", err) + } + context := value.DefaultContext([]byte(testContextText)) + originalText := []byte(testText) + + transformedData, err := envelopeTransformer.TransformToStorage(originalText, context) + if err != nil { + t.Fatalf("envelopeTransformer: error while transforming data to storage: %s", err) + } + untransformedData, _, err := envelopeTransformer.TransformFromStorage(transformedData, context) + if err != nil { + t.Fatalf("could not decrypt Envelope transformer's encrypted data even once: %v", err) + } + if bytes.Compare(untransformedData, originalText) != 0 { + t.Fatalf("envelopeTransformer transformed data incorrectly. Expected: %v, got %v", originalText, untransformedData) + } + + envelopeService.SetDisabledStatus(true) + // Subsequent read for the same data should work fine due to caching. + untransformedData, _, err = envelopeTransformer.TransformFromStorage(transformedData, context) + if err != nil { + t.Fatalf("could not decrypt Envelope transformer's encrypted data using just cache: %v", err) + } + if bytes.Compare(untransformedData, originalText) != 0 { + t.Fatalf("envelopeTransformer transformed data incorrectly using cache. Expected: %v, got %v", originalText, untransformedData) + } +} + +// Makes Envelope transformer hit cache limit, throws error if it misbehaves. +func TestEnvelopeCacheLimit(t *testing.T) { + envelopeTransformer, err := NewEnvelopeTransformer(newTestEnvelopeService(), testEnvelopeCacheSize) + if err != nil { + t.Fatalf("failed to initialize envelope transformer: %v", err) + } + context := value.DefaultContext([]byte(testContextText)) + + transformedOutputs := map[int][]byte{} + + // Overwrite lots of entries in the map + for i := 0; i < 2*testEnvelopeCacheSize; i++ { + numberText := []byte(strconv.Itoa(i)) + + res, err := envelopeTransformer.TransformToStorage(numberText, context) + transformedOutputs[i] = res + if err != nil { + t.Fatalf("envelopeTransformer: error while transforming data (%v) to storage: %s", numberText, err) + } + } + + // Try reading all the data now, ensuring cache misses don't cause a concern. + for i := 0; i < 2*testEnvelopeCacheSize; i++ { + numberText := []byte(strconv.Itoa(i)) + + output, _, err := envelopeTransformer.TransformFromStorage(transformedOutputs[i], context) + if err != nil { + t.Fatalf("envelopeTransformer: error while transforming data (%v) from storage: %s", transformedOutputs[i], err) + } + + if bytes.Compare(numberText, output) != 0 { + t.Fatalf("envelopeTransformer transformed data incorrectly using cache. Expected: %v, got %v", numberText, output) + } + } +} From 449e811ebe3135a35d04afc84c2e9c9481d637fe Mon Sep 17 00:00:00 2001 From: Saksham Sharma Date: Sat, 22 Jul 2017 11:45:36 -0700 Subject: [PATCH 3/3] Add benchmarks for envelope transformer --- .../pkg/storage/value/encrypt/envelope/BUILD | 6 +- .../value/encrypt/envelope/envelope.go | 16 +++-- .../value/encrypt/envelope/envelope_test.go | 64 ++++++++++++++++++- 3 files changed, 76 insertions(+), 10 deletions(-) diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/BUILD b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/BUILD index 55e6204a6f1..1cc1da54175 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/BUILD @@ -15,7 +15,6 @@ go_library( deps = [ "//vendor/github.com/hashicorp/golang-lru:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/value:go_default_library", - "//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/aes:go_default_library", ], ) @@ -24,5 +23,8 @@ go_test( srcs = ["envelope_test.go"], library = ":go_default_library", tags = ["automanaged"], - deps = ["//vendor/k8s.io/apiserver/pkg/storage/value:go_default_library"], + deps = [ + "//vendor/k8s.io/apiserver/pkg/storage/value:go_default_library", + "//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/aes:go_default_library", + ], ) diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope.go b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope.go index 3ae4ec2b7a0..acb26e6f5ed 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope.go @@ -19,12 +19,12 @@ package envelope import ( "crypto/aes" + "crypto/cipher" "crypto/rand" "encoding/binary" "fmt" "k8s.io/apiserver/pkg/storage/value" - aestransformer "k8s.io/apiserver/pkg/storage/value/encrypt/aes" lru "github.com/hashicorp/golang-lru" ) @@ -48,13 +48,16 @@ type envelopeTransformer struct { // cacheSize is the maximum number of DEKs that are cached. cacheSize int + + // baseTransformerFunc creates a new transformer for encrypting the data with the DEK. + baseTransformerFunc func(cipher.Block) value.Transformer } // NewEnvelopeTransformer returns a transformer which implements a KEK-DEK based envelope encryption scheme. // It uses envelopeService to encrypt and decrypt DEKs. Respective DEKs (in encrypted form) are prepended to // the data items they encrypt. A cache (of size cacheSize) is maintained to store the most recently // used decrypted DEKs in memory. -func NewEnvelopeTransformer(envelopeService Service, cacheSize int) (value.Transformer, error) { +func NewEnvelopeTransformer(envelopeService Service, cacheSize int, baseTransformerFunc func(cipher.Block) value.Transformer) (value.Transformer, error) { if cacheSize == 0 { cacheSize = defaultCacheSize } @@ -63,9 +66,10 @@ func NewEnvelopeTransformer(envelopeService Service, cacheSize int) (value.Trans return nil, err } return &envelopeTransformer{ - envelopeService: envelopeService, - transformers: cache, - cacheSize: cacheSize, + envelopeService: envelopeService, + transformers: cache, + cacheSize: cacheSize, + baseTransformerFunc: baseTransformerFunc, }, nil } @@ -141,7 +145,7 @@ func (t *envelopeTransformer) addTransformer(encKey string, key []byte) (value.T if err != nil { return nil, err } - transformer := aestransformer.NewCBCTransformer(block) + transformer := t.baseTransformerFunc(block) t.transformers.Add(encKey, transformer) return transformer, nil } diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope_test.go b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope_test.go index 2832dd5dc78..0ba68afc63d 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope_test.go @@ -18,6 +18,7 @@ package envelope import ( "bytes" + "crypto/aes" "encoding/base64" "fmt" "strconv" @@ -25,6 +26,7 @@ import ( "testing" "k8s.io/apiserver/pkg/storage/value" + aestransformer "k8s.io/apiserver/pkg/storage/value/encrypt/aes" ) const ( @@ -76,7 +78,7 @@ func newTestEnvelopeService() *testEnvelopeService { // Throw error if Envelope transformer tries to contact Envelope without hitting cache. func TestEnvelopeCaching(t *testing.T) { envelopeService := newTestEnvelopeService() - envelopeTransformer, err := NewEnvelopeTransformer(envelopeService, testEnvelopeCacheSize) + envelopeTransformer, err := NewEnvelopeTransformer(envelopeService, testEnvelopeCacheSize, aestransformer.NewCBCTransformer) if err != nil { t.Fatalf("failed to initialize envelope transformer: %v", err) } @@ -108,7 +110,7 @@ func TestEnvelopeCaching(t *testing.T) { // Makes Envelope transformer hit cache limit, throws error if it misbehaves. func TestEnvelopeCacheLimit(t *testing.T) { - envelopeTransformer, err := NewEnvelopeTransformer(newTestEnvelopeService(), testEnvelopeCacheSize) + envelopeTransformer, err := NewEnvelopeTransformer(newTestEnvelopeService(), testEnvelopeCacheSize, aestransformer.NewCBCTransformer) if err != nil { t.Fatalf("failed to initialize envelope transformer: %v", err) } @@ -141,3 +143,61 @@ func TestEnvelopeCacheLimit(t *testing.T) { } } } + +func BenchmarkEnvelopeCBCRead(b *testing.B) { + envelopeTransformer, err := NewEnvelopeTransformer(newTestEnvelopeService(), testEnvelopeCacheSize, aestransformer.NewCBCTransformer) + if err != nil { + b.Fatalf("failed to initialize envelope transformer: %v", err) + } + benchmarkRead(b, envelopeTransformer, 1024) +} + +func BenchmarkAESCBCRead(b *testing.B) { + block, err := aes.NewCipher(bytes.Repeat([]byte("a"), 32)) + if err != nil { + b.Fatal(err) + } + + aesCBCTransformer := aestransformer.NewCBCTransformer(block) + benchmarkRead(b, aesCBCTransformer, 1024) +} + +func BenchmarkEnvelopeGCMRead(b *testing.B) { + envelopeTransformer, err := NewEnvelopeTransformer(newTestEnvelopeService(), testEnvelopeCacheSize, aestransformer.NewGCMTransformer) + if err != nil { + b.Fatalf("failed to initialize envelope transformer: %v", err) + } + benchmarkRead(b, envelopeTransformer, 1024) +} + +func BenchmarkAESGCMRead(b *testing.B) { + block, err := aes.NewCipher(bytes.Repeat([]byte("a"), 32)) + if err != nil { + b.Fatal(err) + } + + aesGCMTransformer := aestransformer.NewGCMTransformer(block) + benchmarkRead(b, aesGCMTransformer, 1024) +} + +func benchmarkRead(b *testing.B, transformer value.Transformer, valueLength int) { + context := value.DefaultContext([]byte(testContextText)) + v := bytes.Repeat([]byte("0123456789abcdef"), valueLength/16) + + out, err := transformer.TransformToStorage(v, context) + if err != nil { + b.Fatal(err) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + from, stale, err := transformer.TransformFromStorage(out, context) + if err != nil { + b.Fatal(err) + } + if stale { + b.Fatalf("unexpected data: %t %q", stale, from) + } + } + b.StopTimer() +}