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 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 diff --git a/aes/aes_cipher.go b/aes/aes_cipher.go index c699e01..89e7f13 100644 --- a/aes/aes_cipher.go +++ b/aes/aes_cipher.go @@ -16,35 +16,28 @@ 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 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 +// 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) { - 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 @@ -73,3 +66,26 @@ 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 { + if checkValue == "" { + return false + } + + derivedCheckValue := c.CheckValue() + if derivedCheckValue == "" { + return false + } + + return strings.EqualFold(derivedCheckValue, 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) diff --git a/aes/aes_factory.go b/aes/aes_factory.go new file mode 100644 index 0000000..d8aa873 --- /dev/null +++ b/aes/aes_factory.go @@ -0,0 +1,49 @@ +/* +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" +) + +// 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") + } + + 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 +} + +// 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 { + 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) + } + } +} diff --git a/kek/kek_bundle.go b/kek/kek_bundle.go index 5c359d4..4efb043 100644 --- a/kek/kek_bundle.go +++ b/kek/kek_bundle.go @@ -9,16 +9,25 @@ 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 ( "errors" + "fmt" "github.com/hashicorp/vault/sdk/helper/xor" + "github.com/transferwise/crypto/aes" "github.com/transferwise/crypto/des" ) +type KeyType string + +const ( + KeyTypeTripleDES KeyType = "TripleDES" + 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 +38,26 @@ type Bundle struct { Size int // result key check value CheckValue string + // key type (TripleDES or AES) + KeyType KeyType // imported components index value map 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, Index: index, Size: size, CheckValue: checkValue, + KeyType: keyType, Components: make(map[int][]byte), } } @@ -50,21 +69,40 @@ 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") - } - if !cipher.VerifyCheckValue(componentCheckValue) { - return errors.New("component check value does not tally") + 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 + case KeyTypeTripleDES: + 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 + default: + return fmt.Errorf("unsupported key type: %s", b.KeyType) } - - // 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 +// Merge tries to build the result TripleDES key from all the imported components +// +// Deprecated: Use MergeTripleDESKey instead. func (b *Bundle) Merge() (des.Cipher, error) { + return b.MergeTripleDESKey() +} + +// 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) @@ -80,3 +118,37 @@ func (b *Bundle) Merge() (des.Cipher, error) { return kekCipher, nil } + +// 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 { + 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") + } + + kekBytes := make([]byte, keyLen) + for _, component := range b.Components { + 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) + if err != nil { + return aes.Cipher{}, err + } + if !kekCipher.VerifyCheckValue(b.CheckValue) { + return aes.Cipher{}, 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..f974c35 100644 --- a/kek/kek_bundle_test.go +++ b/kek/kek_bundle_test.go @@ -117,3 +117,221 @@ 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 TestAESMergeAESKeySuccess(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.MergeAESKey() + 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 TestAESMergeAESKeyCheckValueNotTally(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.MergeAESKey() + if err == nil { + t.Fatal("should have failed if the result key check value does not tally") + } +} + +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") + + kek.AddComponent(1, "E38FD6D9EF85A892F2FBFDD083A407AE", "DD1375") + kek.AddComponent(2, "D0085DBFFB3723B926CB7980B9EA6268", "DACAF5") + kek.AddComponent(3, "20295EBC0B80BF5EF7F78C9125686D3B", "DE5AA9") + + resultKey, err := kek.MergeTripleDESKey() + if err != nil { + t.Fatalf("MergeTripleDESKey failed with %v", err) + } + expectedKey := "13AED5DA1F32347523C708C11F2608FD13AED5DA1F323475" + if !strings.EqualFold(expectedKey, hex.EncodeToString(resultKey.KeyBytes)) { + t.Fatalf("Expected %s but got back %s", expectedKey, hex.EncodeToString(resultKey.KeyBytes)) + } +} 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----- -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 -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 +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 +6AidKFZ1 +=ItXI -----END PGP PUBLIC KEY BLOCK-----