Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
67 changes: 67 additions & 0 deletions aes/aes_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
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/cipher"
"encoding/hex"
"strings"
)

const (
checkValueDefaultBytes = 3
checkValueMinimumBytes = 2
)

// KCVMethod determines how Key Check Values are computed.
type KCVMethod int

const (
// KCVMethodECB computes KCV by AES-ECB encrypting a block of zero bytes.
KCVMethodECB KCVMethod = iota
// KCVMethodCMAC computes KCV using AES-CMAC over a block of zero bytes per RFC 4493.
KCVMethodCMAC
)

// Key wraps an AES key and supports Key Check Value verification.
type Key struct {
keyBlock cipher.Block
KeyBytes []byte
kcvMethod KCVMethod
}

func (k *Key) VerifyCheckValue(checkValue string) bool {
checkValueBytes := len(checkValue) / 2
if checkValueBytes < checkValueMinimumBytes || checkValueBytes > k.keyBlock.BlockSize() {
return false
}

kcvBytes := k.computeKCV()
derivedCheckValue := hex.EncodeToString(kcvBytes[:checkValueBytes])
return strings.EqualFold(derivedCheckValue, checkValue)
}

func (k *Key) CheckValue() string {
kcvBytes := k.computeKCV()
return hex.EncodeToString(kcvBytes[:checkValueDefaultBytes])
}

func (k *Key) GetKeyBytes() []byte {
return k.KeyBytes
}

func (k *Key) computeKCV() []byte {
zeros := make([]byte, k.keyBlock.BlockSize())
encrypted := make([]byte, k.keyBlock.BlockSize())
k.keyBlock.Encrypt(encrypted, zeros)
return encrypted
}
38 changes: 38 additions & 0 deletions aes/aes_key_factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
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 (
stdaes "crypto/aes"
"encoding/hex"
"errors"
)

func CreateKeyFromBytes(keyBytes []byte, kcvMethod KCVMethod) (Key, error) {
if len(keyBytes) != 16 && len(keyBytes) != 24 && len(keyBytes) != 32 {
return Key{}, errors.New("AES key must be 16, 24, or 32 bytes")
}

keyBlock, err := stdaes.NewCipher(keyBytes)
if err != nil {
return Key{}, errors.New("invalid AES key")
}
return Key{keyBlock, keyBytes, kcvMethod}, nil
Comment on lines +25 to +29
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateKeyFromBytes stores the caller-provided keyBytes slice directly in the returned Key. Since slices are mutable, external mutation after creation would change the in-memory key unexpectedly. Consider copying keyBytes before storing it, and similarly have GetKeyBytes() return a defensive copy to prevent accidental mutation of sensitive key material.

Suggested change
keyBlock, err := stdaes.NewCipher(keyBytes)
if err != nil {
return Key{}, errors.New("invalid AES key")
}
return Key{keyBlock, keyBytes, kcvMethod}, nil
keyBytesCopy := append([]byte(nil), keyBytes...)
keyBlock, err := stdaes.NewCipher(keyBytesCopy)
if err != nil {
return Key{}, errors.New("invalid AES key")
}
return Key{keyBlock, keyBytesCopy, kcvMethod}, nil

Copilot uses AI. Check for mistakes.
}

func CreateKeyFromString(key string, kcvMethod KCVMethod) (Key, error) {
keyBytes, err := hex.DecodeString(key)
if err != nil {
return Key{}, errors.New("AES key is not in correct hex format")
}
return CreateKeyFromBytes(keyBytes, kcvMethod)
}
80 changes: 80 additions & 0 deletions aes/aes_key_factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
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 TestCreateKeyFromStringInvalidHex(t *testing.T) {
_, err := CreateKeyFromString("not-hex", KCVMethodECB)
if err == nil {
t.Fatal("should fail with invalid hex")
}
}

func TestCreateKeyFromStringWrongLength(t *testing.T) {
_, err := CreateKeyFromString("AABBCCDD", KCVMethodECB)
if err == nil {
t.Fatal("should fail with wrong key length")
}
}

func TestCreateKeyFromBytesWrongLength(t *testing.T) {
_, err := CreateKeyFromBytes([]byte{1, 2, 3}, KCVMethodECB)
if err == nil {
t.Fatal("should fail with wrong key length")
}
}

