This post is part of the Cryptography Series
Prerequisite Knowledge: Radical Asymmetry
Parameterizing cryptographic primitives
The most useful cryptographic primitives are self-contained building blocks with well defined security guarantees. However, they are often tailored to a particular use case with deployment-specific parameters, which may need to be secret, unpredictable or unique. Note that these are nested sets of properties (in practice, if not in principle): all secret values are unpredictable and all unpredictable values are unique.
A cryptographic key is an arbitrary secret. It is crucial that it remains secret. If an unauthorized person obtains it, the security of the system is broken. This is not always true, but if you are knowledgeable enough to know that, you don’t need this article. Otherwise, it is better to err on the side of maniacal paranoia. If the key is compromised, the cryptography does nothing.
This means that a cryptographic key should be:
- generated with an unbiased random number generator on trustworthy hardware
- large enough that it cannot be guessed in a reasonable time frame
- stored securely
Everything else is considered public. Note that by this definition, your mother’s maiden name and your favorite high school teacher are public values. They might be unknown to generic strangers, but they are not cryptographic secrets and are unlikely to thwart motivated attackers. As a general rule, if they could be plausibly guessed or discovered by anyone willing to spend trillions of dollars and decades of effort, they cannot provide Radical Asymmetry and should be treated as public in a cryptographic context. For this reason, some people use long random strings as their answers to these security questions.
Similarly, all user-generated data is public, since the security of a cryptographic primitive should not depend on the particular data it is processing. Oddly, this implies that the contents of encrypted messages are treated as public. If that sounds confusing, note that the claim is merely that some (or all) of the contents may be independently predictable (for example, it might be obvious from context that it is a particular file type). Even in such a case, good encryption algorithms provide confidentiality - any guessed plaintext is neither confirmed nor refuted and any unknown plaintext remains secret. More generally, cryptographic primitives are designed to ensure that even if the user-generated data is known, they still provide the same security guarantees.
Salts are unpredictable values that are not treated as secrets. Their purpose is to anchor cryptographic primitives to a particular use case. I don’t know if this is the etymology, but it may help to think of a salt as flavoring a hash function. One common example is when an authentication server stores hashes of user passwords so that they can be compared against future login attempts. The hashes themselves are typically not considered secrets since they must be directly available to the authentication server. If the server used the same hash function for each user’s password and an attacker was able to retrieve the hashes, it would be possible for them to precompute the hashes of common or small passwords (and store a TMTO) in order to identify such passwords among the set. It would also be possible to immediately recognize when multiple users shared the same password. Instead, a user-specific salt is included within the hash, effectively providing each user with a dedicated hash function.
This prevents an attacker from using the same TMTO for all password hashes (undermining the entire point of a TMTO). Furthermore, since the salts are unpredictable, the attacker cannot perform any precomputation before they retrieve the salts. Since the salts are stored alongside the hashes, they are considered public and are therefore not cryptographic keys.
A nonce is a number that is unique in a particular cryptographic context (or a number that is used once). Since it doesn’t need to be unpredictable, a simple counter is often used. As an example, a block encryption function (like the ubiquitous AES) can be used to produce a large stream of pseudorandom numbers by simply encrypting consecutive nonces. As long as the encryption key remains secret and the nonce is never repeated, the output is unpredictable even though the input is completely predictable.
In some cases, the nonce has a secondary non-cryptographic purpose where the order also matters. For example, every transaction signed by an Ethereum address contains a nonce, which ensures that each signature is unique, even if there are multiple transactions with identical contents. However, the nonce is also used to order the transactions, so it’s called a sequence number instead.