package vault import ( "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/hex" "errors" "fmt" "strings" "golang.org/x/crypto/argon2" ) const ( argonTime = 3 argonMemory = 64 * 1024 argonThreads = 4 argonKeyLen = 32 ) func DeriveKey(password string, salt []byte) []byte { return argon2.IDKey([]byte(password), salt, argonTime, argonMemory, argonThreads, argonKeyLen) } func GenerateSalt() ([]byte, error) { salt := make([]byte, 32) if _, err := rand.Read(salt); err != nil { return nil, fmt.Errorf("generate salt: %w", err) } return salt, nil } type VaultService struct { key []byte } func NewVaultService(key []byte) *VaultService { return &VaultService{key: key} } func (v *VaultService) Encrypt(plaintext string) (string, error) { block, err := aes.NewCipher(v.key) if err != nil { return "", fmt.Errorf("create cipher: %w", err) } gcm, err := cipher.NewGCM(block) if err != nil { return "", fmt.Errorf("create GCM: %w", err) } iv := make([]byte, gcm.NonceSize()) if _, err := rand.Read(iv); err != nil { return "", fmt.Errorf("generate IV: %w", err) } sealed := gcm.Seal(nil, iv, []byte(plaintext), nil) return fmt.Sprintf("v1:%s:%s", hex.EncodeToString(iv), hex.EncodeToString(sealed)), nil } func (v *VaultService) Decrypt(encrypted string) (string, error) { parts := strings.SplitN(encrypted, ":", 3) if len(parts) != 3 || parts[0] != "v1" { return "", errors.New("invalid encrypted format: expected v1:{iv}:{sealed}") } iv, err := hex.DecodeString(parts[1]) if err != nil { return "", fmt.Errorf("decode IV: %w", err) } sealed, err := hex.DecodeString(parts[2]) if err != nil { return "", fmt.Errorf("decode sealed data: %w", err) } block, err := aes.NewCipher(v.key) if err != nil { return "", fmt.Errorf("create cipher: %w", err) } gcm, err := cipher.NewGCM(block) if err != nil { return "", fmt.Errorf("create GCM: %w", err) } plaintext, err := gcm.Open(nil, iv, sealed, nil) if err != nil { return "", fmt.Errorf("decrypt: %w", err) } return string(plaintext), nil }