mirror of
https://github.com/sonr-io/crypto.git
synced 2026-09-17 01:26:23 +00:00
128 lines
8.0 KiB
Plaintext
128 lines
8.0 KiB
Plaintext
---
|
|||
|
|
title: Symmetric & Secrets
|
||
|
|
description: Bulk encryption, password-based key derivation, and the secret-hygiene helpers — how to pick between them and how they compose.
|
||
|
|
sidebar:
|
||
|
|
order: 1
|
||
|
|
icon: lock
|
||
|
|
---
|
||
|
|
|
||
|
|
This is the "boring" half of the library, and the half you will actually touch on every request path. Nothing here is generic over the elliptic-curve abstraction: every package on these pages takes and returns `[]byte`. That makes the layer easy to reason about and easy to misuse, so each page is explicit about the exact key sizes, ciphertext layouts, and constants involved.
|
||
|
|
|
||
|
|
The packages split into three jobs:
|
||
|
|
|
||
|
|
- **Encrypt bytes.** [`aead`](/symmetric/aead) is randomized AES-256-GCM — the default choice. [`daed`](/symmetric/deterministic-aead) is AES-SIV, a deterministic AEAD for the narrow cases where you need the same plaintext to produce the same ciphertext.
|
||
|
|
- **Turn a secret into a key.** [`argon2`](/symmetric/key-derivation) stretches a human password into key material and produces PHC-encoded password hashes. [`subtle`](/symmetric/key-derivation#subtle-hkdf-and-x25519) is the HKDF + X25519 layer for turning an already-high-entropy secret (a shared secret, a master key) into per-purpose subkeys.
|
||
|
|
- **Handle the secret carefully.** [`secure`](/symmetric/secrets), [`salt`](/symmetric/secrets#salt), [`password`](/symmetric/secrets#password), and [`subtle/random`](/symmetric/secrets#subtle-random) are small hygiene helpers: zeroization, constant-time comparison, salt generation and storage, password policy checks, and raw randomness.
|
||
|
|
|
||
|
|
## Pick one
|
||
|
|
|
||
|
|
| Your goal | Package | Entry point |
|
||
|
|
| --- | --- | --- |
|
||
|
|
| Encrypt a payload, blob, or message | `aead` | `NewAESGCM(key)` → `Encrypt(pt, aad)` |
|
||
|
|
| Encrypt a lookup key or identifier you must still be able to search by | `daed` | `NewAESSIV(key)` → `EncryptDeterministically` |
|
||
|
|
| Wrap key material with no nonce to manage | `daed` | `NewAESSIV(key)` |
|
||
|
|
| Derive an encryption key from a user's password | `argon2` | `New(DefaultConfig()).DeriveKey(pw, salt)` |
|
||
|
|
| Store a verifiable password hash | `argon2` | `HashPassword` / `VerifyPassword` |
|
||
|
|
| Split one master secret into several purpose-bound subkeys | `subtle` | `ComputeHKDF("SHA256", key, salt, info, 32)` |
|
||
|
|
| Agree on a key with a remote peer | `subtle` | X25519 trio → `ComputeHKDF` |
|
||
|
|
| Generate a salt | `salt` or `argon2` | `salt.GenerateDefault()` / `kdf.GenerateSalt()` |
|
||
|
|
| Generate raw random bytes | `secure` | `SecureRandom(buf)` |
|
||
|
|
| Compare two secrets without leaking timing | `argon2` | `CompareHashes(a, b)` |
|
||
|
|
| Zero a key out of memory after use | `secure` | `Zeroize(key)` |
|
||
|
|
| Enforce a password policy at signup | `password` | `NewValidator(nil).Validate(pw)` |
|
||
|
|
|
||
|
|
## How they compose
|
||
|
|
|
||
|
|
The realistic pipeline is: get entropy, stretch or expand it into a 32-byte key, encrypt with that key, then zero the key.
|
||
|
|
|
||
|
|
```go
|
||
|
|
package vault
|
||
|
|
|
||
|
|
import (
|
||
|
|
"github.com/sonr-io/crypto/aead"
|
||
|
|
"github.com/sonr-io/crypto/argon2"
|
||
|
|
"github.com/sonr-io/crypto/secure"
|
||
|
|
)
|
||
|
|
|
||
|
|
func sealWithPassword(password, plaintext, aad []byte) (key, salt, ct []byte, err error) {
|
||
|
|
kdf := argon2.New(argon2.DefaultConfig()) // Argon2id, 64 MiB, t=1, p=4
|
||
|
|
|
||
|
|
salt, err = kdf.GenerateSalt() // 32 bytes
|
||
|
|
if err != nil {
|
||
|
|
return nil, nil, nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
key = kdf.DeriveKey(password, salt) // 32 bytes == aead.KeySize
|
||
|
|
defer secure.Zeroize(key)
|
||
|
|
|
||
|
|
cipher, err := aead.NewAESGCM(key)
|
||
|
|
if err != nil {
|
||
|
|
return nil, nil, nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
ct, err = cipher.Encrypt(plaintext, aad) // nonce || ciphertext || tag
|
||
|
|
return key, salt, ct, err
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Two things make that snippet work, and both are worth internalizing:
|
||
|
|
|
||
|
|
1. `argon2.DefaultConfig().KeyLength` is `32`, and `aead.KeySize` is `32`. The KDF output plugs straight into the AEAD constructor with no truncation or padding.
|
||
|
|
2. `aead.Encrypt` generates its own nonce and prepends it, so the only thing you have to persist alongside the ciphertext is the salt.
|
||
|
|
|
||
|
|
:::tip[Choose the KDF by input entropy, not by habit]
|
||
|
|
`argon2` is deliberately slow and memory-hard because its input is a low-entropy human password. If your input is *already* a uniformly random 32-byte secret — an X25519 shared secret, a master key from an HSM, a threshold-signing output — use `subtle.ComputeHKDF` instead. Running Argon2 on high-entropy input buys you nothing but 64 MiB of allocation per call.
|
||
|
|
:::
|
||
|
|
|
||
|
|
## Sizes at a glance
|
||
|
|
|
||
|
|
Every one of these is a compile-time constant in the package named, not a default you can override. Getting a size wrong is a constructor error, never a silent truncation.
|
||
|
|
|
||
|
|
| Constant | Value | Where |
|
||
|
|
| --- | --- | --- |
|
||
|
|
| `aead.KeySize` | `32` | AES-256-GCM key — the only length accepted |
|
||
|
|
| `aead.NonceSize` | `12` | GCM nonce, generated and prepended by `Encrypt` |
|
||
|
|
| `aead.TagSize` | `16` | GCM authentication tag |
|
||
|
|
| `daed.AESSIVKeySize` | `64` | AES-SIV double-length key: 32-byte CMAC key ‖ 32-byte CTR key |
|
||
|
|
| `salt.DefaultSaltSize` | `32` | Recommended salt size |
|
||
|
|
| `salt.MinSaltSize` / `salt.MaxSaltSize` | `16` / `1024` | Hard bounds enforced by `salt.Generate` |
|
||
|
|
|
||
|
|
An `aead` ciphertext is therefore always `len(plaintext) + 28` bytes; an AES-SIV ciphertext is always `len(plaintext) + 16`. Neither carries a version byte or key identifier, so any framing you need is yours to add.
|
||
|
|
|
||
|
|
## What stays your responsibility
|
||
|
|
|
||
|
|
These packages cover the primitive, not the protocol. Everything below is out of scope for this layer and has to live in your application:
|
||
|
|
|
||
|
|
- **Key lifetime and rotation.** Nothing tracks how many messages a key has protected, and nothing versions a key. `aead` will happily encrypt forever under one key.
|
||
|
|
- **Salt and hash persistence.** `salt.SaltStore` is an in-memory map with no mutex; `argon2.HashPassword` is the only helper that packages a salt into something durable.
|
||
|
|
- **Replay and freshness.** AEAD authenticity says "this ciphertext was produced by someone holding the key", never "recently" or "once". Bind timestamps or counters into the AAD.
|
||
|
|
- **Rate limiting on password paths.** `argon2` makes each guess expensive; it does not make guessing impossible.
|
||
|
|
- **Unicode normalization** of anything a human types, before both validation and derivation.
|
||
|
|
|
||
|
|
## What this layer does not do
|
||
|
|
|
||
|
|
:::warning[Read the caveats on each page]
|
||
|
|
Several packages here are hand-rolled reimplementations of things the standard library already provides — `secure.SecureCompare`, `salt`'s internal `constantTimeCompare`, and `password.SecureCompare` are three separate copies of the same loop, none of which use `crypto/subtle`. `secure` does not lock pages into RAM. `password`'s entropy estimator is a length heuristic, not an entropy measurement. `argon2.EstimateTime` is a formula with no measurement behind it. Each page states exactly what the code does rather than what the name suggests.
|
||
|
|
:::
|
||
|
|
|
||
|
|
There is no key *storage* here — no keystore, no envelope format, no versioned header. `aead.Encrypt` hands you `nonce || ciphertext || tag` and nothing else; if you need an algorithm identifier or a key ID on the wire, you frame it yourself. Likewise there is no ChaCha20-Poly1305, no AES-128, and no streaming/chunked API: `aead` is AES-256-GCM one-shot only.
|
||
|
|
|
||
|
|
## Next
|
||
|
|
|
||
|
|
<CardGroup cols={2}>
|
||
|
|
<Card title="Randomized AEAD" href="/symmetric/aead" icon="lock-keyhole">
|
||
|
|
AES-256-GCM. The default for encrypting anything. Exact key size, nonce handling, and ciphertext layout.
|
||
|
|
</Card>
|
||
|
|
<Card title="Deterministic AEAD" href="/symmetric/deterministic-aead" icon="repeat">
|
||
|
|
AES-SIV with a 64-byte key. Nonce-free and misuse-resistant, at the cost of leaking plaintext equality.
|
||
|
|
</Card>
|
||
|
|
<Card title="Key derivation" href="/symmetric/key-derivation" icon="key-round">
|
||
|
|
Argon2id presets with exact parameters, PHC hash encoding, HKDF, and the X25519 ECDH trio.
|
||
|
|
</Card>
|
||
|
|
<Card title="Secret hygiene" href="/symmetric/secrets" icon="eye-off">
|
||
|
|
Zeroization, salts and the salt store, password policy, randomness — and what each one really guarantees.
|
||
|
|
</Card>
|
||
|
|
</CardGroup>
|
||
|
|
|
||
|
|
Everything outside this layer — signatures, threshold protocols, zero-knowledge proofs — is generic over the curve abstraction described in [Foundations: curves](/foundations/curves).
|