From 146901a479f61d19a8d5ed40e18357fd9c6c4e71 Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Sat, 3 May 2025 18:13:40 -0400 Subject: [PATCH] Implement AES256+GCM encryption/decryption (cascading) --- example.go | 17 +++++++++- wrappers/aes.go | 75 ++++++++++++++++++++++++++++++++++++++++++++ wrappers/chacha.go | 21 ++++++------- wrappers/keyDeriv.go | 1 + 4 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 wrappers/aes.go diff --git a/example.go b/example.go index c88d84f..6d0c12b 100644 --- a/example.go +++ b/example.go @@ -16,6 +16,19 @@ import ( // rcw enc : Encrypts the provided text and outputs the ciphertext to encrypted-example.txt // rcw dec : Decrypts the provided file and outputs the plaintext to stdout +// TODO Tests: +// Salt (aes+chacha) +// Nonce (aes+chacha) +// Encryption (individual+combined) +// Decryption (individual+combined) +// RPC password sharing + +// TODO Enhancements: +// Keyfile: +// Store: +// Hash of passphrase (prevent user from losing data by accidentally providing incorrect passphrase during encryption) +// Order of algorithms (determined randomly at keyfile generation) + func main() { switch len(os.Args) { case 2: @@ -25,10 +38,12 @@ func main() { // decrypt file encBytes, _ := os.ReadFile("encrypted-example.txt") decBytes := wrappers.DecryptCha(encBytes, []byte(os.Args[2])) + decBytes = wrappers.DecryptAES(decBytes, []byte(os.Args[2])) fmt.Println(string(decBytes)) case 4: // encrypt data (from cli args) - encBytes := wrappers.EncryptCha([]byte(os.Args[2]), []byte(os.Args[3])) + encBytes := wrappers.EncryptAES([]byte(os.Args[2]), []byte(os.Args[3])) + encBytes = wrappers.EncryptCha(encBytes, []byte(os.Args[3])) os.WriteFile("encrypted-example.txt", encBytes, 0644) default: // request served data diff --git a/wrappers/aes.go b/wrappers/aes.go new file mode 100644 index 0000000..aa4bafa --- /dev/null +++ b/wrappers/aes.go @@ -0,0 +1,75 @@ +package wrappers + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "fmt" + "io" +) + +const ( + nonceSizeAES = 12 // GCM standard nonce size is 12 bytes +) + +// EncryptAES encrypts data using AES-256-GCM +func EncryptAES(data []byte, passphrase []byte) []byte { + // generate a random salt + salt := make([]byte, saltSize) + io.ReadFull(rand.Reader, salt) + + // derive key from passphrase using the salt + key := deriveKey(passphrase, salt) + + // create AES-256 cipher + block, _ := aes.NewCipher(key) + + // create GCM mode + aesGCM, _ := cipher.NewGCM(block) + + // generate a random nonce + nonce := make([]byte, nonceSizeAES) + io.ReadFull(rand.Reader, nonce) + + // encrypt the data + ciphertext := aesGCM.Seal(nil, nonce, data, nil) + + // format: salt + nonce + ciphertext + result := make([]byte, 0, saltSize+nonceSizeAES+len(ciphertext)) + result = append(result, salt...) + result = append(result, nonce...) + result = append(result, ciphertext...) + + return result +} + +// DecryptAES decrypts data using AES-256-GCM +func DecryptAES(encryptedData []byte, passphrase []byte) []byte { + if len(encryptedData) < saltSize+nonceSizeAES { + fmt.Println("Encrypted data is too short") + return nil + } + + // extract salt, nonce, and ciphertext + salt := encryptedData[:saltSize] + nonce := encryptedData[saltSize : saltSize+nonceSizeAES] + ciphertext := encryptedData[saltSize+nonceSizeAES:] + + // derive key from passphrase using the salt + key := deriveKey(passphrase, salt) + + // create AES-256 cipher + block, _ := aes.NewCipher(key) + + // create GCM mode + aesGCM, _ := cipher.NewGCM(block) + + // decrypt the data + plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil) + if err != nil { + fmt.Printf("Decryption failed (possibly wrong passphrase): %s", err.Error()) + return nil + } + + return plaintext +} diff --git a/wrappers/chacha.go b/wrappers/chacha.go index 101049a..bdb4012 100644 --- a/wrappers/chacha.go +++ b/wrappers/chacha.go @@ -10,13 +10,12 @@ import ( const ( nonceSizeCha = chacha20poly1305.NonceSizeX - saltSizeCha = 16 ) // EncryptCha encrypts data using ChaCha20-Poly1305 func EncryptCha(data []byte, passphrase []byte) []byte { // generate a random salt - salt := make([]byte, saltSizeCha) + salt := make([]byte, saltSize) io.ReadFull(rand.Reader, salt) // derive key from passphrase using the salt @@ -24,17 +23,17 @@ func EncryptCha(data []byte, passphrase []byte) []byte { key := deriveKey(passphrase, salt) // create ChaCha20-Poly1305 cipher - aead, _ := chacha20poly1305.NewX(key) + stream, _ := chacha20poly1305.NewX(key) // generate a random nonce nonce := make([]byte, nonceSizeCha) io.ReadFull(rand.Reader, nonce) // encrypt the data - ciphertext := aead.Seal(nil, nonce, data, nil) + ciphertext := stream.Seal(nil, nonce, data, nil) // format: salt + nonce + ciphertext - result := make([]byte, 0, saltSizeCha+nonceSizeCha+len(ciphertext)) + result := make([]byte, 0, saltSize+nonceSizeCha+len(ciphertext)) result = append(result, salt...) result = append(result, nonce...) result = append(result, ciphertext...) @@ -44,24 +43,24 @@ func EncryptCha(data []byte, passphrase []byte) []byte { // DecryptCha decrypts data using ChaCha20-Poly1305 func DecryptCha(encryptedData []byte, passphrase []byte) []byte { - if len(encryptedData) < saltSizeCha+nonceSizeCha { + if len(encryptedData) < saltSize+nonceSizeCha { fmt.Println("Encrypted data is too short") return nil } // extract salt, nonce, and ciphertext - salt := encryptedData[:saltSizeCha] - nonce := encryptedData[saltSizeCha : saltSizeCha+nonceSizeCha] - ciphertext := encryptedData[saltSizeCha+nonceSizeCha:] + salt := encryptedData[:saltSize] + nonce := encryptedData[saltSize : saltSize+nonceSizeCha] + ciphertext := encryptedData[saltSize+nonceSizeCha:] // derive key from passphrase using the salt key := deriveKey(passphrase, salt) // create ChaCha20-Poly1305 cipher - aead, _ := chacha20poly1305.NewX(key) + stream, _ := chacha20poly1305.NewX(key) // decrypt the data - plaintext, err := aead.Open(nil, nonce, ciphertext, nil) + plaintext, err := stream.Open(nil, nonce, ciphertext, nil) if err != nil { fmt.Printf("Decryption failed (possibly wrong passphrase): %s", err.Error()) return nil diff --git a/wrappers/keyDeriv.go b/wrappers/keyDeriv.go index a0173ee..7197e26 100644 --- a/wrappers/keyDeriv.go +++ b/wrappers/keyDeriv.go @@ -11,6 +11,7 @@ const ( argonMemory = 64 * 1024 argonThreads = 4 argonKeyLen = chacha20poly1305.KeySize + saltSize = 16 ) // DeriveKey derives an encryption key from a passphrase using Argon2.