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

Aead #475

Closed
wants to merge 9 commits into from
Closed

Aead #475

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
40 changes: 27 additions & 13 deletions cry/enc.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
cryptoRand "crypto/rand"
"encoding/base64"
"errors"
"fmt"
"runtime"
"slices"

Expand Down Expand Up @@ -55,6 +56,19 @@ type Enc struct {
key []byte
}

// String implements [fmt.Stringer]
func (e Enc) String() string {
if len(e.key) <= 0 {
return "Enc{key:<EMPTY>}"
}
return fmt.Sprintf("Enc{key:%s<REDACTED>}", string(e.key[0]))
}

// GoString implements [fmt.GoStringer]
func (e Enc) GoString() string {
return e.String()
}

// New returns a [cipher.AEAD]
//
// It panics on error.
Expand All @@ -75,7 +89,7 @@ func New(secretKey string) Enc {
derivedKey := deriveKey(password, salt)

/*
Another option would be to use argon2.
Another option would be to use scrypt.
import "golang.org/x/crypto/scrypt"
key := scrypt.Key("password", salt, 32768, 8, 1, keyLen)
*/
Expand All @@ -99,35 +113,38 @@ func (e Enc) Encrypt(plainTextMsg string) (encryptedMsg []byte) {
msgToEncrypt := []byte(plainTextMsg)

// Select a random nonce, and leave capacity for the ciphertext.
nonce := random(
e.aead.NonceSize(),
e.aead.NonceSize()+len(msgToEncrypt)+e.aead.Overhead(),
)
// https://github.com/golang/crypto/blob/v0.26.0/chacha20poly1305/chacha20poly1305_test.go#L222
// nonce := random(
// chacha20poly1305.NonceSizeX,
// chacha20poly1305.NonceSizeX+len(msgToEncrypt)+chacha20poly1305.Overhead,
// )
nonce := random(chacha20poly1305.NonceSizeX, chacha20poly1305.NonceSizeX)

// Encrypt the message and append the ciphertext to the nonce.
encrypted := e.aead.Seal(nonce, nonce, msgToEncrypt, nil)
encrypted := e.aead.Seal(nil, nonce, msgToEncrypt, nil)

encrypted = append(
// "you can send the nonce in the clear before each message; so long as it's unique." - agl
// see: https://crypto.stackexchange.com/a/5818
//
// "salt does not need to be secret."
// see: https://crypto.stackexchange.com/a/99502
e.salt,
nonce,
encrypted...,
)
encrypted = append(e.salt, encrypted...)

return encrypted
}

// Decrypt authenticates and un-encrypts the encryptedMsg using XChaCha20-Poly1305 and returns decrypted bytes.
func (e Enc) Decrypt(encryptedMsg []byte) (decryptedMsg []byte, err error) {
if len(encryptedMsg) < e.aead.NonceSize() {
if len(encryptedMsg) < chacha20poly1305.NonceSizeX {
return nil, errors.New("ong/cry: ciphertext too short")
}

// get salt
salt, encryptedMsg := encryptedMsg[:saltLen], encryptedMsg[saltLen:]
// get constituent parts
salt, nonce, ciphertext := encryptedMsg[:saltLen], encryptedMsg[saltLen:saltLen+chacha20poly1305.NonceSizeX], encryptedMsg[saltLen+chacha20poly1305.NonceSizeX:]

aead := e.aead
if !slices.Equal(salt, e.salt) {
Expand All @@ -141,9 +158,6 @@ func (e Enc) Decrypt(encryptedMsg []byte) (decryptedMsg []byte, err error) {
}
}

// Split nonce and ciphertext.
nonce, ciphertext := encryptedMsg[:aead.NonceSize()], encryptedMsg[aead.NonceSize():]

// Decrypt the message and check it wasn't tampered with.
return aead.Open(nil, nonce, ciphertext, nil)
}
Expand Down
41 changes: 41 additions & 0 deletions cry/enc_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package cry

import (
"os"
"slices"
"strings"
"sync"
"testing"

Expand Down Expand Up @@ -108,6 +110,45 @@
attest.Equal(t, string(decryptedMsg), msgToEncrypt)
})

t.Run("encrypt/decrypt file", func(t *testing.T) {
t.Parallel()

msgToEncrypt := ""
decryptedMsg := []byte{}

Check failure on line 117 in cry/enc_test.go

View workflow job for this annotation

GitHub Actions / run_analysis (>=1.23.0, ubuntu-22.04)

this value of decryptedMsg is never used (SA4006)
key := tst.SecretKey()

dir := t.TempDir()
originalFile := dir + "/originalFile.txt"
encryptedFile := dir + "/encryptedFile.txt.encrypted"

{
err := os.WriteFile(originalFile, []byte(strings.Repeat("h", (50*1024*1024))), 0o666) // 50MB
attest.Ok(t, err)
}

{
b, err := os.ReadFile(originalFile)
attest.Ok(t, err)
msgToEncrypt = string(b)

enc := New(key)
er := os.WriteFile(encryptedFile, enc.Encrypt(msgToEncrypt), 0o666)
attest.Ok(t, er)
}

{
b, err := os.ReadFile(encryptedFile)
attest.Ok(t, err)

enc := New(key)
msg, err := enc.Decrypt(b)
attest.Ok(t, err)
decryptedMsg = msg
}

attest.Equal(t, string(decryptedMsg), msgToEncrypt)
})

t.Run("concurrency safe", func(t *testing.T) {
t.Parallel()

Expand Down
16 changes: 16 additions & 0 deletions new.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
goos: linux
goarch: amd64
pkg: github.com/komuw/ong/cry
cpu: Intel(R) Core(TM) i5-6300U CPU @ 2.40GHz
BenchmarkEnc-4 240157 4805 ns/op 728 B/op 16 allocs/op
BenchmarkEnc-4 265201 4729 ns/op 728 B/op 16 allocs/op
BenchmarkEnc-4 255612 4406 ns/op 728 B/op 16 allocs/op
BenchmarkEnc-4 248343 4379 ns/op 728 B/op 16 allocs/op
BenchmarkEnc-4 262258 4572 ns/op 728 B/op 16 allocs/op
BenchmarkEnc-4 255978 4402 ns/op 728 B/op 16 allocs/op
BenchmarkEnc-4 247028 4410 ns/op 728 B/op 16 allocs/op
BenchmarkEnc-4 259168 4928 ns/op 728 B/op 16 allocs/op
BenchmarkEnc-4 225398 4771 ns/op 728 B/op 16 allocs/op
BenchmarkEnc-4 265327 4348 ns/op 728 B/op 16 allocs/op
PASS
ok github.com/komuw/ong/cry 13.779s
Loading