Skip to content

Commit

Permalink
Sun Apr 7 17:21:56 CST 2024
Browse files Browse the repository at this point in the history
Signed-off-by: yakumioto <[email protected]>
  • Loading branch information
yakumioto committed Apr 7, 2024
1 parent dc12f9b commit 43ba33b
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 0 deletions.
139 changes: 139 additions & 0 deletions chacha20/chacha20.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
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 {
key []byte
nonceSize int
algorithm types.Algorithm
}

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

Check warning on line 29 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L28-L29

Added lines #L28 - L29 were not covered by tests
}

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

Check warning on line 33 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L32-L33

Added lines #L32 - L33 were not covered by tests
}

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

Check warning on line 38 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L36-L38

Added lines #L36 - L38 were not covered by tests

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

Check warning on line 40 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L40

Added line #L40 was not covered by tests
}

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

Check warning on line 44 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L43-L44

Added lines #L43 - L44 were not covered by tests
}

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

Check warning on line 48 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L47-L48

Added lines #L47 - L48 were not covered by tests
}

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

Check warning on line 52 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L51-L52

Added lines #L51 - L52 were not covered by tests
}

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 58 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L58

Added line #L58 was not covered by tests
}

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

Check warning on line 63 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L63

Added line #L63 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 86 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L86

Added line #L86 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 92 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L92

Added line #L92 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 97 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L97

Added line #L97 was not covered by tests
}

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

Check warning on line 101 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L101

Added line #L101 was not covered by tests
}

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

aead, err := chacha20.NewUnauthenticatedCipher(k.key, 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 119 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L119

Added line #L119 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 129 in chacha20/chacha20.go

View check run for this annotation

Codecov / codecov/patch

chacha20/chacha20.go#L128-L129

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

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

return &KeyImpl[T]{
key: extendKey,
nonceSize: nonceSize,
algorithm: alg,
}, nil
}
38 changes: 38 additions & 0 deletions chacha20/chacha20_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package chacha20

import (
"testing"

"github.com/stretchr/testify/assert"

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

func TestEncryptAndDecrypt(t *testing.T) {
tcs := []struct {
algorithm types.Algorithm
}{
{
algorithm: types.Chacha20,
},
{
algorithm: types.XChacha20,
},
}

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

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

ct, err := key.Encrypt("hello world")
assert.NoErrorf(t, err, "Encrypt failed: %s", err)

t.Log(ct)

plaintext, err := key.Decrypt(ct)
assert.NoErrorf(t, err, "Decrypt failed: %s", err)
assert.Equal(t, "hello world", plaintext, "Decrypt failed")
}
}
2 changes: 2 additions & 0 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const (
AesGcm128 Algorithm = "aes_gcm_128"
AesGcm192 Algorithm = "aes_gcm_192"
AesGcm256 Algorithm = "aes_gcm_256"
Chacha20 Algorithm = "chacha20"
XChacha20 Algorithm = "x_chacha20"
)

// asymmetric algorithms type
Expand Down

0 comments on commit 43ba33b

Please sign in to comment.