package crypto import ( "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha256" "encoding/base64" "errors" "io" "golang.org/x/crypto/argon2" ) // DeriveKey derives a 256-bit key from password and salt using Argon2id. func DeriveKey(password []byte, salt []byte) []byte { return argon2.IDKey(password, salt, 3, 64*1024, 4, 32) } // KeyFromSecret derives a 256-bit AES key from a string secret (for token encryption). func KeyFromSecret(secret string) []byte { h := sha256.Sum256([]byte(secret)) return h[:] } // EncryptString encrypts a string and returns base64-encoded ciphertext. func EncryptString(plaintext string, key []byte) (string, error) { ct, err := Encrypt([]byte(plaintext), key) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(ct), nil } // DecryptString decrypts a base64-encoded ciphertext to a string. func DecryptString(encoded string, key []byte) (string, error) { ct, err := base64.StdEncoding.DecodeString(encoded) if err != nil { return "", err } pt, err := Decrypt(ct, key) if err != nil { return "", err } return string(pt), nil } // GenerateSalt returns a random 16-byte salt. func GenerateSalt() ([]byte, error) { salt := make([]byte, 16) _, err := io.ReadFull(rand.Reader, salt) return salt, err } // Encrypt encrypts plaintext with AES-256-GCM using the given key. func Encrypt(plaintext, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } gcm, err := cipher.NewGCM(block) if err != nil { return nil, err } nonce := make([]byte, gcm.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return nil, err } return gcm.Seal(nonce, nonce, plaintext, nil), nil } // Decrypt decrypts AES-256-GCM ciphertext using the given key. func Decrypt(ciphertext, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } gcm, err := cipher.NewGCM(block) if err != nil { return nil, err } nonceSize := gcm.NonceSize() if len(ciphertext) < nonceSize { return nil, errors.New("ciphertext too short") } nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] return gcm.Open(nil, nonce, ciphertext, nil) }