In the beginning of 2023 I've started extending my open-source project z-tokens, from just supporting passwords (plus other related tokens) generation and hashing (with various well-known algorithms), with support for a very "opinionated" encryption tool.
I know that "opinionated" is currently a very misused word, from "opinionated" web frameworks, to "opinionated" cryptography tools. However, in this case I think I'm not misusing it, as I'll try to describe further.
!!! WARNING !!!
I am not a cryptographer, or even a security expert!, at best I am a hobbyist applied-cryptography developer, thus take everything I say with a large grain of salt and lots of circumspection. As we all know: never implement your own cryptography! (It used to be just "[...] own crypto", but then some people wanted to rebrand ponzi-schemes into something that sounded more academic...)
Why I've set to implement another encryption tool is perhaps a story for another time. However, for the moment I would like to describe the approach I've taken.
Many tools -- by which I actually mean GnuPG and age -- support any of the following encryption mechanisms:
shared password -- i.e. type some stuff on the keyboard, and the tool will base the encryption keys on that;
public/private keys (RSA, X25519, etc.) -- i.e. point to the recipient's public key, and the tool will make use of that;
both support, for the same message, using at the same time a shared password and a public key (or more), but these are alternatives; namely at decryption time you need either the shared password either one of the private keys, but not necessarily both;
So, isn't that enough, one might ask? Obviously it is enough from a purely technical point-of-view, however not necessarily in terms of intended use-cases.
how about requiring both a password and a private key? -- for example the private key could be an USB token or even a key handled by a software agent (like the
ssh-agent), thus serving the role of a second factor ("something you have"), meanwhile the password being the first factor ("something you know");
how about two passwords? -- sure, one can just concatenate them, but that just isn't the best user experience;
how much effort (mainly time) should the tool spend for deriving the password into cryptographic material?
- what if one just wants to paste something in a chat with such a small "freshness" time window, that even if it's compromised in 5 minutes it won't matter much; I wouldn't want to waste 1 second of 100% CPU time to derive a weak password for this;
- what if, at the other end of the spectrum, one is about to encrypt something so important that would like the derivation to take 10 seconds;
To answer these use-cases, here follows my "opinionated" proposal.
First of all, provide multiple mechanisms (that sometimes are technically equivalent):
(there are no "passwords", because the word "password" is nowadays almost meaningless as a definition in cryptography or related domains;)
PIN's -- weak (low entropy, mainly user chosen) tokens, that take (on a mildly modern laptop) around 100ms to derive; these are named "PIN's" and not "passwords" because I want to convey to the user the fact that these aren't to be used as a serious security mechanism;
secrets -- strong (high entropy, mainly generated by the tool) tokens that take around 1 second to derive; these should have been perhaps named "passphrases", however they are actually "shared secrets", thus the name;
X25519 private/public key pairs, one for the sender and one for the receiver -- which besides encryption (via DHE) also provide authentication of the sender to the receiver; (this authentication is a much weaker assertion than publicly verifiable signatures, however sometimes it is much more desirable);
some kind of random oracle; -- I've chosen to start with
ssh-agent-based keys because these can be easily forwarded to remote machines via SSH, but also because they are ubiquitous and already implemented in many keyrings, credential services, and password managers; however, in future iterations, one could use here any kind of cryptographic token or TPM; for a more detailed description of such oracles, see my next article on the topic;
Then, allow any number of these (including multiple of each kind) to be used together. For example, both one X25519 sender private key and two X25519 recipient public keys, plus also a PIN.
Additionally, also adding support for:
context (or "additional data") -- say I want to store a couple of encrypted scripts somewhere, one for running at startup and one at shutdown, and I want to be sure that the operator of the storage service doesn't swap them; (i.e. I want to bind each script to a given purpose;)
"cryptographic ballast" (for the lack of a better name) -- say I'm not content with the 1 second secret derivation time; for each ballast added, it takes an additional 10 seconds, thus the derivation time increases linearly, plus the derivation memory requirement increases with a logarithmic factor;
AONT (All or Nothing Transform) -- shred or mangle even a couple of octets from the encrypted file, and nothing is recoverable (even having all the right secrets and keys) without brute-forcing the presumably mangled bytes;
For the curios, the Rust implementation (still an early prototype) can be reviewed on GitHub at: z-tokens/exchange/crypto.rs.
Now one might ask, and definitively I've asked myself this, isn't this overly complicated? I can't say it isn't, however I also can't find a solution that is at the same time both flexible and not a foot-gun...
Sure, one could go the PGP route and allow the user to choose the number of password derivation iterations or even the preferred cipher suite, but that would require too much from the user.
Meanwhile, with the current proposed solution, I can easily and safely implement various scenarios that would be quite hard to achieve with the existing tools.
Just to give a few examples:
"two-factor encryption" -- by using a PIN (which can even be something simple) and a USB-stick that contains a secret token;
"multi-factor encryption" -- extend the above scheme to multiple USB-sticks that should contain each different secret tokens;
improve any of the above by replacing the secret token with a ballast token (or at least make one of them be a ballast); this way brute-forcing is made completely unapproachable;
unattended decryption by storing one secret token at an URL (that can be easily deleted if needed) and one secret token on the VM or target machine (or in the VM user-data);
remote decryption via SSH-agent forwarding;
many more scenarios where one can combine one or more PIN's, secrets, X25519 key pairs, oracles (e.g. SSH-keys), or ballasts;
Technically, both the PIN's, secrets, and ballasts, work in the same way with the main difference being the time it takes to derive each of these into cryptographic material.
I'm curious if anyone would find such a tool useful? Or if perhaps there aren't already tools that do something similar?
Let me know via an email or comment as described in the feedback section.