# Secret Value Manager in Go

- **URL:** https://isaacfei.com/posts/secret-value-manager-in-go
- **Date:** 2025-01-03
- **Tags:** Go, Cryptography
- **Description:** Build a simple secret value manager in Go.

---

## Core Components

The encryption system consists of three main components:

1. Passphrase management (digesting and verification)
2. Secret encryption
3. Secret decryption

## Passphrase Management

The master passphrase is never stored directly. Instead, we store a digest created using PBKDF2:

```go
// Constants for cryptographic operations
const saltLength int = 32        // Length of salt in bytes for key derivation
const secretKeyLength int = 32   // Length of derived key (256 bits for AES-256)
const separator string = "-"     // Separator for components in stored values

func DigestPassphrase(passphrase string) string {
    // Derive a key and get a salt (nil means generate new salt)
    key, salt := deriveKey(passphrase, nil)
    
    // Store as: <derived_key>-<salt>
    // Both components are hex-encoded for safe storage
    digestedPassphrase := strings.Join(
        []string{hex.EncodeToString(key), hex.EncodeToString(salt)},
        separator,
    )
    return digestedPassphrase
}
```

The key derivation function uses PBKDF2 with these specific parameters:

- SHA-256 as the hash function
- 100,000 iterations
- 32-byte output key
- 32-byte random salt

```go
func deriveKey(passphrase string, salt []byte) ([]byte, []byte) {
    // Generate a random salt if none provided
    if salt == nil {
        salt = make([]byte, saltLength)
        // Use crypto/rand for cryptographically secure random numbers
        rand.Read(salt)
    }

    // Use PBKDF2 to derive a key from the passphrase
    // - passphrase: the input secret
    // - salt: prevents rainbow table attacks
    // - 100000: iteration count for computational cost
    // - secretKeyLength: final key length (32 bytes = 256 bits)
    // - sha256.New: use SHA-256 as the hash function
    key := pbkdf2.Key(
        []byte(passphrase),
        salt,
        100000,
        secretKeyLength,
        sha256.New,
    )
    return key, salt
}
```

When verifying a passphrase, we:

1. Split the stored digest to get the original key and salt
2. Derive a new key using the same salt
3. Compare the keys

```go
func VerifyPassphrase(passphrase string, digestedPassphrase string) bool {
    // Split stored value into key and salt components
    parts := strings.Split(digestedPassphrase, separator)
    if len(parts) != 2 {
        return false // Invalid format, verification fails
    }

    // Extract the stored key and salt
    groundTruthKey := parts[0]  // The original derived key
    salt, _ := hex.DecodeString(parts[1])  // Decode stored salt from hex
    
    // Derive a new key using the same salt and passphrase
    key, _ := deriveKey(passphrase, salt)
    // Compare the newly derived key with the stored one
    return hex.EncodeToString(key) == groundTruthKey
}
```

## Secret Encryption

Each secret is encrypted using AES-GCM. The encrypted value format is:
`<ciphertext>-<salt>-<iv>`

```go
const ivLength int = 12  // 12 bytes is optimal for GCM mode (96 bits)

func Encrypt(passphrase string, value string) (string, error) {
    // For each encryption:
    // 1. Generate a new key using a fresh salt
    // 2. Generate a new IV (nonce) for GCM mode
    key, salt := deriveKey(passphrase, nil)
    iv := make([]byte, ivLength)
    rand.Read(iv)  // Cryptographically secure random IV

    // Create AES cipher in GCM mode:
    // 1. Create AES cipher with our derived key
    // 2. Wrap it in GCM mode for authenticated encryption
    blockCipher, _ := aes.NewCipher(key)
    gcmCipher, _ := cipher.NewGCM(blockCipher)

    // Encrypt and authenticate in one step
    // - nil: no additional authenticated data
    // - iv: nonce for GCM mode
    // - value: the secret to encrypt
    ciphertext := gcmCipher.Seal(nil, iv, []byte(value), nil)

    // Store as: <ciphertext>-<salt>-<iv>
    // All components are hex-encoded for safe storage
    encryptedValue := strings.Join(
        []string{
            hex.EncodeToString(ciphertext),
            hex.EncodeToString(salt),
            hex.EncodeToString(iv),
        },
        separator,
    )
    return encryptedValue, nil
}
```

Important aspects of the encryption:

- Each secret gets a unique salt for key derivation
- Each encryption uses a random 12-byte IV
- GCM mode provides authenticated encryption
- All components are hex-encoded for safe storage

## Secret Decryption

To decrypt a secret, we:

1. Split the encrypted value into its components
2. Decode from hex
3. Derive the same key using the stored salt
4. Decrypt and verify using AES-GCM

```go
func Decrypt(passphrase string, encryptedValue string) (string, error) {
    // Split stored value into its three components
    // Format: <ciphertext>-<salt>-<iv>
    parts := strings.Split(encryptedValue, separator)
    if len(parts) != 3 {
        return "", errors.New("invalid encrypted value format")
    }

    // Decode all components from their hex representation
    ciphertext, _ := hex.DecodeString(parts[0])  // The encrypted secret
    salt, _ := hex.DecodeString(parts[1])        // Salt used for key derivation
    iv, _ := hex.DecodeString(parts[2])          // IV used for encryption

    // Derive the same key using the stored salt
    // This will give us the same key used for encryption
    key, _ := deriveKey(passphrase, salt)

    // Set up decryption:
    // 1. Create AES cipher with the derived key
    // 2. Wrap in GCM mode for authenticated decryption
    blockCipher, _ := aes.NewCipher(key)
    gcmCipher, _ := cipher.NewGCM(blockCipher)
    
    // Decrypt and verify authenticity in one step
    // If authentication fails, returns an error
    decryptedValue, err := gcmCipher.Open(nil, iv, ciphertext, nil)
    if err != nil {
        return "", err
    }

    return string(decryptedValue), nil
}
```

## Security Properties

This implementation provides:

1. **Key Security**
   - Unique key for each secret (different salts)
   - High-cost key derivation (100,000 iterations)
   - No plaintext passphrase storage

2. **Encryption Security**
   - Authenticated encryption (GCM mode)
   - No IV reuse (random generation)
   - No padding attacks (GCM is streamlike)

3. **Storage Security**
   - Safe encoding (hex)
   - Clear component separation
   - Integrity protection

## References

- [My Source Code](https://github.com/Isaac-Fate/myst/blob/master/internal/crypto/crypto.go)
- [Helpful Gist](https://gist.github.com/tscholl2/dc7dc15dc132ea70a98e8542fefffa28)
- [PBKDF2 in Go](https://pkg.go.dev/golang.org/x/crypto/pbkdf2)
- [AES-GCM in Go](https://pkg.go.dev/crypto/cipher#NewGCM)