func TestCreateKeyFromBytes16(t *testing.T) {
key, err := CreateKeyFromBytes(make([]byte, 16), KCVMethodECB)
if err != nil {
t.Fatalf("failed to create AES-128 key: %v", err)
}
if len(key.KeyBytes) != 16 {
t.Fatalf("expected 16 bytes but got %d", len(key.KeyBytes))
}
}

func TestCreateKeyFromBytes24(t *testing.T) {
key, err := CreateKeyFromBytes(make([]byte, 24), KCVMethodECB)
if err != nil {
t.Fatalf("failed to create AES-192 key: %v", err)
}
if len(key.KeyBytes) != 24 {
t.Fatalf("expected 24 bytes but got %d", len(key.KeyBytes))
}
}

func TestCreateKeyFromBytes32(t *testing.T) {
key, err := CreateKeyFromBytes(make([]byte, 32), KCVMethodECB)
if err != nil {
t.Fatalf("failed to create AES-256 key: %v", err)
}
if len(key.KeyBytes) != 32 {
t.Fatalf("expected 32 bytes but got %d", len(key.KeyBytes))
}
}

func TestCreateKeyFromStringValid(t *testing.T) {
key, err := CreateKeyFromString("702E73B9230ECADBB8F120BDE3870493", KCVMethodCMAC)
if err != nil {
t.Fatalf("failed to create key from hex string: %v", err)
}
if len(key.KeyBytes) != 16 {
t.Fatalf("expected 16 bytes but got %d", len(key.KeyBytes))
}
if key.kcvMethod != KCVMethodCMAC {
t.Fatal("expected CMAC KCV method")
}
}
87 changes: 87 additions & 0 deletions aes/aes_key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
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 (
"encoding/hex"
"strings"
"testing"
)

const testKeyHex = "702E73B9230ECADBB8F120BDE3870493"

func mustDecodeHex(s string) []byte {
b, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return b
}

func TestECBCheckValue(t *testing.T) {
key, err := CreateKeyFromString(testKeyHex, KCVMethodECB)
if err != nil {
t.Fatalf("failed to create key: %v", err)
}

expected := "A3DB34"
if !strings.EqualFold(key.CheckValue(), expected) {
t.Fatalf("expected ECB KCV %s but got %s", expected, key.CheckValue())
}
}

func TestECBVerifyCheckValue(t *testing.T) {
key, err := CreateKeyFromString(testKeyHex, KCVMethodECB)
if err != nil {
t.Fatalf("failed to create key: %v", err)
}

if !key.VerifyCheckValue("A3DB34") {
t.Fatal("ECB KCV verification should pass")
}
if key.VerifyCheckValue("000000") {
t.Fatal("ECB KCV verification should fail with wrong value")
}
}

func TestVerifyCheckValueCaseInsensitive(t *testing.T) {
key, err := CreateKeyFromString(testKeyHex, KCVMethodECB)
if err != nil {
t.Fatalf("failed to create key: %v", err)
}

if !key.VerifyCheckValue("a3db34") {
t.Fatal("KCV verification should be case insensitive")
}
}

func TestVerifyCheckValueTooShort(t *testing.T) {
key, err := CreateKeyFromString(testKeyHex, KCVMethodECB)
if err != nil {
t.Fatalf("failed to create key: %v", err)
}

if key.VerifyCheckValue("A3") {
t.Fatal("KCV with less than 2 bytes should fail")
}
}

func TestGetKeyBytes(t *testing.T) {
key, err := CreateKeyFromBytes(mustDecodeHex(testKeyHex), KCVMethodECB)
if err != nil {
t.Fatalf("failed to create key: %v", err)
}

if len(key.GetKeyBytes()) != 16 {
t.Fatalf("expected 16 bytes but got %d", len(key.GetKeyBytes()))
}
}
4 changes: 4 additions & 0 deletions des/des_cipher.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ func (cipher *Cipher) VerifyCheckValue(checkValue string) bool {
return strings.EqualFold(derivedCheckValue, checkValue)
}

func (cipher *Cipher) GetKeyBytes() []byte {
return cipher.KeyBytes
}

func (cipher *Cipher) CheckValue() string {
cipherBytes, err := cipher.Encrypt(keyCheckValuePlainText8Bytes)
if err != nil {
Expand Down
Loading
Loading