Complete RFC 9580 crypto modernization (Argon2 S2K + 256-bit keys)
Context
Thank you for the swift merge of #3364.
The migration to ProtonMail/go-crypto and the activation of AEAD (SEIPDv2/GCM) is a major security improvement. Yopass now uses modern authenticated encryption.
Two low-hanging improvements remain to fully align with the OpenPGP crypto refresh (RFC 9580) and maximize the cryptographic strength of Yopass:
- Argon2 for String-to-Key (S2K): replace the legacy iterated SHA-256 with memory-hard Argon2
- 256-bit encryption keys: increase from 131 bits (22 chars) to 256 bits (43 chars)
Both changes are backward-compatible: existing secrets remain readable, only new secrets benefit.
1. Argon2 for S2K key derivation
Current behavior
OpenPGP.js defaults to S2K type 3 (iterated and salted SHA-256, ~65 million iterations). This is CPU-only and theoretically vulnerable to GPU/ASIC bruteforce attacks, though Yopass's auto-generated keys mitigate this in practice.
Proposed change
Frontend: website/src/shared/lib/crypto.ts:
export const encryptionConfig: Partial<Config> = {
aeadProtect: true,
preferredAEADAlgorithm: enums.aead.gcm,
s2kType: enums.s2k.argon2, // ← add this
};
Server: pkg/yopass/yopass.go (for CLI --encrypt consistency):
import "github.com/ProtonMail/go-crypto/openpgp/s2k"
var pgpConfig = &packet.Config{
DefaultHash: crypto.SHA256,
DefaultCipher: packet.CipherAES256,
DefaultCompressionAlgo: packet.CompressionNone,
AEADConfig: &packet.AEADConfig{DefaultMode: packet.AEADModeGCM},
S2KConfig: &s2k.Config{S2KMode: s2k.Argon2S2K}, // ← add this
}
No change is needed for CLI --decrypt: ProtonMail/go-crypto v1.4.1 auto-detects the S2K type from the message and already supports Argon2 (S2K type 4).
Why?
| Property |
Iterated S2K (type 3) |
Argon2 (type 4) |
| Algorithm |
SHA-256, ~65M iterations |
Argon2id (memory-hard) |
| GPU/ASIC resistance |
Low: SHA-256 is fast on dedicated hardware |
High: memory-bound |
| RFC 9580 status |
Legacy |
Recommended |
| OpenPGP.js 6.x support |
✅ |
✅ |
| go-crypto v1.4.1 support |
✅ |
✅ |
This matters especially for users who supply custom passphrases via --key instead of using the auto-generated key, since custom passphrases typically have lower entropy.
2. Increase key entropy to 256 bits
Current behavior
GenerateKey() in pkg/yopass/yopass.go:
func GenerateKey() (string, error) {
const length = 22
b := make([]byte, length)
if _, err := rand.Read(b); err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b)[:length], nil
}
This generates 22 random bytes, base64url-encodes them, then truncates to 22 characters; yielding approximately 131 bits of entropy. While 131 bits is strong, it does not match the 256-bit AES key size.
Proposed change
func GenerateKey() (string, error) {
b := make([]byte, 32) // 256 bits
if _, err := rand.Read(b); err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(b), nil // 43 chars, no padding
}
And the corresponding test in yopass_test.go:
format := regexp.MustCompile("^[a-zA-Z0-9-_]{43}$")
Impact on URLs
Before (131 bits, 22 chars):
https://yopass.se/#/s/3ae57dce-16e8-4ee5-a09a-98341a84ffbc/aB3kXvTj96RMZXCJwPcn5a
After (256 bits, 43 chars):
https://yopass.se/#/s/3ae57dce-16e8-4ee5-a09a-98341a84ffbc/xK9m2Fh7vNqYpR3wT8bZjL5sA0dGcE6iUoXnHyWfBkQ
The URL grows by ~21 characters. It remains copy-pasteable, QR-codable, and compatible with all messaging channels.
Why?
Matching the key entropy to the cipher key size is a cryptographic best practice. AES-256 provides 256 bits of security, but if the passphrase only has 131 bits of entropy, the effective security is bounded at 131 bits. With 256-bit keys, the passphrase strength matches the cipher, and the full security margin of AES-256 is utilized.
Backward compatibility
Both changes are fully backward-compatible:
| Scenario |
Behavior |
| Old secret (S2K type 3, 22-char key) read by updated Yopass |
✅ Works: S2K type auto-detected, key length irrelevant for decryption |
| New secret (Argon2, 43-char key) read by older Yopass |
⚠️ Requires OpenPGP.js ≥ 5.x and go-crypto (both support Argon2). Older OpenPGP.js v4 cannot read Argon2 messages. |
| Mixed deployment during rollout |
Old clients can't decrypt new secrets, but this is already the case with AEAD from #3364 |
Since #3364 already introduced a format change (AEAD/SEIPDv2), adding Argon2 + longer keys in the same release cycle does not create additional compatibility concerns.
Summary
| Change |
Effort |
Impact |
| Argon2 S2K |
1 line frontend + 2 lines Go |
Memory-hard KDF, GPU/ASIC resistant |
| 256-bit keys |
3 lines Go + test update |
Full AES-256 security margin |
Together with the AEAD migration from #3364, these changes would give Yopass a complete RFC 9580 cryptographic profile:
- ✅ AES-256-GCM (AEAD, authenticated encryption)
- ✅ Argon2 (memory-hard key derivation)
- ✅ 256-bit keys (full security margin)
- ✅
ProtonMail/go-crypto (actively maintained)
Would be happy to submit a PR if helpful.
Thank you in advance.
Complete RFC 9580 crypto modernization (Argon2 S2K + 256-bit keys)
Context
Thank you for the swift merge of #3364.
The migration to
ProtonMail/go-cryptoand the activation of AEAD (SEIPDv2/GCM) is a major security improvement. Yopass now uses modern authenticated encryption.Two low-hanging improvements remain to fully align with the OpenPGP crypto refresh (RFC 9580) and maximize the cryptographic strength of Yopass:
Both changes are backward-compatible: existing secrets remain readable, only new secrets benefit.
1. Argon2 for S2K key derivation
Current behavior
OpenPGP.js defaults to S2K type 3 (iterated and salted SHA-256, ~65 million iterations). This is CPU-only and theoretically vulnerable to GPU/ASIC bruteforce attacks, though Yopass's auto-generated keys mitigate this in practice.
Proposed change
Frontend:
website/src/shared/lib/crypto.ts:Server:
pkg/yopass/yopass.go(for CLI--encryptconsistency):No change is needed for CLI
--decrypt:ProtonMail/go-cryptov1.4.1 auto-detects the S2K type from the message and already supports Argon2 (S2K type 4).Why?
This matters especially for users who supply custom passphrases via
--keyinstead of using the auto-generated key, since custom passphrases typically have lower entropy.2. Increase key entropy to 256 bits
Current behavior
GenerateKey()inpkg/yopass/yopass.go:This generates 22 random bytes, base64url-encodes them, then truncates to 22 characters; yielding approximately 131 bits of entropy. While 131 bits is strong, it does not match the 256-bit AES key size.
Proposed change
And the corresponding test in
yopass_test.go:Impact on URLs
The URL grows by ~21 characters. It remains copy-pasteable, QR-codable, and compatible with all messaging channels.
Why?
Matching the key entropy to the cipher key size is a cryptographic best practice. AES-256 provides 256 bits of security, but if the passphrase only has 131 bits of entropy, the effective security is bounded at 131 bits. With 256-bit keys, the passphrase strength matches the cipher, and the full security margin of AES-256 is utilized.
Backward compatibility
Both changes are fully backward-compatible:
Since #3364 already introduced a format change (AEAD/SEIPDv2), adding Argon2 + longer keys in the same release cycle does not create additional compatibility concerns.
Summary
Together with the AEAD migration from #3364, these changes would give Yopass a complete RFC 9580 cryptographic profile:
ProtonMail/go-crypto(actively maintained)Would be happy to submit a PR if helpful.
Thank you in advance.