Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support chacha20 and xchacha20 #2

Merged
merged 2 commits into from
Apr 8, 2024
Merged
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions aes/aes.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ var (
)

type CbcKeyImpl[T types.DataType] struct {
key []byte
inputKey []byte
extendKey []byte
algorithm types.Algorithm
}

Expand All @@ -29,12 +30,12 @@ func (a *CbcKeyImpl[T]) Algorithm() types.Algorithm {
}

func (a *CbcKeyImpl[T]) Export() (key T, err error) {
return T(a.key), nil
return T(a.inputKey), nil
}

func (a *CbcKeyImpl[T]) SKI() T {
sha := sha256.New()
sha.Write(a.key)
sha.Write(a.inputKey)

return T(utils.ToHexString(sha.Sum(nil)))
}
Expand All @@ -59,7 +60,7 @@ func (a *CbcKeyImpl[T]) Encrypt(plaintext T) (T, error) {
return T(""), fmt.Errorf("aes-cbc: encrypt failed to generate random iv: %w", err)
}

block, err := aes.NewCipher(a.key)
block, err := aes.NewCipher(a.extendKey)
if err != nil {
return T(""), fmt.Errorf("aes-cbc: encrypt failed to create aes cipher: %w", err)
}
Expand Down Expand Up @@ -105,7 +106,7 @@ func (a *CbcKeyImpl[T]) Decrypt(ciphertext T) (T, error) {
iv := encryptedPayload[:aes.BlockSize]
ciphertextBytes := encryptedPayload[aes.BlockSize:]

block, err := aes.NewCipher(a.key)
block, err := aes.NewCipher(a.extendKey)
if err != nil {
return T(""), fmt.Errorf("aes-cbc: cipher creation error: %w", err)
}
Expand All @@ -122,7 +123,8 @@ func (a *CbcKeyImpl[T]) Decrypt(ciphertext T) (T, error) {
}

type GcmKeyImpl[T types.DataType] struct {
key []byte
inputKey []byte
extendKey []byte
algorithm types.Algorithm
}

Expand All @@ -131,12 +133,12 @@ func (a *GcmKeyImpl[T]) Algorithm() types.Algorithm {
}

func (a *GcmKeyImpl[T]) Export() (key T, err error) {
return T(a.key), nil
return T(a.inputKey), nil
}

func (a *GcmKeyImpl[T]) SKI() T {
sha := sha256.New()
sha.Write(a.key)
sha.Write(a.inputKey)

return T(utils.ToHexString(sha.Sum(nil)))
}
Expand All @@ -154,7 +156,7 @@ func (a *GcmKeyImpl[T]) Verify(_, _ T) (bool, error) {
}

func (a *GcmKeyImpl[T]) Encrypt(plaintext T) (T, error) {
block, err := aes.NewCipher(a.key)
block, err := aes.NewCipher(a.extendKey)
if err != nil {
return T(""), fmt.Errorf("aes-gcm: new aes cipher error: %w", err)
}
Expand Down Expand Up @@ -202,7 +204,7 @@ func (a *GcmKeyImpl[T]) Decrypt(ciphertext T) (T, error) {
return T(""), fmt.Errorf("aes-gcm: decrypt failed to decode base64: %w", err)
}

block, err := aes.NewCipher(a.key)
block, err := aes.NewCipher(a.extendKey)
if err != nil {
return T(""), fmt.Errorf("aes-gcm: new aes cipher error: %w", err)
}
Expand Down Expand Up @@ -250,9 +252,9 @@ func (a *KeyImportImpl[T]) KeyImport(raw interface{}, alg types.Algorithm, opts

switch alg {
case types.AesCbc128, types.AesCbc192, types.AesCbc256:
return &CbcKeyImpl[T]{algorithm: alg, key: extendKey}, nil
return &CbcKeyImpl[T]{algorithm: alg, inputKey: keyBytes, extendKey: extendKey}, nil
case types.AesGcm128, types.AesGcm192, types.AesGcm256:
return &GcmKeyImpl[T]{algorithm: alg, key: extendKey}, nil
return &GcmKeyImpl[T]{algorithm: alg, inputKey: keyBytes, extendKey: extendKey}, nil
default:
panic("unhandled default case")
}
Expand Down
100 changes: 100 additions & 0 deletions aes/aes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,106 @@ import (
"github.com/yakumioto/go-crypto-suite/types"
)

func TestAlgorithm(t *testing.T) {
tcs := []struct {
algorithm types.Algorithm
}{
{
algorithm: types.AesCbc128,
},
{
algorithm: types.AesGcm128,
},
}

for _, tc := range tcs {
ki := new(KeyImportImpl[string])

key, err := ki.KeyImport("123456", tc.algorithm)
assert.NoErrorf(t, err, "KeyImport failed: %s", err)

assert.Equal(t, tc.algorithm, key.Algorithm(), "Algorithm failed")
}
}

func TestExport(t *testing.T) {
tcs := []struct {
algorithm types.Algorithm
}{
{
algorithm: types.AesCbc128,
},
{
algorithm: types.AesGcm128,
},
}

for _, tc := range tcs {
ki := new(KeyImportImpl[string])

key, err := ki.KeyImport("123456", tc.algorithm)
assert.NoErrorf(t, err, "KeyImport failed: %s", err)

password, err := key.Export()
assert.NoErrorf(t, err, "Export failed: %s", err)
assert.Equal(t, "123456", password, "Export failed")
}
}

func TestSKI(t *testing.T) {
tcs := []struct {
algorithm types.Algorithm
}{
{
algorithm: types.AesCbc128,
},
{
algorithm: types.AesGcm128,
},
}

for _, tc := range tcs {
ki := new(KeyImportImpl[string])

key, err := ki.KeyImport("123456", tc.algorithm)
assert.NoErrorf(t, err, "KeyImport failed: %s", err)

assert.Equal(t, "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", key.SKI(), "SKI failed")
}
}

func TestUnsupportedMethod(t *testing.T) {
tcs := []struct {
algorithm types.Algorithm
}{
{
algorithm: types.AesCbc128,
},
{
algorithm: types.AesGcm128,
},
}

for _, tc := range tcs {
ki := new(KeyImportImpl[string])

key, err := ki.KeyImport("123456", tc.algorithm)
assert.NoErrorf(t, err, "KeyImport failed: %s", err)

_, err = key.PublicKey()
assert.EqualError(t, err, ErrUnsupportedMethod.Error(), "PublicKey failed")

err = nil
_, err = key.Sign("hello world")
assert.EqualError(t, err, ErrUnsupportedMethod.Error(), "Sign failed")

err = nil
_, err = key.Verify("hello world", "signature")
assert.EqualError(t, err, ErrUnsupportedMethod.Error(), "Verify failed")

}
}

func TestEncryptAndDecrypt(t *testing.T) {
tcs := []struct {
algorithm types.Algorithm
Expand Down
141 changes: 141 additions & 0 deletions chacha20/chacha20.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package chacha20

import (
"bytes"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"strings"

"golang.org/x/crypto/chacha20"

"github.com/yakumioto/go-crypto-suite/key"
"github.com/yakumioto/go-crypto-suite/types"
"github.com/yakumioto/go-crypto-suite/utils"
)

var (
ErrUnsupportedMethod = errors.New("chacha20: unsupported method")
)

type KeyImpl[T types.DataType] struct {
inputKey []byte
expendKey []byte
nonceSize int
algorithm types.Algorithm
}

func (k *KeyImpl[T]) Algorithm() types.Algorithm {
return k.algorithm
}

func (k *KeyImpl[T]) Export() (key T, err error) {
return T(k.inputKey), nil
}

func (k *KeyImpl[T]) SKI() T {
sha := sha256.New()
sha.Write(k.inputKey)

return T(utils.ToHexString(sha.Sum(nil)))
}

func (k *KeyImpl[T]) PublicKey() (key.Key[T], error) {
return nil, ErrUnsupportedMethod
}

func (k *KeyImpl[T]) Sign(_ T) (signature T, err error) {
return T(""), ErrUnsupportedMethod
}

func (k *KeyImpl[T]) Verify(_, _ T) (bool, error) {
return false, ErrUnsupportedMethod
}

func (k *KeyImpl[T]) Encrypt(plaintext T) (ciphertext T, err error) {
nonce, err := utils.RandomSize(k.nonceSize)
if err != nil {
return T(""), fmt.Errorf("chacha20: encrypt failed to generate random nonce: %w", err)

Check warning on line 59 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L59

Added line #L59 was not covered by tests
}

aead, err := chacha20.NewUnauthenticatedCipher(k.expendKey, nonce)
if err != nil {
return T(""), fmt.Errorf("chacha20: encrypt failed to create cipher: %w", err)

Check warning on line 64 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L64

Added line #L64 was not covered by tests
}

plaintextBytes := utils.ToBytes(plaintext)
ciphertextBytes := make([]byte, len(plaintextBytes))
aead.XORKeyStream(ciphertextBytes, plaintextBytes)

payload := make([]byte, 0, len(nonce)+len(ciphertextBytes))
payload = append(payload, nonce...)
payload = append(payload, ciphertextBytes...)

data := bytes.NewBuffer(nil)
data.WriteString(k.algorithm)
data.WriteString(".")
data.WriteString(base64.RawStdEncoding.EncodeToString(payload))

return T(data.String()), nil
}

func (k *KeyImpl[T]) Decrypt(ciphertext T) (plaintext T, err error) {
dataBytes := utils.ToString(ciphertext)
parts := strings.SplitN(dataBytes, ".", 2)
if len(parts) != 2 {
return T(""), errors.New("chacha20: invalid encrypted data structure")

Check warning on line 87 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L87

Added line #L87 was not covered by tests
}

algorithm, payload := parts[0], parts[1]

if algorithm != k.algorithm {
return T(""), fmt.Errorf("chacha20: invalid algorithm type: %s", algorithm)

Check warning on line 93 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L93

Added line #L93 was not covered by tests
}

encryptedPayload, err := base64.RawStdEncoding.DecodeString(payload)
if err != nil {
return T(""), fmt.Errorf("chacha20: decrypt failed to decode base64: %w", err)

Check warning on line 98 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L98

Added line #L98 was not covered by tests
}

if len(encryptedPayload) < k.nonceSize {
return T(""), errors.New("chacha20: ciphertext too short")

Check warning on line 102 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L102

Added line #L102 was not covered by tests
}

nonce, ciphertextBytes := encryptedPayload[:k.nonceSize], encryptedPayload[k.nonceSize:]

aead, err := chacha20.NewUnauthenticatedCipher(k.expendKey, nonce)

plaintextBytes := make([]byte, len(ciphertextBytes))
aead.XORKeyStream(plaintextBytes, ciphertextBytes)

return T(plaintextBytes), nil
}

type KeyImportImpl[T types.DataType] struct{}

func (k *KeyImportImpl[T]) KeyImport(raw interface{}, alg types.Algorithm, opts ...key.Option[T]) (key.Key[T], error) {
keyBytes, err := utils.ToKeyBytes(raw)
if err != nil {
return nil, fmt.Errorf("chacha20: key import failed to convert key: %w", err)

Check warning on line 120 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L120

Added line #L120 was not covered by tests
}

var nonceSize int
switch alg {
case types.Chacha20:
nonceSize = chacha20.NonceSize
case types.XChacha20:
nonceSize = chacha20.NonceSizeX
default:
return nil, fmt.Errorf("chacha20: invalid algorithm: %v", alg)

Check warning on line 130 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L129-L130

Added lines #L129 - L130 were not covered by tests
}

extendKey := utils.ExtendKey(keyBytes, chacha20.KeySize)

return &KeyImpl[T]{
inputKey: keyBytes,
expendKey: extendKey,
nonceSize: nonceSize,
algorithm: alg,
}, nil
}
Loading
Loading