From 84bd8c3a42633664e56dd9aeaef163dfcbddfa75 Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Sat, 3 May 2025 17:13:09 -0400 Subject: [PATCH] Initial encryption/decryption via ChaCha20-Poly1305 --- example.go | 26 ++++++++++++++-- go.mod | 1 + go.sum | 2 ++ wrappers/chacha.go | 71 ++++++++++++++++++++++++++++++++++++++++++++ wrappers/keyDeriv.go | 19 ++++++++++++ 5 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 wrappers/chacha.go create mode 100644 wrappers/keyDeriv.go diff --git a/example.go b/example.go index 8b01558..c88d84f 100644 --- a/example.go +++ b/example.go @@ -4,12 +4,34 @@ import ( "fmt" "os" "rcw/daemon" + "rcw/wrappers" ) +// This sample program serves purley as a way to interactively test the features +// of RCW before building it into your own application. +// +// Usage: +// rcw : Runs the rcw daemon to serve the provided text for three minutes +// rcw : Requests the data served by the RCW daemon and outputs it to stdout +// 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 + func main() { - if len(os.Args) > 1 { + switch len(os.Args) { + case 2: + // serve data daemon.Start(os.Args[1]) - } else { + case 3: + // decrypt file + encBytes, _ := os.ReadFile("encrypted-example.txt") + decBytes := wrappers.DecryptCha(encBytes, []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])) + os.WriteFile("encrypted-example.txt", encBytes, 0644) + default: + // request served data fmt.Println(daemon.Call()) } } diff --git a/go.mod b/go.mod index 089c1f3..2a837d3 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require github.com/rwinkhart/peercred-mini v0.0.0-20250407033241-c09add2eceea require ( github.com/Microsoft/go-winio v0.6.2 + golang.org/x/crypto v0.37.0 golang.org/x/sys v0.32.0 ) diff --git a/go.sum b/go.sum index d899f8d..8a13cca 100644 --- a/go.sum +++ b/go.sum @@ -4,3 +4,5 @@ github.com/rwinkhart/peercred-mini v0.0.0-20250407033241-c09add2eceea h1:VE2ti/A github.com/rwinkhart/peercred-mini v0.0.0-20250407033241-c09add2eceea/go.mod h1:t+YkvAdnTKTrg4d469tw3K+GCUzX/Bja4h8yKjSIsGs= github.com/rwinkhart/sys-freebsd-13-xucred v0.32.0 h1:KRbqimv9Eexf3VB2FrRAQ4v2fGGu7gt3ayMgdT0FNao= github.com/rwinkhart/sys-freebsd-13-xucred v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= diff --git a/wrappers/chacha.go b/wrappers/chacha.go new file mode 100644 index 0000000..653d1bd --- /dev/null +++ b/wrappers/chacha.go @@ -0,0 +1,71 @@ +package wrappers + +import ( + "crypto/rand" + "io" + "log" + + "golang.org/x/crypto/chacha20poly1305" +) + +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) + io.ReadFull(rand.Reader, salt) + + // derive key from passphrase using the salt + // TODO ensure the passphrase is consistent (store a hashed version to compare against) + key := deriveKey(passphrase, salt) + + // create ChaCha20-Poly1305 cipher + aead, _ := 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) + + // format: salt + nonce + ciphertext + result := make([]byte, 0, saltSizeCha+nonceSizeCha+len(ciphertext)) + result = append(result, salt...) + result = append(result, nonce...) + result = append(result, ciphertext...) + + return result +} + +// DecryptCha decrypts data using ChaCha20-Poly1305 +func DecryptCha(encryptedData []byte, passphrase []byte) []byte { + if len(encryptedData) < saltSizeCha+nonceSizeCha { + log.Fatalf("encrypted data is too short") + return nil + } + + // extract salt, nonce, and ciphertext + salt := encryptedData[:saltSizeCha] + nonce := encryptedData[saltSizeCha : saltSizeCha+nonceSizeCha] + ciphertext := encryptedData[saltSizeCha+nonceSizeCha:] + + // derive key from passphrase using the salt + key := deriveKey(passphrase, salt) + + // create ChaCha20-Poly1305 cipher + aead, _ := chacha20poly1305.NewX(key) + + // decrypt the data + plaintext, err := aead.Open(nil, nonce, ciphertext, nil) + if err != nil { + log.Fatalf("decryption failed (possibly wrong passphrase): %w", err) + return nil + } + + return plaintext +} diff --git a/wrappers/keyDeriv.go b/wrappers/keyDeriv.go new file mode 100644 index 0000000..a0173ee --- /dev/null +++ b/wrappers/keyDeriv.go @@ -0,0 +1,19 @@ +package wrappers + +import ( + "golang.org/x/crypto/argon2" + "golang.org/x/crypto/chacha20poly1305" +) + +// parameters for Argon2 +const ( + argonTime = 1 + argonMemory = 64 * 1024 + argonThreads = 4 + argonKeyLen = chacha20poly1305.KeySize +) + +// DeriveKey derives an encryption key from a passphrase using Argon2. +func deriveKey(passphrase []byte, salt []byte) []byte { + return argon2.IDKey(passphrase, salt, argonTime, argonMemory, argonThreads, argonKeyLen) +}