From 420030998fdb74f155aa03156f6ce7524120e4bd Mon Sep 17 00:00:00 2001 From: Zack Scholl Date: Wed, 21 Apr 2021 16:53:29 -0700 Subject: [PATCH] add chacha20 --- src/crypt/crypt.go | 61 +++++++++++++++++++++++++++++++++++++++++ src/crypt/crypt_test.go | 50 +++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/src/crypt/crypt.go b/src/crypt/crypt.go index 3991e567..d33a4d8c 100644 --- a/src/crypt/crypt.go +++ b/src/crypt/crypt.go @@ -8,6 +8,8 @@ import ( "fmt" "log" + "golang.org/x/crypto/argon2" + "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/pbkdf2" ) @@ -70,3 +72,62 @@ func Decrypt(encrypted []byte, key []byte) (plaintext []byte, err error) { plaintext, err = aesgcm.Open(nil, encrypted[:12], encrypted[12:], nil) return } + +// NewArgon2 generates a new key based on a passphrase and salt +// using argon2 +// https://pkg.go.dev/golang.org/x/crypto/argon2 +func NewArgon2(passphrase []byte, usersalt []byte) (key []byte, salt []byte, err error) { + if len(passphrase) < 1 { + err = fmt.Errorf("need more than that for passphrase") + return + } + if usersalt == nil { + salt = make([]byte, 8) + // http://www.ietf.org/rfc/rfc2898.txt + // Salt. + if _, err := rand.Read(salt); err != nil { + log.Fatalf("can't get random salt: %v", err) + } + } else { + salt = usersalt + } + key = argon2.IDKey(passphrase, salt, 1, 64*1024, 4, 32) + return +} + +// EncryptChaCha will encrypt ChaCha20-Poly1305 using the pre-generated key +// https://pkg.go.dev/golang.org/x/crypto/chacha20poly1305 +func EncryptChaCha(plaintext []byte, key []byte) (encrypted []byte, err error) { + aead, err := chacha20poly1305.NewX(key) + if err != nil { + return + } + nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(plaintext)+aead.Overhead()) + if _, err := rand.Read(nonce); err != nil { + panic(err) + } + + // Encrypt the message and append the ciphertext to the nonce. + encrypted = aead.Seal(nonce, nonce, plaintext, nil) + return +} + +// DecryptChaCha will encrypt ChaCha20-Poly1305 using the pre-generated key +// https://pkg.go.dev/golang.org/x/crypto/chacha20poly1305 +func DecryptChaCha(encryptedMsg []byte, key []byte) (encrypted []byte, err error) { + aead, err := chacha20poly1305.NewX(key) + if err != nil { + return + } + if len(encryptedMsg) < aead.NonceSize() { + err = fmt.Errorf("ciphertext too short") + return + } + + // Split nonce and ciphertext. + nonce, ciphertext := encryptedMsg[:aead.NonceSize()], encryptedMsg[aead.NonceSize():] + + // Decrypt the message and check it wasn't tampered with. + encrypted, err = aead.Open(nil, nonce, ciphertext, nil) + return +} diff --git a/src/crypt/crypt_test.go b/src/crypt/crypt_test.go index 54331682..d47dd4c4 100644 --- a/src/crypt/crypt_test.go +++ b/src/crypt/crypt_test.go @@ -1,6 +1,7 @@ package crypt import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -23,6 +24,23 @@ func BenchmarkDecrypt(b *testing.B) { } } +func BenchmarkEncryptChaCha(b *testing.B) { + bob, _, _ := NewArgon2([]byte("password"), nil) + for i := 0; i < b.N; i++ { + EncryptChaCha([]byte("hello, world"), bob) + } +} + +func BenchmarkDecryptChaCha(b *testing.B) { + key, _, _ := NewArgon2([]byte("password"), nil) + msg := []byte("hello, world") + enc, _ := EncryptChaCha(msg, key) + b.ResetTimer() + for i := 0; i < b.N; i++ { + DecryptChaCha(enc, key) + } +} + func TestEncryption(t *testing.T) { key, salt, err := New([]byte("password"), nil) assert.Nil(t, err) @@ -53,3 +71,35 @@ func TestEncryption(t *testing.T) { _, _, err = New([]byte(""), nil) assert.NotNil(t, err) } + +func TestEncryptionChaCha(t *testing.T) { + key, salt, err := NewArgon2([]byte("password"), nil) + fmt.Printf("key: %x\n", key) + assert.Nil(t, err) + msg := []byte("hello, world") + enc, err := EncryptChaCha(msg, key) + assert.Nil(t, err) + dec, err := DecryptChaCha(enc, key) + assert.Nil(t, err) + assert.Equal(t, msg, dec) + + // check reusing the salt + key2, _, err := NewArgon2([]byte("password"), salt) + dec, err = DecryptChaCha(enc, key2) + assert.Nil(t, err) + assert.Equal(t, msg, dec) + + // check reusing the salt + key2, _, err = NewArgon2([]byte("wrong password"), salt) + dec, err = DecryptChaCha(enc, key2) + assert.NotNil(t, err) + assert.NotEqual(t, msg, dec) + + // error with no password + dec, err = DecryptChaCha([]byte(""), key) + assert.NotNil(t, err) + + // error with small password + _, _, err = NewArgon2([]byte(""), nil) + assert.NotNil(t, err) +}