From 7522fa336b7b040c5ff97edaa280049b7891a460 Mon Sep 17 00:00:00 2001 From: Lewis Chan Date: Wed, 22 Apr 2026 13:59:10 +0800 Subject: [PATCH 01/12] feat(aes): add CheckValue and VerifyCheckValue methods Compute AES Key Check Value by encrypting a 16-byte zero block using ECB internally and returning the first 3 bytes as hex. Co-Authored-By: Claude Opus 4.7 --- aes/aes_cipher.go | 20 ++++++++++++++++++++ aes/aes_cipher_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/aes/aes_cipher.go b/aes/aes_cipher.go index c699e01..2d56d92 100644 --- a/aes/aes_cipher.go +++ b/aes/aes_cipher.go @@ -16,11 +16,17 @@ package aes import ( "crypto/aes" "crypto/cipher" + "encoding/hex" "errors" + "strings" "github.com/hashicorp/go-uuid" ) +const checkValueBytes = 3 + +var keyCheckValuePlainText16Bytes = make([]byte, 16) + // Cipher is wrapper of the AES GCM cipher and stores the raw key bytes type Cipher struct { gcm cipher.AEAD @@ -73,3 +79,17 @@ func (cipher *Cipher) Decrypt(cipherBytes []byte, nonce []byte) ([]byte, error) return cipher.gcm.Open(nil, nonce, cipherBytes, nil) } + +func (c *Cipher) CheckValue() string { + block, err := aes.NewCipher(c.KeyBytes) + if err != nil { + return "" + } + cipherBytes := make([]byte, len(keyCheckValuePlainText16Bytes)) + block.Encrypt(cipherBytes, keyCheckValuePlainText16Bytes) + return hex.EncodeToString(cipherBytes[:checkValueBytes]) +} + +func (c *Cipher) VerifyCheckValue(checkValue string) bool { + return strings.EqualFold(c.CheckValue(), checkValue) +} diff --git a/aes/aes_cipher_test.go b/aes/aes_cipher_test.go index ee5b956..e707bc6 100644 --- a/aes/aes_cipher_test.go +++ b/aes/aes_cipher_test.go @@ -57,6 +57,34 @@ func TestAESCipher_EncryptAndDecryptNotPrefixNonce(t *testing.T) { } } +func TestAESCheckValue(t *testing.T) { + keyBytes, _ := hex.DecodeString("2b7e151628aed2a6abf7158809cf4f3c") + cipher, _ := New(keyBytes) + + checkValue := cipher.CheckValue() + if checkValue != "7df76b" { + t.Errorf("Expected check value 7df76b but got %s", checkValue) + } +} + +func TestAESVerifyCheckValue(t *testing.T) { + keyBytes, _ := hex.DecodeString("2b7e151628aed2a6abf7158809cf4f3c") + cipher, _ := New(keyBytes) + + if !cipher.VerifyCheckValue("7df76b") { + t.Error("Expected check value to verify successfully") + } + if !cipher.VerifyCheckValue("7DF76B") { + t.Error("Expected check value verification to be case insensitive") + } + if cipher.VerifyCheckValue("abcdef") { + t.Error("Expected wrong check value to fail verification") + } + if cipher.VerifyCheckValue("") { + t.Error("Expected empty check value to fail verification") + } +} + func TestAESCipher_EncryptAndDecryptPrefixNonce(t *testing.T) { keyBytes, _ := uuid.GenerateRandomBytes(32) cipher, _ := New(keyBytes) From efd52298c8670671093f7481d858213e6dbdc64b Mon Sep 17 00:00:00 2001 From: Lewis Chan Date: Wed, 22 Apr 2026 14:04:22 +0800 Subject: [PATCH 02/12] feat(aes): add factory functions for key creation Add CreateFromKeyBytes and CreateFromKeyString factory functions, matching the DES package pattern. Existing New() now delegates to CreateFromKeyBytes. Co-Authored-By: Claude Opus 4.7 --- aes/aes_cipher.go | 18 +-------- aes/aes_factory.go | 45 +++++++++++++++++++++++ aes/aes_factory_test.go | 81 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 17 deletions(-) create mode 100644 aes/aes_factory.go create mode 100644 aes/aes_factory_test.go diff --git a/aes/aes_cipher.go b/aes/aes_cipher.go index 2d56d92..07a4a91 100644 --- a/aes/aes_cipher.go +++ b/aes/aes_cipher.go @@ -33,24 +33,8 @@ type Cipher struct { KeyBytes []byte } -// New constructs a new AES GCM cipher using the raw key bytes provided, the raw bytes must be -// either 16, 24, or 32 bytes func New(keyBytes []byte) (Cipher, error) { - var err error - - // Setup the cipher - aesCipher, err := aes.NewCipher(keyBytes) - if err != nil { - return Cipher{}, err - } - - // Setup the GCM - gcmCipher, err := cipher.NewGCM(aesCipher) - if err != nil { - return Cipher{}, err - } - - return Cipher{gcmCipher, keyBytes}, nil + return CreateFromKeyBytes(keyBytes) } // Encrypt takes plain bytes and output cipher bytes, the nonce will be prefixed to diff --git a/aes/aes_factory.go b/aes/aes_factory.go new file mode 100644 index 0000000..796f236 --- /dev/null +++ b/aes/aes_factory.go @@ -0,0 +1,45 @@ +/* + 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 aes + +import ( + "crypto/aes" + "crypto/cipher" + "encoding/hex" + "errors" +) + +func CreateFromKeyBytes(keyBytes []byte) (Cipher, error) { + if len(keyBytes) != 16 && len(keyBytes) != 24 && len(keyBytes) != 32 { + return Cipher{}, errors.New("AES key must be 16, 24, or 32 bytes") + } + + aesCipher, err := aes.NewCipher(keyBytes) + if err != nil { + return Cipher{}, errors.New("invalid AES keyBlock") + } + + gcmCipher, err := cipher.NewGCM(aesCipher) + if err != nil { + return Cipher{}, errors.New("invalid AES GCM cipher") + } + + return Cipher{gcmCipher, keyBytes}, nil +} + +func CreateFromKeyString(key string) (Cipher, error) { + keyBytes, err := hex.DecodeString(key) + if err != nil { + return Cipher{}, errors.New("AES key is not in correct hex format") + } + return CreateFromKeyBytes(keyBytes) +} diff --git a/aes/aes_factory_test.go b/aes/aes_factory_test.go new file mode 100644 index 0000000..591d325 --- /dev/null +++ b/aes/aes_factory_test.go @@ -0,0 +1,81 @@ +/* + 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 aes + +import ( + "testing" +) + +func TestInvalidAESKeyStrings(t *testing.T) { + invalidKeys := []string{ + "", + "sme", + "844e5fb5-96d1-4b19-9ce0-b90f252ea370", + " naksn", + "2b7e1516", + "2b7e151628aed2a6abf71588", + } + + for _, key := range invalidKeys { + if _, err := CreateFromKeyString(key); err == nil { + t.Errorf("Expecting AES key %s to be invalid", key) + } + } +} + +func TestValidAES128KeyStrings(t *testing.T) { + validKeys := []string{ + "2b7e151628aed2a6abf7158809cf4f3c", + "000102030405060708090a0b0c0d0e0f", + } + + for _, key := range validKeys { + if _, err := CreateFromKeyString(key); err != nil { + t.Errorf("Expecting AES-128 key %s to be valid but got %v", key, err) + } + } +} + +func TestValidAES192KeyStrings(t *testing.T) { + validKeys := []string{ + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + } + + for _, key := range validKeys { + if _, err := CreateFromKeyString(key); err != nil { + t.Errorf("Expecting AES-192 key %s to be valid but got %v", key, err) + } + } +} + +func TestValidAES256KeyStrings(t *testing.T) { + validKeys := []string{ + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + } + + for _, key := range validKeys { + if _, err := CreateFromKeyString(key); err != nil { + t.Errorf("Expecting AES-256 key %s to be valid but got %v", key, err) + } + } +} + +func TestInvalidAESKeyBytes(t *testing.T) { + invalidLengths := []int{0, 7, 15, 17, 23, 25, 31, 33} + + for _, length := range invalidLengths { + keyBytes := make([]byte, length) + if _, err := CreateFromKeyBytes(keyBytes); err == nil { + t.Errorf("Expecting AES key of length %d bytes to be invalid", length) + } + } +} From dfa644c0ebfbbec655db2d073c72c7824e5c07ba Mon Sep 17 00:00:00 2001 From: Lewis Chan Date: Wed, 22 Apr 2026 14:08:52 +0800 Subject: [PATCH 03/12] feat(kek): add AES key type support for KEK bundles Add KeyType field, NewWithKeyType constructor, and MergeKey method that supports both 3DES and AES key types. Existing New() and Merge() remain backward compatible for 3DES bundles. Includes KeyCipher interface and comprehensive AES bundle tests. Co-Authored-By: Claude Opus 4.7 --- kek/cipher.go | 17 ++++++ kek/kek_bundle.go | 101 +++++++++++++++++++++++++++++++---- kek/kek_bundle_test.go | 116 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 225 insertions(+), 9 deletions(-) create mode 100644 kek/cipher.go diff --git a/kek/cipher.go b/kek/cipher.go new file mode 100644 index 0000000..593bed5 --- /dev/null +++ b/kek/cipher.go @@ -0,0 +1,17 @@ +/* + 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 kek + +type KeyCipher interface { + VerifyCheckValue(checkValue string) bool + CheckValue() string +} diff --git a/kek/kek_bundle.go b/kek/kek_bundle.go index 5c359d4..c9418ef 100644 --- a/kek/kek_bundle.go +++ b/kek/kek_bundle.go @@ -16,9 +16,17 @@ import ( "errors" "github.com/hashicorp/vault/sdk/helper/xor" + "github.com/transferwise/crypto/aes" "github.com/transferwise/crypto/des" ) +type KeyType string + +const ( + KeyType3DES KeyType = "3DES" + KeyTypeAES KeyType = "AES" +) + // Bundle is the in memory data structure to help construct a KEK from a list of components type Bundle struct { // name of the key @@ -29,16 +37,23 @@ type Bundle struct { Size int // result key check value CheckValue string + // key type (3DES or AES) + KeyType KeyType // imported components index value map Components map[int][]byte } func New(name string, index int, size int, checkValue string) *Bundle { + return NewWithKeyType(name, index, size, checkValue, KeyType3DES) +} + +func NewWithKeyType(name string, index int, size int, checkValue string, keyType KeyType) *Bundle { return &Bundle{ Name: name, Index: index, Size: size, CheckValue: checkValue, + KeyType: keyType, Components: make(map[int][]byte), } } @@ -50,21 +65,35 @@ func (b *Bundle) IsComplete() bool { // AddComponent add a new component to the Bundle func (b *Bundle) AddComponent(componentIndex int, componentValue string, componentCheckValue string) error { - cipher, err := des.CreateFromTripleDESKeyString(componentValue) - if err != nil { - return errors.New("invalid component") + switch b.KeyType { + case KeyTypeAES: + cipher, err := aes.CreateFromKeyString(componentValue) + if err != nil { + return errors.New("invalid component") + } + if !cipher.VerifyCheckValue(componentCheckValue) { + return errors.New("component check value does not tally") + } + b.Components[componentIndex] = cipher.KeyBytes + default: + cipher, err := des.CreateFromTripleDESKeyString(componentValue) + if err != nil { + return errors.New("invalid component") + } + if !cipher.VerifyCheckValue(componentCheckValue) { + return errors.New("component check value does not tally") + } + b.Components[componentIndex] = cipher.KeyBytes } - if !cipher.VerifyCheckValue(componentCheckValue) { - return errors.New("component check value does not tally") - } - - // Override the previous value if the same component is imported again - b.Components[componentIndex] = cipher.KeyBytes return nil } // Merge tries to build the result 3DES key from all the imported components func (b *Bundle) Merge() (des.Cipher, error) { + if b.KeyType == KeyTypeAES { + return des.Cipher{}, errors.New("Merge() does not support AES bundles, use MergeKey() instead") + } + kekBytes := make([]byte, 24) for _, component := range b.Components { kekBytes, _ = xor.XORBytes(kekBytes, component) @@ -80,3 +109,57 @@ func (b *Bundle) Merge() (des.Cipher, error) { return kekCipher, nil } + +// MergeKey tries to build the result key from all the imported components. +// It supports both 3DES and AES key types. +func (b *Bundle) MergeKey() (KeyCipher, error) { + switch b.KeyType { + case KeyTypeAES: + return b.mergeAES() + default: + return b.merge3DES() + } +} + +func (b *Bundle) merge3DES() (KeyCipher, error) { + kekBytes := make([]byte, 24) + for _, component := range b.Components { + kekBytes, _ = xor.XORBytes(kekBytes, component) + } + + kekCipher, err := des.CreateFromTripleDESKeyBytes(kekBytes) + if err != nil { + return nil, err + } + if !kekCipher.VerifyCheckValue(b.CheckValue) { + return nil, errors.New("derived key check value does not tally") + } + + return &kekCipher, nil +} + +func (b *Bundle) mergeAES() (KeyCipher, error) { + var keyLen int + for _, component := range b.Components { + keyLen = len(component) + break + } + if keyLen == 0 { + return nil, errors.New("no components to merge") + } + + kekBytes := make([]byte, keyLen) + for _, component := range b.Components { + kekBytes, _ = xor.XORBytes(kekBytes, component) + } + + kekCipher, err := aes.CreateFromKeyBytes(kekBytes) + if err != nil { + return nil, err + } + if !kekCipher.VerifyCheckValue(b.CheckValue) { + return nil, errors.New("derived key check value does not tally") + } + + return &kekCipher, nil +} diff --git a/kek/kek_bundle_test.go b/kek/kek_bundle_test.go index 91f718c..37bdd9a 100644 --- a/kek/kek_bundle_test.go +++ b/kek/kek_bundle_test.go @@ -15,6 +15,9 @@ import ( "encoding/hex" "strings" "testing" + + "github.com/transferwise/crypto/aes" + "github.com/transferwise/crypto/des" ) func TestAddComponentInvalidValue(t *testing.T) { @@ -117,3 +120,116 @@ func TestMergeResultKeySuccess(t *testing.T) { t.Fatalf("Expected %s but got back %s", expectedKey, hex.EncodeToString(resultKey.KeyBytes)) } } + +// AES KEK tests + +func TestAESAddComponentInvalidValue(t *testing.T) { + kek := NewWithKeyType("aes-kek", 1, 3, "B1DD24", KeyTypeAES) + + err := kek.AddComponent(1, "invalid", "7DF76B") + if err == nil { + t.Fatal("should have failed if the component value is invalid") + } +} + +func TestAESAddComponentCheckValueNotTally(t *testing.T) { + kek := NewWithKeyType("aes-kek", 1, 3, "B1DD24", KeyTypeAES) + + err := kek.AddComponent(1, "2b7e151628aed2a6abf7158809cf4f3c", "AAAAAA") + if err == nil { + t.Fatal("should have failed if the component check value does not tally") + } +} + +func TestAESAddComponentSuccess(t *testing.T) { + kek := NewWithKeyType("aes-kek", 1, 3, "B1DD24", KeyTypeAES) + + err := kek.AddComponent(1, "2b7e151628aed2a6abf7158809cf4f3c", "7DF76B") + if err != nil { + t.Fatalf("adding component failed with %v", err) + } +} + +func TestAESMergeKeySuccess(t *testing.T) { + kek := NewWithKeyType("aes-kek", 1, 3, "B1DD24", KeyTypeAES) + + err := kek.AddComponent(1, "2b7e151628aed2a6abf7158809cf4f3c", "7DF76B") + if err != nil { + t.Fatalf("adding component 1 failed with %v", err) + } + + err = kek.AddComponent(2, "8e73b0f7da0e6452c810f32b809079e5", "56F9E5") + if err != nil { + t.Fatalf("adding component 2 failed with %v", err) + } + + err = kek.AddComponent(3, "603deb1015ca71be2b73aef0857d77d9", "D0140D") + if err != nil { + t.Fatalf("adding component 3 failed with %v", err) + } + + resultKey, err := kek.MergeKey() + if err != nil { + t.Fatalf("merge result key failed with %v", err) + } + if resultKey.CheckValue() != "b1dd24" { + t.Fatalf("Expected check value b1dd24 but got %s", resultKey.CheckValue()) + } +} + +func TestAESMergeKeyCheckValueNotTally(t *testing.T) { + kek := NewWithKeyType("aes-kek", 1, 3, "FFFFFF", KeyTypeAES) + + err := kek.AddComponent(1, "2b7e151628aed2a6abf7158809cf4f3c", "7DF76B") + if err != nil { + t.Fatalf("adding component 1 failed with %v", err) + } + + err = kek.AddComponent(2, "8e73b0f7da0e6452c810f32b809079e5", "56F9E5") + if err != nil { + t.Fatalf("adding component 2 failed with %v", err) + } + + err = kek.AddComponent(3, "603deb1015ca71be2b73aef0857d77d9", "D0140D") + if err != nil { + t.Fatalf("adding component 3 failed with %v", err) + } + + _, err = kek.MergeKey() + if err == nil { + t.Fatal("should have failed if the result key check value does not tally") + } +} + +func TestMergeOnAESBundleReturnsError(t *testing.T) { + kek := NewWithKeyType("aes-kek", 1, 3, "B1DD24", KeyTypeAES) + + kek.AddComponent(1, "2b7e151628aed2a6abf7158809cf4f3c", "7DF76B") + kek.AddComponent(2, "8e73b0f7da0e6452c810f32b809079e5", "56F9E5") + kek.AddComponent(3, "603deb1015ca71be2b73aef0857d77d9", "D0140D") + + _, err := kek.Merge() + if err == nil { + t.Fatal("Merge() should return error for AES bundles") + } +} + +func TestMergeKeyWith3DES(t *testing.T) { + kek := New("visa", 1, 3, "2D617C") + + kek.AddComponent(1, "E38FD6D9EF85A892F2FBFDD083A407AE", "DD1375") + kek.AddComponent(2, "D0085DBFFB3723B926CB7980B9EA6268", "DACAF5") + kek.AddComponent(3, "20295EBC0B80BF5EF7F78C9125686D3B", "DE5AA9") + + resultKey, err := kek.MergeKey() + if err != nil { + t.Fatalf("MergeKey failed with %v", err) + } + if resultKey.CheckValue() != "2d617c" { + t.Fatalf("Expected check value 2d617c but got %s", resultKey.CheckValue()) + } +} + +// Compile-time interface compliance checks +var _ KeyCipher = (*des.Cipher)(nil) +var _ KeyCipher = (*aes.Cipher)(nil) From f44b8876215e0bf162a49f6add3a204a945a0a12 Mon Sep 17 00:00:00 2001 From: Lewis Chan Date: Wed, 22 Apr 2026 14:15:24 +0800 Subject: [PATCH 04/12] refactor: rename KeyType 3DES to TripleDES --- kek/kek_bundle.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kek/kek_bundle.go b/kek/kek_bundle.go index c9418ef..ef7cca0 100644 --- a/kek/kek_bundle.go +++ b/kek/kek_bundle.go @@ -23,7 +23,7 @@ import ( type KeyType string const ( - KeyType3DES KeyType = "3DES" + KeyTypeTripleDES KeyType = "TripleDES" KeyTypeAES KeyType = "AES" ) @@ -44,7 +44,7 @@ type Bundle struct { } func New(name string, index int, size int, checkValue string) *Bundle { - return NewWithKeyType(name, index, size, checkValue, KeyType3DES) + return NewWithKeyType(name, index, size, checkValue, KeyTypeTripleDES) } func NewWithKeyType(name string, index int, size int, checkValue string, keyType KeyType) *Bundle { From 9e1f997f52e6bdb2dcf9be14f292af90c5defd9c Mon Sep 17 00:00:00 2001 From: Lewis Chan Date: Wed, 22 Apr 2026 14:21:59 +0800 Subject: [PATCH 05/12] docs: update comments to highlight deprecated methods --- aes/aes_cipher.go | 2 ++ kek/kek_bundle.go | 16 +++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/aes/aes_cipher.go b/aes/aes_cipher.go index 07a4a91..bd99a4f 100644 --- a/aes/aes_cipher.go +++ b/aes/aes_cipher.go @@ -33,6 +33,8 @@ type Cipher struct { KeyBytes []byte } +// New creates a new AES cipher from the given key bytes. +// Deprecated: Use CreateFromKeyBytes instead. func New(keyBytes []byte) (Cipher, error) { return CreateFromKeyBytes(keyBytes) } diff --git a/kek/kek_bundle.go b/kek/kek_bundle.go index ef7cca0..22adec6 100644 --- a/kek/kek_bundle.go +++ b/kek/kek_bundle.go @@ -9,7 +9,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// package kek helps construct an 3DES key encryption key from a list of components +// package kek helps construct a key encryption key from a list of components package kek import ( @@ -24,7 +24,7 @@ type KeyType string const ( KeyTypeTripleDES KeyType = "TripleDES" - KeyTypeAES KeyType = "AES" + KeyTypeAES KeyType = "AES" ) // Bundle is the in memory data structure to help construct a KEK from a list of components @@ -37,7 +37,7 @@ type Bundle struct { Size int // result key check value CheckValue string - // key type (3DES or AES) + // key type (TripleDES or AES) KeyType KeyType // imported components index value map Components map[int][]byte @@ -88,7 +88,9 @@ func (b *Bundle) AddComponent(componentIndex int, componentValue string, compone return nil } -// Merge tries to build the result 3DES key from all the imported components +// Merge tries to build the result TripleDES key from all the imported components +// +// Deprecated: Use MergeKey instead as it supports both TripleDES and AES keys. func (b *Bundle) Merge() (des.Cipher, error) { if b.KeyType == KeyTypeAES { return des.Cipher{}, errors.New("Merge() does not support AES bundles, use MergeKey() instead") @@ -111,17 +113,17 @@ func (b *Bundle) Merge() (des.Cipher, error) { } // MergeKey tries to build the result key from all the imported components. -// It supports both 3DES and AES key types. +// It supports both TripleDES and AES key types. func (b *Bundle) MergeKey() (KeyCipher, error) { switch b.KeyType { case KeyTypeAES: return b.mergeAES() default: - return b.merge3DES() + return b.mergeTripleDES() } } -func (b *Bundle) merge3DES() (KeyCipher, error) { +func (b *Bundle) mergeTripleDES() (KeyCipher, error) { kekBytes := make([]byte, 24) for _, component := range b.Components { kekBytes, _ = xor.XORBytes(kekBytes, component) From 4faa1f6ca631b37d9b4bd9e172105da8a813440f Mon Sep 17 00:00:00 2001 From: Lewis Chan Date: Wed, 22 Apr 2026 14:27:30 +0800 Subject: [PATCH 06/12] refactor: cleanup kek_bundle --- kek/kek_bundle.go | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/kek/kek_bundle.go b/kek/kek_bundle.go index 22adec6..855d63a 100644 --- a/kek/kek_bundle.go +++ b/kek/kek_bundle.go @@ -43,10 +43,13 @@ type Bundle struct { Components map[int][]byte } +// New creates a new KEK Bundle for TripleDES +// Deprecated: Use NewWithKeyType instead func New(name string, index int, size int, checkValue string) *Bundle { return NewWithKeyType(name, index, size, checkValue, KeyTypeTripleDES) } +// NewWithKeyType creates a new KEK Bundle with the specified key type func NewWithKeyType(name string, index int, size int, checkValue string, keyType KeyType) *Bundle { return &Bundle{ Name: name, @@ -95,21 +98,11 @@ func (b *Bundle) Merge() (des.Cipher, error) { if b.KeyType == KeyTypeAES { return des.Cipher{}, errors.New("Merge() does not support AES bundles, use MergeKey() instead") } - - kekBytes := make([]byte, 24) - for _, component := range b.Components { - kekBytes, _ = xor.XORBytes(kekBytes, component) - } - - kekCipher, err := des.CreateFromTripleDESKeyBytes(kekBytes) + result, err := b.mergeTripleDES() if err != nil { return des.Cipher{}, err } - if !kekCipher.VerifyCheckValue(b.CheckValue) { - return des.Cipher{}, errors.New("derived key check value does not tally") - } - - return kekCipher, nil + return *result.(*des.Cipher), nil } // MergeKey tries to build the result key from all the imported components. From ca7620d57eb3b6b5a15895be3a148447ec89df6a Mon Sep 17 00:00:00 2001 From: Lewis Chan Date: Wed, 22 Apr 2026 14:36:06 +0800 Subject: [PATCH 07/12] refactor: separate merge method --- kek/cipher.go | 17 ----------------- kek/kek_bundle.go | 42 +++++++++++++----------------------------- kek/kek_bundle_test.go | 39 ++++++++++----------------------------- 3 files changed, 23 insertions(+), 75 deletions(-) delete mode 100644 kek/cipher.go diff --git a/kek/cipher.go b/kek/cipher.go deleted file mode 100644 index 593bed5..0000000 --- a/kek/cipher.go +++ /dev/null @@ -1,17 +0,0 @@ -/* - 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 kek - -type KeyCipher interface { - VerifyCheckValue(checkValue string) bool - CheckValue() string -} diff --git a/kek/kek_bundle.go b/kek/kek_bundle.go index 855d63a..47e0ecd 100644 --- a/kek/kek_bundle.go +++ b/kek/kek_bundle.go @@ -93,30 +93,13 @@ func (b *Bundle) AddComponent(componentIndex int, componentValue string, compone // Merge tries to build the result TripleDES key from all the imported components // -// Deprecated: Use MergeKey instead as it supports both TripleDES and AES keys. +// Deprecated: Use MergeTripleDESKey instead. func (b *Bundle) Merge() (des.Cipher, error) { - if b.KeyType == KeyTypeAES { - return des.Cipher{}, errors.New("Merge() does not support AES bundles, use MergeKey() instead") - } - result, err := b.mergeTripleDES() - if err != nil { - return des.Cipher{}, err - } - return *result.(*des.Cipher), nil + return b.MergeTripleDESKey() } -// MergeKey tries to build the result key from all the imported components. -// It supports both TripleDES and AES key types. -func (b *Bundle) MergeKey() (KeyCipher, error) { - switch b.KeyType { - case KeyTypeAES: - return b.mergeAES() - default: - return b.mergeTripleDES() - } -} - -func (b *Bundle) mergeTripleDES() (KeyCipher, error) { +// MergeTripleDESKey tries to build the result TripleDES key from all the imported components +func (b *Bundle) MergeTripleDESKey() (des.Cipher, error) { kekBytes := make([]byte, 24) for _, component := range b.Components { kekBytes, _ = xor.XORBytes(kekBytes, component) @@ -124,23 +107,24 @@ func (b *Bundle) mergeTripleDES() (KeyCipher, error) { kekCipher, err := des.CreateFromTripleDESKeyBytes(kekBytes) if err != nil { - return nil, err + return des.Cipher{}, err } if !kekCipher.VerifyCheckValue(b.CheckValue) { - return nil, errors.New("derived key check value does not tally") + return des.Cipher{}, errors.New("derived key check value does not tally") } - return &kekCipher, nil + return kekCipher, nil } -func (b *Bundle) mergeAES() (KeyCipher, error) { +// MergeAESKey tries to build the result AES key from all the imported components +func (b *Bundle) MergeAESKey() (aes.Cipher, error) { var keyLen int for _, component := range b.Components { keyLen = len(component) break } if keyLen == 0 { - return nil, errors.New("no components to merge") + return aes.Cipher{}, errors.New("no components to merge") } kekBytes := make([]byte, keyLen) @@ -150,11 +134,11 @@ func (b *Bundle) mergeAES() (KeyCipher, error) { kekCipher, err := aes.CreateFromKeyBytes(kekBytes) if err != nil { - return nil, err + return aes.Cipher{}, err } if !kekCipher.VerifyCheckValue(b.CheckValue) { - return nil, errors.New("derived key check value does not tally") + return aes.Cipher{}, errors.New("derived key check value does not tally") } - return &kekCipher, nil + return kekCipher, nil } diff --git a/kek/kek_bundle_test.go b/kek/kek_bundle_test.go index 37bdd9a..7d0abaa 100644 --- a/kek/kek_bundle_test.go +++ b/kek/kek_bundle_test.go @@ -15,9 +15,6 @@ import ( "encoding/hex" "strings" "testing" - - "github.com/transferwise/crypto/aes" - "github.com/transferwise/crypto/des" ) func TestAddComponentInvalidValue(t *testing.T) { @@ -150,7 +147,7 @@ func TestAESAddComponentSuccess(t *testing.T) { } } -func TestAESMergeKeySuccess(t *testing.T) { +func TestAESMergeAESKeySuccess(t *testing.T) { kek := NewWithKeyType("aes-kek", 1, 3, "B1DD24", KeyTypeAES) err := kek.AddComponent(1, "2b7e151628aed2a6abf7158809cf4f3c", "7DF76B") @@ -168,7 +165,7 @@ func TestAESMergeKeySuccess(t *testing.T) { t.Fatalf("adding component 3 failed with %v", err) } - resultKey, err := kek.MergeKey() + resultKey, err := kek.MergeAESKey() if err != nil { t.Fatalf("merge result key failed with %v", err) } @@ -177,7 +174,7 @@ func TestAESMergeKeySuccess(t *testing.T) { } } -func TestAESMergeKeyCheckValueNotTally(t *testing.T) { +func TestAESMergeAESKeyCheckValueNotTally(t *testing.T) { kek := NewWithKeyType("aes-kek", 1, 3, "FFFFFF", KeyTypeAES) err := kek.AddComponent(1, "2b7e151628aed2a6abf7158809cf4f3c", "7DF76B") @@ -195,41 +192,25 @@ func TestAESMergeKeyCheckValueNotTally(t *testing.T) { t.Fatalf("adding component 3 failed with %v", err) } - _, err = kek.MergeKey() + _, err = kek.MergeAESKey() if err == nil { t.Fatal("should have failed if the result key check value does not tally") } } -func TestMergeOnAESBundleReturnsError(t *testing.T) { - kek := NewWithKeyType("aes-kek", 1, 3, "B1DD24", KeyTypeAES) - - kek.AddComponent(1, "2b7e151628aed2a6abf7158809cf4f3c", "7DF76B") - kek.AddComponent(2, "8e73b0f7da0e6452c810f32b809079e5", "56F9E5") - kek.AddComponent(3, "603deb1015ca71be2b73aef0857d77d9", "D0140D") - - _, err := kek.Merge() - if err == nil { - t.Fatal("Merge() should return error for AES bundles") - } -} - -func TestMergeKeyWith3DES(t *testing.T) { +func TestMergeTripleDESKeySuccess(t *testing.T) { kek := New("visa", 1, 3, "2D617C") kek.AddComponent(1, "E38FD6D9EF85A892F2FBFDD083A407AE", "DD1375") kek.AddComponent(2, "D0085DBFFB3723B926CB7980B9EA6268", "DACAF5") kek.AddComponent(3, "20295EBC0B80BF5EF7F78C9125686D3B", "DE5AA9") - resultKey, err := kek.MergeKey() + resultKey, err := kek.MergeTripleDESKey() if err != nil { - t.Fatalf("MergeKey failed with %v", err) + t.Fatalf("MergeTripleDESKey failed with %v", err) } - if resultKey.CheckValue() != "2d617c" { - t.Fatalf("Expected check value 2d617c but got %s", resultKey.CheckValue()) + expectedKey := "13AED5DA1F32347523C708C11F2608FD13AED5DA1F323475" + if !strings.EqualFold(expectedKey, hex.EncodeToString(resultKey.KeyBytes)) { + t.Fatalf("Expected %s but got back %s", expectedKey, hex.EncodeToString(resultKey.KeyBytes)) } } - -// Compile-time interface compliance checks -var _ KeyCipher = (*des.Cipher)(nil) -var _ KeyCipher = (*aes.Cipher)(nil) From 209503dc210e17cc0adf01f0c47b7e4d6e139ec4 Mon Sep 17 00:00:00 2001 From: Lewis Chan Date: Wed, 22 Apr 2026 14:37:56 +0800 Subject: [PATCH 08/12] docs: update comment --- aes/aes_cipher.go | 2 +- aes/aes_factory.go | 22 +++++++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/aes/aes_cipher.go b/aes/aes_cipher.go index bd99a4f..63b081b 100644 --- a/aes/aes_cipher.go +++ b/aes/aes_cipher.go @@ -33,7 +33,7 @@ type Cipher struct { KeyBytes []byte } -// New creates a new AES cipher from the given key bytes. +// CreateFromKeyBytes constructs a new AES GCM cipher using the raw key bytes provided, the raw bytes must be either 16, 24, or 32 bytes. // Deprecated: Use CreateFromKeyBytes instead. func New(keyBytes []byte) (Cipher, error) { return CreateFromKeyBytes(keyBytes) diff --git a/aes/aes_factory.go b/aes/aes_factory.go index 796f236..d8aa873 100644 --- a/aes/aes_factory.go +++ b/aes/aes_factory.go @@ -1,13 +1,15 @@ /* - 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. +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 aes @@ -18,6 +20,7 @@ import ( "errors" ) +// CreateFromKeyBytes constructs a new AES GCM cipher using the raw key bytes provided, the raw bytes must be either 16, 24, or 32 bytes func CreateFromKeyBytes(keyBytes []byte) (Cipher, error) { if len(keyBytes) != 16 && len(keyBytes) != 24 && len(keyBytes) != 32 { return Cipher{}, errors.New("AES key must be 16, 24, or 32 bytes") @@ -36,6 +39,7 @@ func CreateFromKeyBytes(keyBytes []byte) (Cipher, error) { return Cipher{gcmCipher, keyBytes}, nil } +// CreateFromKeyString constructs a new AES GCM cipher using the hex-encoded key string provided func CreateFromKeyString(key string) (Cipher, error) { keyBytes, err := hex.DecodeString(key) if err != nil { From 7bceb17ba522cfc7c2182bcf07000c8a589fa20b Mon Sep 17 00:00:00 2001 From: Lewis Chan Date: Wed, 22 Apr 2026 14:42:10 +0800 Subject: [PATCH 09/12] bump version to 1.12.0 --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index 169f19b..32bd932 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.11.0 \ No newline at end of file +1.12.0 \ No newline at end of file From 9b6f97fcf66b8e3acd3e1bee2376ad36635d64bb Mon Sep 17 00:00:00 2001 From: Lewis Chan Date: Wed, 22 Apr 2026 14:43:28 +0800 Subject: [PATCH 10/12] docs: update README --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ac4d1f0..c569758 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,9 @@ This repo provides a list of utility modules for common crypto operations. ### AES -- factory methods to construct an AES-GCM cipher with a 96-bit nonce from the input raw key bytes +- factory methods to construct an AES-GCM cipher with a 96-bit nonce from the input raw key bytes or hex text - encrypt & decrypt methods, the output ciphertext is prefixed with the random nonce. +- verify the constructed cipher against the check value ### DES @@ -13,7 +14,7 @@ This repo provides a list of utility modules for common crypto operations. ### KEK Bundle -Helper class to construct a 3DES key encryption key from a list of components. +Helper class to construct a TripleDES or AES key encryption key from a list of components. ### RSA From df4f43141d711248cb7a5793ae2ba2afaf4f45b9 Mon Sep 17 00:00:00 2001 From: Lewis Chan Date: Wed, 22 Apr 2026 14:51:48 +0800 Subject: [PATCH 11/12] test: fix expired key used for pgp testing --- pgp/pgp_factory_test.go | 8 +- pgp/testdata/key_info.yml | 2 +- pgp/testdata/private.pgp | 219 ++++++++++++++++++++------------------ pgp/testdata/public.pgp | 112 ++++++++++--------- 4 files changed, 182 insertions(+), 159 deletions(-) diff --git a/pgp/pgp_factory_test.go b/pgp/pgp_factory_test.go index c4bdcc9..844f769 100644 --- a/pgp/pgp_factory_test.go +++ b/pgp/pgp_factory_test.go @@ -29,7 +29,7 @@ func TestEvalHash(t *testing.T) { } hash := pgp.EvalHash() - if hash != "a9ad3d647c3978c569ed6295b402369b789c90d6" { + if hash != "f6c8bf3371502b21e4f43f5bde9277a682fbc4d3" { t.Fatalf("hash does not match: %s", hash) } } @@ -53,15 +53,15 @@ func TestEncryptDecrypt(t *testing.T) { text := "Secret text" enc, err := pgp.Encrypt([]byte(text)) if err != nil { - t.Fatal("Failed to encrypt text") + t.Fatalf("Failed to encrypt text: %v", err) } dec, err := pgp.Decrypt(enc, nil) if err != nil { - t.Fatal("Failed to decrypt text") + t.Fatalf("Failed to decrypt text: %v", err) } if string(dec) != text { - t.Fatal("Decrypted text does not match") + t.Fatalf("Decrypted text does not match: %s", string(dec)) } } diff --git a/pgp/testdata/key_info.yml b/pgp/testdata/key_info.yml index 564a3b1..2e5af1d 100644 --- a/pgp/testdata/key_info.yml +++ b/pgp/testdata/key_info.yml @@ -1,3 +1,3 @@ -expiry: 2026-04-18 +expiry: 2027-04-22 keyType: RSA and RSA keysize: 4096 bits diff --git a/pgp/testdata/private.pgp b/pgp/testdata/private.pgp index c345518..deb54b7 100644 --- a/pgp/testdata/private.pgp +++ b/pgp/testdata/private.pgp @@ -1,107 +1,118 @@ -----BEGIN PGP PRIVATE KEY BLOCK----- -lQcYBGQ/ou4BEADVpjRzvdzOmBWf6uIB9LIH64KfmpcQoksXjteZtW5CZFkDWiHX -Ji0dPrm/W7w9KxSWRqnakSWFAQM0wc3UfgWNFH5fwBNk5cd1SlAf+y0tXdUFFmHb -nKkyBghgVn47mU5nRm+Pf23KuJIxA8OjvG1PM3OSLHBIqA9j9eBjFhOaItjfor0v -2FtKUYPukkb/dwLT+NvBGiRNZuPByFlM1KxiV8gcIwEnofMvqWcYPkgNWxtXaAS/ -zRYHhshrGYZBD83fODjgRP9lO7p1N5d5asBHga1V5ly6Rzz6D02itd0E73Nzcvhi -vaTKQiAJdpOPPffsphQ9J/+y3lLneX4x1ZbAL96nESivupO6a1sZbSinCvGChxMF -dH1CJKPdaifpxgojAxVLl27ypBK/rc6PYonLPQt6oUtk62s1BD6HTWdRAT3S4W7M -Wgtnt7GrDHtE++w8PODxjmk+ovu01TbiIxx90j4EmewSj58BY90/l+J6loF6JRCt -VaX2U5Z0B0bOKK24GI5+4hpb74IdVHyjIklg/k9HfkpAC0xgfxv4zyz5YED5Ae4z -0DP/pI4fetjFwyEzcHpeNAyWYctBTueYDnpWzqGcKdJ5SKKyXHIJig3hMpGFA2m+ -GZSDNuQVEI9KPVuk3TvST2Ihqmu5LorjFVmaOA+LEa2AlXB6gI8o0TjhOQARAQAB -AA/+NJx1nSzbc4pzBMrX9ehWcOnXZEsyuRdmC3qS8BiD+thwLQmkfLE7edCo+Az0 -iVGTA8CJ8jc8+Ig7zGGPOK0B2/B4gEax3+5PlDwFCCMXyGyrWNHerjBhX96pcGPM -OQvzCS6IqVsVY87YvTYylp7/gfnnE09bJSExjQmOxIe6swGyyzttgBs0u9Pf+79M -uXsKmL1XDDHOT7svCdi22NskSiFYt0kNbz+LvahnKUqo/pT5YihMxxiPPLI9p1YF -P/9O0JGtO9jx1XGEZHoYlH8rs1AlKZqNKhgB1YgwM4m6UA1RLlRZAyO1MVNgRURM -aQE3DLEHGdpANsFVxAegglAZC24G6cBpH9QC7UcLM9c1okoKJoG24ohbZcY/IqsI -Vo1VfKPby30/GZkxnP4ay1P522Xm1ZjG+34tzFKwUiC7lGiLaYY/5dXlscXyOY7W -2ASi4ebek8J+hBW00Ud97M7uI+jnzojFzFPMBJZcUI5BzMYzbG/mBTto8fZcjbRG -XgPcfObul6gavGAG8tJNYRzargv7lX9Dp1cqIebNFPJQzi6lMyew17D3ziFB5z7q -3SaRlAkhDuSIbEKkbmE/Vzqe35r1y2Tnu4l171mUXaG64cKLtQMd1WX2lGs1Gkuq -rqOdbvRZ6+9R1PHe4OSmlVtO8XsdVf70fz721qbOn7rrolsIANb1NRxgzONxZEWN -rRQbXPPKlZDEg9XVFMYDAdmHTsCaIsTjgTJoWGERSnMeeb48y+XR9JTaDgCjsf07 -XPW1Ub96eId6BzT/vY1X+WLpFQoUFlK1oZheUMKCqrYr8SYv5lLFQWaRZ50gbGrf -BZQy1il+N6r95FY1oT5nVunYBqgcU7BIMltV1AVItAsNwpj7y2qkwCTqK4PRHiC1 -aSc/H8kEjhcl4/2BRXaCGFNCW/+9M02ZDLn58mBsftJyQ3Db95kiqERO1jDIq8AZ -wVuyOjjC7k/Wv3wPTSF9Q9iKsNPSNCR7dZvoYd3buisEa4UoiUYUbR/cx7yjWo8f -+S33CKsIAP5xCPS2HbhznsqTkN+DLEokxH+htcvVwa75a6CRu6HXzoonbT+OhD1a -77esX602cVNuCX41a/794ZPAdTCXKcJNIplyoZmEUlDNMrZXkajAKcHm7yZlTj05 -N1tTXZoj3ixlOHrp1bo3UhTjoE/ONY215SjTSuwZcEZDUFab0Rp7yn4wYhrAnv+4 -k7EXtH1oylVjG3YhyScRiIvdBRC2ymUkvOGhNk+X1J0yQck183fjdqiyQNnvGHvq -EMQE86G/zo9L0lgCU0r4OTRci7Dwf/yJMEFNRRixyYvpU9X6neWM241omOVqP0a/ -Zcmkhtc6W211gPiCbbbAK8pL9FegRasH/1EotCXjRTKLIs0cQvuyJip48k00HHeA -K8ZBJipznlZ7b/A3k14xIa4r7VVM0OU6wxdqmldR0wfc4EVrtnYBnHx4dGEtHTHW -GBmat9fMtYkWmOCh0gRKzfzd/Y1nFDaast5qxW7SKk93LlaacrOFKtcmLnLamXWy -Z3WbfloV6pcZt9IoUAGdJEbKp6gK8uzZuI2aI0CtqHx5LBY++Twr1oUAY5hzfiWm -7vZx/metSGSKOixk+NWZUESKGkJNNBBL6ZNWpmTMiOsFT0N8OKgc5fPv8+hT20BD -Qav+Tec/rCZJQ4sdNeieuPv/i04dM7xen0ylVvCaP5rZLBKL8Q+LQDWCWLRXU3Bl -bmQgSXNzdWFuY2UgVGVhbSBUZXN0IChUaGlzIGlzIGEgdGVzdCBrZXkgZm9yIGNy -eXB0bykgPHNwZW5kLWlzc3VhbmNlLXRlYW1Ad2lzZS5jb20+iQJXBBMBCABBFiEE -qa09ZHw5eMVp7WKVtAI2m3ickNYFAmQ/ou4CGwMFCQWjmoAFCwkIBwICIgIGFQoJ -CAsCBBYCAwECHgcCF4AACgkQtAI2m3ickNYR6g//UZCQfUAXqR9zAqhyGQkUmy9q -NsVHJE35+4W0oSh+gvqHqGLdGT8nqJtq12VGaQV5D5zZfDQVxtrPmnI96d56xULQ -WRm/m/tdM6XQcLCmFmgdeppuhXbiHoq2SN4D2HvMtNI+bHE7NbCnrVGSc3XvVJnA -z9xyjJSBDRI/JDYEiSNl60f/U4pflQw1cP9EQbzzmgh4ojral/GPRpFnBzDXpjj2 -MBzj/h6YF5+0dzepwh7QWAXaS/CJD3pLCWPQdQYH7+j60aovFclNRYWTtPYrGlEF -70I5G2SNG+kNEsF51OlWejr2C1MhFlfsbOt5BjHbUJQn9aF6i8+2FID8chr3E9AB -Wt/pbhLlGnxd0zSLZXYPhim0U2iTI3v0dC5gj5U5Mn+8jfvPTo3JbgYA7cCgtqkA -TSdSUwFCvVSMpVupiGDwSfyabpwFJfa5OxFENnQjFMzc5DvdGaLLO5LDph6pmTw0 -W6PTObyKF63NfLhhP1qGkFcchff3NBlWxmw5DX8Dw5Trc8gkgzwpigLIObm2eFHD -ox9koHh/OUjzyqpsQaGakq6iZOwXNdrs83Dciv1iRJfNpLHOnRhl4+QVpOwerX4P -UXb+Uen6Q/ztv854BRd16elQLkdUjAsLc1FleWtsXxJDLpd6oU6DMHAMRpMKN90k -ah4+U5ganVnbHAvfosCdBxgEZD+i7gEQAM3kEc1s5X+mtO7xfY8OB4HXE2ZnTwkM -o7tfrjGjfuHdmBYayrrrvnnT7zDj5n+ltI7I2DnKcRO/nwXr6uIOET+mZfJjSMRu -U04Eb6JcgRvsv6+Pz/1SeNk5B77JHEKBHTVjfwhYX85Dkgu4aHbwkbd9+nmSRei+ -VvxQGRtZS4lMc8VW92Txu0uTIYR6FCa6bYPTL9oaNfwS/dvwUgkl255qiNSJjV0G -mBg+j7CLbVPCZIu91EwWAsf0jgBPtRTeF9Zw7PuZU7pcfbkjpILSdrd1cK7AvwQQ -7EsJ7id5h4G9xAnykd3jRBKXuz72vYxGjjtey9mQN7COU6RLEZexDTQBN2cpIzqJ -lb2VDlaatfnCupZSBONblasi750/N21XLinsqFgtu5Qt2FYAC1bMF1NubbLXHOrs -tpynqRXL4OViRyHaTIJfCpiHByQEHAHidBWRVIr6CKGyRHrfGTYnI0DFnn9glVg0 -Vmplpl3H0JNs3ah8EXy7uDEQK3Y0X83zEuXlg3yHFcvrtqrwumuXtPG8t24vxDt7 -q6Gbxo2FOmPJZfuK9TbYLxq4rh+sD68jYSl2PsHUhDGZnURDijM+cYbyCPAL30px -k3NI4/rlAYfyQB0lidCLqRQlCNRYe0kSPmhTySL+pLr2IXz0Jzbf52SprQH5zxxA -SG5FAojn6tQpABEBAAEAD/0RrE2Q5hOPEQa/6e++q1Qeo5ERPbXtj22whEaGEUQv -bB+FajhTsi+v8m66Mh3TgCxPA1N4JnXtXYnvbrBDRjTZ01U16KrS3at962iPtcQJ -WxmbHsuaMY2ZC8mr6URpOv8AWbGKTFew82Dia6pf50eCyigJbnJcx5Xc4508GT6g -IvVfD5d9zTgoBRGf9wCq0F7dLXASlyRiJlkvG2JXUa4X24oyQDpnrUy57AjWfSld -wrZxvlIuU8daVZYo1a60A1pleF0HPcKPoSfTkAkrh5hUKBsLjRT4auF1kX0lW0uz -6c+dkPpPYxYxSF/0yCY+F8JWu0zhOH1nRNhOJnl0nWiGEjJI1JWBfgjqwnWFBswP -g/chJkrwGsDXoBtqwxIHPVazdhSFnjlljc8kV4FNxpSDA/ILXf3AQjI4O0gFPXvo -bju4KvqQtHUHmLpEZw3924Ma90c7EMpOHIip0MTa1g4uSPVVrn7II9/qPkCQrMAq -tCYeXQMet78Xzmhnr5GOWI339yZoa7rFJxpnADccwgs4yuHshiFByT2/AJtfxeeE -vR54t7vmR6gCpi6/+N3KTnlBDPjZ97jgyk5gdmuhqxgOa9Skw1AHkhYx0QzfLjC7 -pgOREaSECAYEi2o5ofG1D1Tn5C7VxEsHjxvlYpYtRXMXURjrm/qYfyZ59QN1JA5O -BwgA0mfFSmzMsUec/hjkAqHdgavEmE/s6eUq4Yha3yAJxorLlYIVvfZBWwVB8Cmw -Mo+TayUN9Yg1jNb0LeuR0O7j79feiVyeDdcOd0IbCKSTeuf09XrKwaKTQ3IvTneM -H2u0jJOZbZuDVCTiOsVhWatHpDZywtUrj2HO0AyDyH0ub8QxnvhzV/EqbCCv6bZx -bIWmiOGbxuNbQY0/xAiTcJwdpxNVSuawhye+6lZmwB+qEC0dUYZ2n8v5UxmBdZhp -dOFn7SJzw0+qVYpK5VcegG2EUAsVPlrPfYWcZGvCY+iFwLDra3E//QB8KS2hH2I+ -qUHsz9ve18jgF9Z6uLl+oJaQgwgA+oHcCLNJHm+J8bW9B3nTwpwi3jjqQyNdyUNE -5s1EPUtvamB/4fhY2AazAvNolDFfkAL7NNp7ckhCZzUbbXUGPdgpRvAK86DMoHFd -StAbFLQVZYdptd4V8up9MYlndReqeotnYQN+4g6IF3TANcUHJRA3g+EcRv2NNYfn -nVz5BTFrY/3wIFJkwJFED5al0HHPTgPD+0MJ1+PlADQYjr722sDf/UUQc1+VOidL -uN3gnKHDjKPF2BjUrMLvbvG3R0ntAxO87Z/mRq/Ddz8IiCssWejNggpRSEtaBD9e -ce4G9opE/4TTx0m9Dbviop4olOumk/m+LhjfChIEf/TvojWQ4wgAws/SROViUcCI -n0ZANht8bT7et+cKVHLIlaz/YBfhqkPRpQ8ZiRtTN3ERR3s6AJr2kzQnQq2eMZH7 -rzEU3xYzgnrh76vVJrwPNogDMLK14O9PkAl67xPsgtJlHnuVjO2n/N+AkTrzEiXI -qrbLXjmKxyCQVxcwA3aMTx6ZySy9KKGraVixZN29cKxSWlkdDzh94WxPQIZ+77LJ -maCHMM9VIc0OHbXkKB+V06KZxDERfCkVDXp9QmO68uA7no/XS3JwLQfNPnucbhRH -Nr4OyTIf6eQmNRAdKWyA1zlIZz56ZJqjgpgSlAGml/7XarSp3FwhWJVGma397RHz -N4sefQbwMmtViQI8BBgBCAAmFiEEqa09ZHw5eMVp7WKVtAI2m3ickNYFAmQ/ou4C -GwwFCQWjmoAACgkQtAI2m3ickNbQ+hAAkYmJUaR0UOWI0lsFP/yU6xwyvM9qq9ZZ -BRpiObets4IzrawBOv2mhemu45ovS9m4Ke7n6y8NNnK4Q8Zm2ngsPMGT3PBLlY+1 -SjnggrHXgbboBHA3RMAXEnEt6f7s8fo2BHoCHU0hDws5GFAu3Kepe9DP1518jdfK -JX7DiDGVoPiHW7AWMVPNNMYFpr2Mo/RXcYiPr57HKa/CLcTV3lvPoQQTRlSPQj/g -lvGG5fmf5hnx79yaNPgt4amyVy2QCDcEwkzFDRM2A7juOvIu1Yab90WKlhp7fwMd -Pa93O5dVD/mrxSaRC1P3DmPIsY//96jJ2KI8iQ3HNgIl5sESEdM2rJxycOsI+f2e -VqUWp88ay4K5jzb/RJs2Y/ZrHxaqP2ejme1YQoihfRZBd/+BHKJnqudmf7Vn/TeC -cW+5S0bfed1sCWvsMjkhIsc3Y/EVtJ7oyFI20bSlCzhybD8lUOeMQbkQSatY9EcD -1Gb42UauiJoJMmBhBWBCdZQcGToaw95bgr3zP/PDwM5T14ATYeYyrCldeaLGefhb -uzNb1hpUSzNYLC2xDzPs+uoKTQVW1iHx0U5+vCtlzxla9+xPwI0yVai+lNK6f0uF -0Xdt4gb+cwinMcPhXVNVbamfwA02bjAbqOzBCuXolHuL9gIsyzyS/lEA7B1/6k9X -StSh2AuWXHo= -=RKuH +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 +=G4ie -----END PGP PRIVATE KEY BLOCK----- diff --git a/pgp/testdata/public.pgp b/pgp/testdata/public.pgp index 3da45d1..ce87377 100644 --- a/pgp/testdata/public.pgp +++ b/pgp/testdata/public.pgp @@ -1,53 +1,65 @@ -----BEGIN PGP PUBLIC KEY BLOCK----- -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 -kv5RAOwdf+pPV0rUodgLllx6 -=SiHQ +mQINBGnob8IBEAC7mhVzMe9wx417oUxQXdWPQB2IqYw8WSSCneJKa58JWZlvfWpF +LgJUMu8sOCCUZldYS1m5kd5DEqX4URg9AodGhwjuVsTmgtKf5npXjusU7rdKEa7C +nFHPNhqR1zeAb8IEECLKVgPf8rIgvQkPrT+OBZ3m7ezs+6TvlPEHNJzAZqg0g8sN +sOb7tNOGYIx2c5h3SncK4xak3ewVUAIsxJuEvSk/E/oOKqQ49S2xDsBwkewzA6re ++pEb2cYuCJ/2T/algjtlvntlt/c+XTkuc9Wd4BdZPssJvZkmU1dKOqBN2stE7ypR +kfWh0OkHimZempFdc7GCEe/8KDbRV9I6bI3i8A3wiMynAlebqFi1G9e44PxdT00L +Wlb1zKz5FqGqsKhGA1Adft7a/qAR1oWIsqFRqbaw5v/M+5+6xwzG1l720DyiLmei +d64QJ3vfbFZOiREkzZ9G4RwfDq3UjljjMniUyKeuHyBvwjLTpr/uaYBGIMv/roFf +y0FuLT33hMMmN/JN2dmhaQ8mo9Mulp2En6dSR57kMa7ye+NRrkaHC5solKbQK1Pr +Eli3eODuxYN2tq4DpOtr1HTTiiC+UbDZxUFpUvNkycOgrpVazJdb08vT/BtXhnkL +NmXF3JDpAUJ/7YQKfNSI9PCibSCdrHP1oabrb1nNOmaGcH+MulXhTjM6iwARAQAB +tBxUZXN0IFVzZXIgPHRlc3RAZXhhbXBsZS5jb20+iQJ0BBMBCABeFiEE9si/M3FQ +KyHk9D9b3pJ3poL7xNMFAmnob8IbFIAAAAAABAAObWFudTIsMi41KzEuMTIsMCwz +AxsvBAUJAeELfgULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRDeknemgvvE +0xLFD/4wbtoDWfuRLl2/9C7Q86FGlSRBnBqQ16L7g1zC7i035magHkN9ntSJG3+r +sjXvN3B1zvrAJNxkrHVyJE/vmrx7GnccDESI4X5HuVSH/l+uZd6JMF0ijxFNdv4C +eX2VZxTf7OBmFTzPsDAkcB7eUics6dJaD2lARAmF3lG1FHSoTMHatDgaPdaljxuo +1bAk2d7Z3KE9n82goSbophnguuxEiFEllxmy7L6IANxgvjQ/QDP5yHvNvz2mJDWZ +O04wjruc1TE3dZM4/HifjixK4SflVcYKRuV2ErD2QQTfmWvQz/+4L6YHWQmKZsAh +mDDvSXh3+y4OUcw4nj09kMcQZ0jTKSMmLn8tnGpRQr1GwF851tnfGWygwRkIcEgL +37dI9coUmPkEnGqNRUr26UFeAw6/VygAHtxuY5RHWmhkj+DYLVf+GhGt0CDVogK5 +Danrl8OIuLr6NJJJC06ouQNVnz8ZKJOKr2gf9XdE4CgQ+ujF4BlVCAoh7ZaGEyEF +1+rFbepaoinaCXMqJqj8/ophCIRlSGgnwyOxBGXoNGR5mZD0WRKRp83zq2mRYB70 +8ezT7SgPVTNadHXfGB4ms7c5pjjzbeg1ZjcT/qPHKsTL37We/syPcxWWLPXw754U ++6vLJqh29CW4XjRONUlVhoppEEGQnzUNmzuZ9U6Kb83DLf31LbkCDQRp6G/CARAA +8Fsit4H6xZdEBWzrbDkJc03Ma4CJDUMoRp6BWjZh+zYjJpVDlv+lVau4awXxkXwy +Wbwuxe+36A2/ep0RgmFFqsRMyr7OiVDa1vF8fdFEMcNjxQrIvtvnms7InldqkZx2 +jYRFtGrHnim5RlIrsQ+X/hUIv99At2R7k+N+tlI0RO6OuWOLYgF/k9n5g3RBboqh +9+H24XpCVTLhdGz/EvZbkU312m0Dbqk7nqzfsWNe8rXVEICBAWuhDGmDVflAswCK +kIfs85uWRsdGjlqAylw+XiDnwpTpwGXAD/tLVYlLgj1k4q2BjrwsRoeJEeVAE+YP +CQeJYV5UMlm1KO37yf473M3MlbQRpS3lidzUGbbFWIkPSqW55iErPPWSdofahk5q +fgga0Q83acluTJnlUFrDvXa1W7OVrQVIg4HnajPBx6JFF7uPBawutbOUz43tOQEB +MrZOn6HMjOsFd+eDO8wfdX2RZwViCu4xqWTjxEmNkld31/LTwbFwrybv61Hwk3Mf +aXPFzo4Ryf4nxJijErAqpBwK1BvLjvQE3JbKuFPMNKud4vuSVMiGraBUlOAlWDub +o1cnUJJKz0uXlCDS5H6Ut/yulhcWH7HExCLxnWsXtgnL9Ui2vIgSzVssLRzHzg5b +M43gWjeWvLr4+1N51XhO3MJcOKVvPclHe6rUUIjLCVMAEQEAAYkEjgQYAQgAQhYh +BPbIvzNxUCsh5PQ/W96Sd6aC+8TTBQJp6G/CGxSAAAAAAAQADm1hbnUyLDIuNSsx +LjEyLDAsMwIbLgUJAeELfgJACRDeknemgvvE08F0IAQZAQgAHRYhBDeMU+i7CtwL +YxRKiRdiec9hFppMBQJp6G/CAAoJEBdiec9hFppMmLMQANCC6BYUIgw7tEOf8Oi0 +1tOzRDJOpNXUxsxGxEFwspWFneaYBRy1TUIFZcI/rQUEF0CqWfNK7zsxHUo3CWS6 +AWSKB8Plo6TKM7kkNkaXCY4Llydm7072vnn/G7QmqWCPU6GBb6OcGl8MPiWaJ3qT +BuBUtzMFs3YDp+eb5qJ20R2cE0d2R6+2B105m1TzIwYn5X5BvQLJ7gl97EDf2g2H +7arvgEJN3Kacti51A2D7IRKz0Ee7CGMNOt024y+fDUBt4zuwwgWWNtPd3MzxOfqz +vLJuWSMM4W2Kwusy5Np5ezagyrHjHvpJxK/cLOTPlKVJHznMiD6wLwAruYkNA+ov +/4IJsN9uQWNP5ckRwXVa2l/8Am4HyOM1o1AiadPDADl4KJ3BvykTmIMUYJG4SfPx +34SAL5fuEu18zxeLCmmYyg6YdyzCg4Ih7rBE5Rj/6JTYRtMeK43l5SH5usjJCugX +IF1jUSMaNJkTFaEzYPl1bjuie6mgGa84Btd6yJc6VHyIydJJvUGe6HfxsxL/ZVeR +ajmWY07w0/RaXjHtOtauQhrAHZ4tbHO0FEGPFsW8WJx7ArSPbJYVYF6pdZcQEjHw +a8xBqrPJa5ZfZYLv5AHYBfnhJxQ3dKQPr63MM3mWFRY2ATFfNLmabyh6266hbPRu +DcMuIs2M8kXfZGicBu0R4zjGoLUQAKdbgZ51yIkkL0c2tAdILIDMKe+n0lSnscTI +3PIfEmdFEuiWtBgshnbK/YnB3o/A7qPoZL2dm+gPyfO4lpoRfx8dIKu0MXFBuInJ +pXr+lFBxSTy0cyBo6qSCWpPZrvGcJ1vc+uBKkBNpkFm2T1EnUw1Rq1ppSdWSTwzq +YnWwLwIl9FckzKm8zKow3U/sVDzpFJzMqpTbf0A7cs+QUWeRsrDO9zpKEarC/wKV +yoHu4EYhf9koOgqsuOqyxTCeBnuz348+tgAoeJ7BleZIEshljdVIhWNrpOMZCIVw +GJFpLL9hjS4O4gaDT8SMykCtdFiZXCEI9P0UF1i3JMn/VR3m7BnW5Q7393zyLTDh +tiLFQm2D508XIvszrJGbNRxWSlMtELDhURdIk5G3YBC1n7KgVLhI1PgJyn+9YAvE +Y4uFw+LKSVSBTCDWd0uYHywIv36+1ZsEGB1vw53MvA9R1GhYJwtYA4q6f8z0q5es +HqF9VM3oQXCkQwRyaGh8+g6YPE2YkA/y7s1GGdNjAbZKFYQpY5UOhuH/fki4QOvR +aSy681KAB07RTuu/zlWEOnh3a0Uc0SxIxhjulsISqZ7KAj4qy7Z3V54+ddPDo9Ua +I8r15wikhJ7ThM5lVG7Fie/VCBN+9BkuyvAcCx5aOnKzkieT19SrHtT4rAwJ8xt4 +6AidKFZ1 +=ItXI -----END PGP PUBLIC KEY BLOCK----- From 44a3cca140719d52e034a466b722b66b2ca5847c Mon Sep 17 00:00:00 2001 From: Lewis Chan Date: Wed, 22 Apr 2026 15:07:13 +0800 Subject: [PATCH 12/12] fix comments and add more tests --- aes/aes_cipher.go | 14 ++++- kek/kek_bundle.go | 18 ++++-- kek/kek_bundle_test.go | 121 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 6 deletions(-) diff --git a/aes/aes_cipher.go b/aes/aes_cipher.go index 63b081b..89e7f13 100644 --- a/aes/aes_cipher.go +++ b/aes/aes_cipher.go @@ -33,7 +33,8 @@ type Cipher struct { KeyBytes []byte } -// CreateFromKeyBytes constructs a new AES GCM cipher using the raw key bytes provided, the raw bytes must be either 16, 24, or 32 bytes. +// New constructs a new AES GCM cipher using the raw key bytes provided; the raw bytes must be either 16, 24, or 32 bytes. +// // Deprecated: Use CreateFromKeyBytes instead. func New(keyBytes []byte) (Cipher, error) { return CreateFromKeyBytes(keyBytes) @@ -77,5 +78,14 @@ func (c *Cipher) CheckValue() string { } func (c *Cipher) VerifyCheckValue(checkValue string) bool { - return strings.EqualFold(c.CheckValue(), checkValue) + if checkValue == "" { + return false + } + + derivedCheckValue := c.CheckValue() + if derivedCheckValue == "" { + return false + } + + return strings.EqualFold(derivedCheckValue, checkValue) } diff --git a/kek/kek_bundle.go b/kek/kek_bundle.go index 47e0ecd..4efb043 100644 --- a/kek/kek_bundle.go +++ b/kek/kek_bundle.go @@ -14,6 +14,7 @@ package kek import ( "errors" + "fmt" "github.com/hashicorp/vault/sdk/helper/xor" "github.com/transferwise/crypto/aes" @@ -78,7 +79,7 @@ func (b *Bundle) AddComponent(componentIndex int, componentValue string, compone return errors.New("component check value does not tally") } b.Components[componentIndex] = cipher.KeyBytes - default: + case KeyTypeTripleDES: cipher, err := des.CreateFromTripleDESKeyString(componentValue) if err != nil { return errors.New("invalid component") @@ -87,6 +88,8 @@ func (b *Bundle) AddComponent(componentIndex int, componentValue string, compone return errors.New("component check value does not tally") } b.Components[componentIndex] = cipher.KeyBytes + default: + return fmt.Errorf("unsupported key type: %s", b.KeyType) } return nil } @@ -120,8 +123,11 @@ func (b *Bundle) MergeTripleDESKey() (des.Cipher, error) { func (b *Bundle) MergeAESKey() (aes.Cipher, error) { var keyLen int for _, component := range b.Components { - keyLen = len(component) - break + if keyLen == 0 { + keyLen = len(component) + } else if len(component) != keyLen { + return aes.Cipher{}, fmt.Errorf("component length mismatch: expected %d bytes but got %d", keyLen, len(component)) + } } if keyLen == 0 { return aes.Cipher{}, errors.New("no components to merge") @@ -129,7 +135,11 @@ func (b *Bundle) MergeAESKey() (aes.Cipher, error) { kekBytes := make([]byte, keyLen) for _, component := range b.Components { - kekBytes, _ = xor.XORBytes(kekBytes, component) + var err error + kekBytes, err = xor.XORBytes(kekBytes, component) + if err != nil { + return aes.Cipher{}, fmt.Errorf("failed to XOR components: %w", err) + } } kekCipher, err := aes.CreateFromKeyBytes(kekBytes) diff --git a/kek/kek_bundle_test.go b/kek/kek_bundle_test.go index 7d0abaa..f974c35 100644 --- a/kek/kek_bundle_test.go +++ b/kek/kek_bundle_test.go @@ -198,6 +198,127 @@ func TestAESMergeAESKeyCheckValueNotTally(t *testing.T) { } } +func TestAES192MergeAESKeySuccess(t *testing.T) { + kek := NewWithKeyType("aes192-kek", 1, 3, "5AE64F", KeyTypeAES) + + err := kek.AddComponent(1, "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", "22452D") + if err != nil { + t.Fatalf("adding component 1 failed with %v", err) + } + + err = kek.AddComponent(2, "603deb1015ca71be2b73aef0857d7781ee9893232c8a057b", "CCF0FB") + if err != nil { + t.Fatalf("adding component 2 failed with %v", err) + } + + err = kek.AddComponent(3, "2b7e151628aed2a6abf7158809cf4f3c1234567890abcdef", "493E5F") + if err != nil { + t.Fatalf("adding component 3 failed with %v", err) + } + + resultKey, err := kek.MergeAESKey() + if err != nil { + t.Fatalf("merge result key failed with %v", err) + } + if resultKey.CheckValue() != "5ae64f" { + t.Fatalf("Expected check value 5ae64f but got %s", resultKey.CheckValue()) + } +} + +func TestAES256MergeAESKeySuccess(t *testing.T) { + kek := NewWithKeyType("aes256-kek", 1, 3, "3820D1", KeyTypeAES) + + err := kek.AddComponent(1, "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "E568F6") + if err != nil { + t.Fatalf("adding component 1 failed with %v", err) + } + + err = kek.AddComponent(2, "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b01020304050607a1", "583F49") + if err != nil { + t.Fatalf("adding component 2 failed with %v", err) + } + + err = kek.AddComponent(3, "2b7e151628aed2a6abf7158809cf4f3caabbccddeeff0011223344556677cc55", "EFBEED") + if err != nil { + t.Fatalf("adding component 3 failed with %v", err) + } + + resultKey, err := kek.MergeAESKey() + if err != nil { + t.Fatalf("merge result key failed with %v", err) + } + if resultKey.CheckValue() != "3820d1" { + t.Fatalf("Expected check value 3820d1 but got %s", resultKey.CheckValue()) + } +} + +func TestAES192MergeAESKeyCheckValueNotTally(t *testing.T) { + kek := NewWithKeyType("aes192-kek", 1, 3, "FFFFFF", KeyTypeAES) + + err := kek.AddComponent(1, "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", "22452D") + if err != nil { + t.Fatalf("adding component 1 failed with %v", err) + } + + err = kek.AddComponent(2, "603deb1015ca71be2b73aef0857d7781ee9893232c8a057b", "CCF0FB") + if err != nil { + t.Fatalf("adding component 2 failed with %v", err) + } + + err = kek.AddComponent(3, "2b7e151628aed2a6abf7158809cf4f3c1234567890abcdef", "493E5F") + if err != nil { + t.Fatalf("adding component 3 failed with %v", err) + } + + _, err = kek.MergeAESKey() + if err == nil { + t.Fatal("should have failed if the result key check value does not tally") + } +} + +func TestAES256MergeAESKeyCheckValueNotTally(t *testing.T) { + kek := NewWithKeyType("aes256-kek", 1, 3, "FFFFFF", KeyTypeAES) + + err := kek.AddComponent(1, "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "E568F6") + if err != nil { + t.Fatalf("adding component 1 failed with %v", err) + } + + err = kek.AddComponent(2, "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b01020304050607a1", "583F49") + if err != nil { + t.Fatalf("adding component 2 failed with %v", err) + } + + err = kek.AddComponent(3, "2b7e151628aed2a6abf7158809cf4f3caabbccddeeff0011223344556677cc55", "EFBEED") + if err != nil { + t.Fatalf("adding component 3 failed with %v", err) + } + + _, err = kek.MergeAESKey() + if err == nil { + t.Fatal("should have failed if the result key check value does not tally") + } +} + +func TestAESComponentLengthMismatch(t *testing.T) { + kek := NewWithKeyType("aes-mixed-kek", 1, 2, "AAAAAA", KeyTypeAES) + + err := kek.AddComponent(1, "2b7e151628aed2a6abf7158809cf4f3c", "7DF76B") + if err != nil { + t.Fatalf("adding AES-128 component failed with %v", err) + } + + err = kek.AddComponent(2, "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", "22452D") + if err != nil { + t.Fatalf("adding AES-192 component failed with %v", err) + } + + _, err = kek.MergeAESKey() + if err == nil { + t.Fatal("should have failed if components have mismatched lengths") + } +} + func TestMergeTripleDESKeySuccess(t *testing.T) { kek := New("visa", 1, 3, "2D617C")