mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-09 12:07:47 +00:00
Add an AES-CBC mode for encrypt at rest
This commit is contained in:
parent
23cd6c52ba
commit
395399ab3d
@ -18,9 +18,13 @@ limitations under the License.
|
|||||||
package aes
|
package aes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/storage/value"
|
"k8s.io/apiserver/pkg/storage/value"
|
||||||
)
|
)
|
||||||
@ -78,3 +82,71 @@ func (t *gcm) TransformToStorage(data []byte, context value.Context) ([]byte, er
|
|||||||
cipherText := aead.Seal(result[nonceSize:nonceSize], result[:nonceSize], data, context.AuthenticatedData())
|
cipherText := aead.Seal(result[nonceSize:nonceSize], result[:nonceSize], data, context.AuthenticatedData())
|
||||||
return result[:nonceSize+len(cipherText)], nil
|
return result[:nonceSize+len(cipherText)], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cbc implements encryption at rest of the provided values given a cipher.Block algorithm.
|
||||||
|
type cbc struct {
|
||||||
|
block cipher.Block
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCBCTransformer takes the given block cipher and performs encryption and decryption on the given
|
||||||
|
// data.
|
||||||
|
func NewCBCTransformer(block cipher.Block) value.Transformer {
|
||||||
|
return &cbc{block: block}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errInvalidBlockSize = fmt.Errorf("the stored data is not a multiple of the block size")
|
||||||
|
errInvalidPKCS7Data = errors.New("invalid PKCS7 data (empty or not padded)")
|
||||||
|
errInvalidPKCS7Padding = errors.New("invalid padding on input")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *cbc) TransformFromStorage(data []byte, context value.Context) ([]byte, bool, error) {
|
||||||
|
blockSize := aes.BlockSize
|
||||||
|
if len(data) < blockSize {
|
||||||
|
return nil, false, fmt.Errorf("the stored data was shorter than the required size")
|
||||||
|
}
|
||||||
|
iv := data[:blockSize]
|
||||||
|
data = data[blockSize:]
|
||||||
|
|
||||||
|
if len(data)%blockSize != 0 {
|
||||||
|
return nil, false, errInvalidBlockSize
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]byte, len(data))
|
||||||
|
copy(result, data)
|
||||||
|
mode := cipher.NewCBCDecrypter(t.block, iv)
|
||||||
|
mode.CryptBlocks(result, result)
|
||||||
|
|
||||||
|
// remove and verify PKCS#7 padding for CBC
|
||||||
|
c := result[len(result)-1]
|
||||||
|
paddingSize := int(c)
|
||||||
|
size := len(result) - paddingSize
|
||||||
|
if paddingSize == 0 || paddingSize > len(result) {
|
||||||
|
return nil, false, errInvalidPKCS7Data
|
||||||
|
}
|
||||||
|
for i := 0; i < paddingSize; i++ {
|
||||||
|
if result[size+i] != c {
|
||||||
|
return nil, false, errInvalidPKCS7Padding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result[:size], false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *cbc) TransformToStorage(data []byte, context value.Context) ([]byte, error) {
|
||||||
|
blockSize := aes.BlockSize
|
||||||
|
paddingSize := blockSize - (len(data) % blockSize)
|
||||||
|
result := make([]byte, blockSize+len(data)+paddingSize)
|
||||||
|
iv := result[:blockSize]
|
||||||
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read sufficient random bytes")
|
||||||
|
}
|
||||||
|
copy(result[blockSize:], data)
|
||||||
|
|
||||||
|
// add PKCS#7 padding for CBC
|
||||||
|
copy(result[blockSize+len(data):], bytes.Repeat([]byte{byte(paddingSize)}, paddingSize))
|
||||||
|
|
||||||
|
mode := cipher.NewCBCEncrypter(t.block, iv)
|
||||||
|
mode.CryptBlocks(result[blockSize:], result[blockSize:])
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
@ -20,7 +20,11 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/storage/value"
|
"k8s.io/apiserver/pkg/storage/value"
|
||||||
@ -93,6 +97,58 @@ func TestGCMKeyRotation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCBCKeyRotation(t *testing.T) {
|
||||||
|
testErr := fmt.Errorf("test error")
|
||||||
|
block1, err := aes.NewCipher([]byte("abcdefghijklmnop"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
block2, err := aes.NewCipher([]byte("0123456789abcdef"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
context := value.DefaultContext([]byte("authenticated_data"))
|
||||||
|
|
||||||
|
p := value.NewPrefixTransformers(testErr,
|
||||||
|
value.PrefixTransformer{Prefix: []byte("first:"), Transformer: NewCBCTransformer(block1)},
|
||||||
|
value.PrefixTransformer{Prefix: []byte("second:"), Transformer: NewCBCTransformer(block2)},
|
||||||
|
)
|
||||||
|
out, err := p.TransformToStorage([]byte("firstvalue"), context)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(out, []byte("first:")) {
|
||||||
|
t.Fatalf("unexpected prefix: %q", out)
|
||||||
|
}
|
||||||
|
from, stale, err := p.TransformFromStorage(out, context)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if stale || !bytes.Equal([]byte("firstvalue"), from) {
|
||||||
|
t.Fatalf("unexpected data: %t %q", stale, from)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify changing the context fails storage
|
||||||
|
from, stale, err = p.TransformFromStorage(out, value.DefaultContext([]byte("incorrect_context")))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CBC mode does not support authentication: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// reverse the order, use the second key
|
||||||
|
p = value.NewPrefixTransformers(testErr,
|
||||||
|
value.PrefixTransformer{Prefix: []byte("second:"), Transformer: NewCBCTransformer(block2)},
|
||||||
|
value.PrefixTransformer{Prefix: []byte("first:"), Transformer: NewCBCTransformer(block1)},
|
||||||
|
)
|
||||||
|
from, stale, err = p.TransformFromStorage(out, context)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !stale || !bytes.Equal([]byte("firstvalue"), from) {
|
||||||
|
t.Fatalf("unexpected data: %t %q", stale, from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkGCMRead_16_1024(b *testing.B) { benchmarkGCMRead(b, 16, 1024, false) }
|
func BenchmarkGCMRead_16_1024(b *testing.B) { benchmarkGCMRead(b, 16, 1024, false) }
|
||||||
func BenchmarkGCMRead_32_1024(b *testing.B) { benchmarkGCMRead(b, 32, 1024, false) }
|
func BenchmarkGCMRead_32_1024(b *testing.B) { benchmarkGCMRead(b, 32, 1024, false) }
|
||||||
func BenchmarkGCMRead_32_16384(b *testing.B) { benchmarkGCMRead(b, 32, 16384, false) }
|
func BenchmarkGCMRead_32_16384(b *testing.B) { benchmarkGCMRead(b, 32, 16384, false) }
|
||||||
@ -170,3 +226,147 @@ func benchmarkGCMWrite(b *testing.B, keyLength int, valueLength int) {
|
|||||||
}
|
}
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkCBCRead_32_1024(b *testing.B) { benchmarkCBCRead(b, 32, 1024, false) }
|
||||||
|
func BenchmarkCBCRead_32_16384(b *testing.B) { benchmarkCBCRead(b, 32, 16384, false) }
|
||||||
|
func BenchmarkCBCRead_32_16384_Stale(b *testing.B) { benchmarkCBCRead(b, 32, 16384, true) }
|
||||||
|
|
||||||
|
func BenchmarkCBCWrite_32_1024(b *testing.B) { benchmarkCBCWrite(b, 32, 1024) }
|
||||||
|
func BenchmarkCBCWrite_32_16384(b *testing.B) { benchmarkCBCWrite(b, 32, 16384) }
|
||||||
|
|
||||||
|
func benchmarkCBCRead(b *testing.B, keyLength int, valueLength int, stale bool) {
|
||||||
|
block1, err := aes.NewCipher(bytes.Repeat([]byte("a"), keyLength))
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
block2, err := aes.NewCipher(bytes.Repeat([]byte("b"), keyLength))
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
p := value.NewPrefixTransformers(nil,
|
||||||
|
value.PrefixTransformer{Prefix: []byte("first:"), Transformer: NewCBCTransformer(block1)},
|
||||||
|
value.PrefixTransformer{Prefix: []byte("second:"), Transformer: NewCBCTransformer(block2)},
|
||||||
|
)
|
||||||
|
|
||||||
|
context := value.DefaultContext([]byte("authenticated_data"))
|
||||||
|
v := bytes.Repeat([]byte("0123456789abcdef"), valueLength/16)
|
||||||
|
|
||||||
|
out, err := p.TransformToStorage(v, context)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
// reverse the key order if stale
|
||||||
|
if stale {
|
||||||
|
p = value.NewPrefixTransformers(nil,
|
||||||
|
value.PrefixTransformer{Prefix: []byte("first:"), Transformer: NewCBCTransformer(block1)},
|
||||||
|
value.PrefixTransformer{Prefix: []byte("second:"), Transformer: NewCBCTransformer(block2)},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
from, stale, err := p.TransformFromStorage(out, context)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if stale {
|
||||||
|
b.Fatalf("unexpected data: %t %q", stale, from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.StopTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkCBCWrite(b *testing.B, keyLength int, valueLength int) {
|
||||||
|
block1, err := aes.NewCipher(bytes.Repeat([]byte("a"), keyLength))
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
block2, err := aes.NewCipher(bytes.Repeat([]byte("b"), keyLength))
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
p := value.NewPrefixTransformers(nil,
|
||||||
|
value.PrefixTransformer{Prefix: []byte("first:"), Transformer: NewCBCTransformer(block1)},
|
||||||
|
value.PrefixTransformer{Prefix: []byte("second:"), Transformer: NewCBCTransformer(block2)},
|
||||||
|
)
|
||||||
|
|
||||||
|
context := value.DefaultContext([]byte("authenticated_data"))
|
||||||
|
v := bytes.Repeat([]byte("0123456789abcdef"), valueLength/16)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := p.TransformToStorage(v, context)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.StopTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoundTrip(t *testing.T) {
|
||||||
|
lengths := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 128, 1024}
|
||||||
|
|
||||||
|
aes16block, err := aes.NewCipher([]byte(bytes.Repeat([]byte("a"), 16)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
aes24block, err := aes.NewCipher([]byte(bytes.Repeat([]byte("b"), 24)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
aes32block, err := aes.NewCipher([]byte(bytes.Repeat([]byte("c"), 32)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
context value.Context
|
||||||
|
t value.Transformer
|
||||||
|
}{
|
||||||
|
{name: "GCM 16 byte key", t: NewGCMTransformer(aes16block)},
|
||||||
|
{name: "GCM 24 byte key", t: NewGCMTransformer(aes24block)},
|
||||||
|
{name: "GCM 32 byte key", t: NewGCMTransformer(aes32block)},
|
||||||
|
{name: "CBC 32 byte key", t: NewCBCTransformer(aes32block)},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
context := tt.context
|
||||||
|
if context == nil {
|
||||||
|
context = value.DefaultContext("")
|
||||||
|
}
|
||||||
|
for _, l := range lengths {
|
||||||
|
data := make([]byte, l)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, data); err != nil {
|
||||||
|
t.Fatalf("unable to read sufficient random bytes: %v", err)
|
||||||
|
}
|
||||||
|
original := append([]byte{}, data...)
|
||||||
|
|
||||||
|
ciphertext, err := tt.t.TransformToStorage(data, context)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("TransformToStorage error = %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result, stale, err := tt.t.TransformFromStorage(ciphertext, context)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("TransformFromStorage error = %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if stale {
|
||||||
|
t.Errorf("unexpected stale output")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case l == 0:
|
||||||
|
if len(result) != 0 {
|
||||||
|
t.Errorf("Round trip failed len=%d\noriginal:\n%s\nresult:\n%s", l, hex.Dump(original), hex.Dump(result))
|
||||||
|
}
|
||||||
|
case !reflect.DeepEqual(original, result):
|
||||||
|
t.Errorf("Round trip failed len=%d\noriginal:\n%s\nresult:\n%s", l, hex.Dump(original), hex.Dump(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user