major architecture refactor

This commit is contained in:
juancwu 2026-01-23 03:10:53 +00:00
commit f82d06dbf0
11 changed files with 636 additions and 272 deletions

View file

@ -0,0 +1,98 @@
package security
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"io"
"golang.org/x/crypto/scrypt"
)
const (
scryptN = 32768
scryptR = 8
scryptP = 1
scryptKeyLen = 32
)
// Encrypt takes a plain text string and a password, returning a base64 encoded string
// containing the salt, nonce, and ciphertext.
func Encrypt(plainText, password string) (string, error) {
salt := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
return "", fmt.Errorf("failed to generate salt: %w", err)
}
key, err := scrypt.Key([]byte(password), salt, scryptN, scryptR, scryptP, scryptKeyLen)
if err != nil {
return "", fmt.Errorf("failed to derive key: %w", err)
}
block, err := aes.NewCipher(key)
if err != nil {
return "", fmt.Errorf("failed to create cipher: %w", err)
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", fmt.Errorf("failed to create GCM: %w", err)
}
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", fmt.Errorf("failed to generate nonce: %w", err)
}
cipherText := gcm.Seal(nonce, nonce, []byte(plainText), nil)
combined := append(salt, cipherText...)
return base64.StdEncoding.EncodeToString(combined), nil
}
// Decrypt takes the base64 encoded string and password, returning the plain text.
func Decrypt(encodedData, password string) (string, error) {
data, err := base64.StdEncoding.DecodeString(encodedData)
if err != nil {
return "", fmt.Errorf("failed to decode base64: %w", err)
}
if len(data) < 16+12 { // 16 salt + 12 nonce (min)
return "", errors.New("invalid data length")
}
salt := data[:16]
cipherTextWithNonce := data[16:]
key, err := scrypt.Key([]byte(password), salt, scryptN, scryptR, scryptP, scryptKeyLen)
if err != nil {
return "", fmt.Errorf("failed to derive key: %w", err)
}
block, err := aes.NewCipher(key)
if err != nil {
return "", fmt.Errorf("failed to create cipher: %w", err)
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", fmt.Errorf("failed to create GCM: %w", err)
}
nonceSize := gcm.NonceSize()
if len(cipherTextWithNonce) < nonceSize {
return "", errors.New("ciphertext too short")
}
nonce, actualCipherText := cipherTextWithNonce[:nonceSize], cipherTextWithNonce[nonceSize:]
plainText, err := gcm.Open(nil, nonce, actualCipherText, nil)
if err != nil {
return "", errors.New("invalid password or corrupted data")
}
return string(plainText), nil
}