diff --git a/CLAUDE.md b/CLAUDE.md index 14f8cc72..c5d2a88a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -101,9 +101,9 @@ type Crypter interface { ### Data Flow 1. User submits message + PIN via web UI or API 2. Server validates input (PIN size, expiration limits) -3. MessageProc hashes PIN with bcrypt, encrypts message data -4. Encrypted message saved to storage engine with UUID key and expiration -5. For retrieval: validate PIN attempts, decrypt if correct, delete after successful read +3. MessageProc hashes PIN with bcrypt; API messages are encrypted server-side, UI messages arrive already encrypted by the browser and are stored as-is +4. Encrypted message saved to storage engine with 12-character base62 key and expiration +5. For retrieval: validate PIN attempts, then delete and return; API messages are decrypted server-side first, UI messages are handed back as stored ciphertext for the browser to decrypt ### File Message Format File messages use a distinct storage format with encrypted metadata: @@ -183,7 +183,7 @@ When `--allow-no-pin` / `ALLOW_NO_PIN` is enabled, secrets can be created withou - **rest.RealIP middleware** - Extracts client IP from headers for CDN/proxy compatibility (from go-pkgz/rest v1.20.6+) - Header priority: X-Real-IP → CF-Connecting-IP → leftmost public IP in X-Forwarded-For → RemoteAddr - Filters private/loopback/link-local IPs automatically -- **HashedIP middleware** - Anonymizes client IP using HMAC-SHA1 hash (12-char hex) for audit logging +- **HashedIP middleware** - Anonymizes client IP using HMAC-SHA256 hash (8-char hex) for audit logging - Must run after rest.RealIP middleware (reads `r.RemoteAddr` set by RealIP) - **Logger middleware** - Logs requests with masked sensitive paths (PINs) and anonymized IPs - Must run after HashedIP middleware (reads hashed IP from context) diff --git a/README.md b/README.md index 9238969f..1249fc47 100644 --- a/README.md +++ b/README.md @@ -84,11 +84,11 @@ The service uses **hybrid encryption** based on how you access it: ### Security Architecture **Encryption:** -- Server-side: AES-256-GCM for message encryption +- Server-side: NaCl secretbox (XSalsa20-Poly1305) for message encryption - Client-side (web UI): AES-128-GCM via Web Crypto API -- PIN hashing: bcrypt (cost 14) -- Random key generation: 32-byte cryptographically secure -- Server rejects unencrypted content from web clients (ciphertext format validation) +- PIN hashing: bcrypt (`bcrypt.DefaultCost`, currently 10) +- Random generation: 128-bit client keys and 12-character base62 message IDs, both from a CSPRNG +- Server checks that content from web clients has the shape of a ciphertext envelope (base64url, at least IV plus tag) **HTTP Security Headers:** - `Content-Security-Policy`: restricts scripts, styles, fonts to trusted sources; `frame-ancestors 'none'`; `form-action 'self'` @@ -384,7 +384,7 @@ $ curl -X POST https://safesecret.info/api/v1/message \ { "exp": "2024-01-15T10:30:00Z", - "key": "f1acfe04-277f-4016-518d-16c312ab84b5" + "key": "u2yKL07dDtRR" } ``` @@ -395,10 +395,10 @@ GET /api/v1/message/:key/:pin ``` ```bash -$ curl https://safesecret.info/api/v1/message/f1acfe04-277f-4016-518d-16c312ab84b5/12345 +$ curl https://safesecret.info/api/v1/message/u2yKL07dDtRR/12345 { - "key": "f1acfe04-277f-4016-518d-16c312ab84b5", + "key": "u2yKL07dDtRR", "message": "my secret" } ``` diff --git a/app/messager/crypt.go b/app/messager/crypt.go index 876bf7a5..1b3bbd39 100644 --- a/app/messager/crypt.go +++ b/app/messager/crypt.go @@ -68,7 +68,7 @@ func (c Crypt) Decrypt(req Request) ([]byte, error) { return decrypted, nil } -// MakeSignKey creates 32-pin bytes signKey for AES256 +// MakeSignKey creates 32-pin bytes signKey for secretbox func MakeSignKey(signKey string, pinSize int) (result string) { if len(signKey) >= 32-pinSize { return signKey[:32-pinSize]