An idea whose time has come?
Inefficient by design
Computer science is all about efficiency: doing more with less. Getting to the same answer faster in fewer CPU cycles, using less memory or economizing on some other scarce resource such as power consumption. That philosophy makes certain designs stand out for doing exactly the opposite: protocols crafted to deliberately waste CPU cycles, take up vast amounts of memory or delay availability of some answer. Bitcoin mining gets criticized— often unfairly— as the poster-child for an arms race designed to throw more and more computing power at a “useless” problem. Yet there is a much older, more mundane and rarely questioned waste of CPU cycles: password hashing.
In the beginning: /etc/passwd
Password hashing has ancient origins when judged by the compressed timeline of computing history. Original UNIX systems stored all user passwords in a single world-readable file under /etc/passwd. That was in the day of time-sharing systems, when computers were bulky and expensive. Individuals did not have the luxury of keeping one under their desk, much less in their pocket. Instead they were given accounts on a shared machine owned and managed by the university/corporation/branch of government. When Alice and Bob are on the same machine, the operating system is responsible for making sure they stay in their lane: Alice can’t access Bob’s files or vice verse. Among other things, that means making sure Alice can not find out Bob’s password.
Putting everyone’s password in a single world-readable file obviously fall short of that goal. Instead UNIX took a different approach: place a one-way cryptographic hash of the password. Alice and Bob could still observe each other’s password hashes but the hashes alone were not useful. Logging into the system required the original, cleartext version of the password. With a cryptographic hash the only way to “invert” the function is by guessing: start with a list of likely passwords, hash each one and compare against the one stored in the passed file. The hash function was also made deliberately slow. In most cryptographic applications, one prefers their hash function to execute quickly, inefficiency is a virtue here. It slows down attackers more than it slows down the defenders. The hash function is executed only a handful of times for “honest” paths when a legitimate user is trying to login. By contrast an attacker trying to guess passwords must repeat the same operation billions of times.
Later UNIX versions changed this game by separating password hashes— sensitive stuff— from other account metadata which must remain world-readable. Hashes were moved to a different place appropriately called the “shadow” file commonly placed at /etc/shadow. With this improvement attackers must first exploit a local privilege-escalation vulnerability to read the shadow file before they can unleash their password-cracking rig on target hashes.
Atavistic designs for the 21st century
While the 1970s are a distant memory, vestiges of this design persist in the way modern websites manage customer passwords. There may not be a world-readable password file or even shadow file at risk of being exposed to users but in one crucial way the best-practice has not budged: use one-way hash function to process passwords for storage. There have been significant advances in the construction of the hash functions are designed. For example the 1990s era PBKDF2 includes an adjustable difficulty— read: inefficiency— level, allowing the hash function to waste more time as CPUs become faster. Its modern successor bcrypt follows the same approach. Scrypt ups the along a new dimension: in addition to being inefficient in time by consuming gratuitous amount of CPU cycles, it also seeks to be inefficient in space by deliberately gobbling up memory to prevent attackers from leveraging fast but memory-constrained systems such as GPUs and ASICs. (Interestingly these same constraints also come up in the design of proof-of-work functions for blockchains where burning up some resource such as CPU cycles is the entire point.)
Aside from committing the cardinal sin of computer science— inefficiency— there is a more troubling issue with this approach: it places the burden of security on users. The security of the password depends not only on the choice of hash function and its parameters, but also on the quality of the password selected. Quality is elusive to define formally, despite no shortage of “password strength meters” on websites egging users on to max out their scale. Informally it is a measure of how unlikely that password is to be included among guesses tried by a hypothetical attacker. That of course depends on exactly the order an attacker goes about guessing. Given that the number of all possible passwords is astronomically large, no classical computer has a chance of going through each guess even when a simple hash function is used. (Quantum-computers provide a speed-up in principle with Grover’s algorithm.) Practical attacks instead leverage the weak point: human factors. Users have difficulty remembering long, random strings. Absent some additional help such as password managers to generate such random choices, they are far more likely to pick simple ones with patterns: dictionary words, keyboard sequences (“asdfgh
”), names of relatives, calendar dates. Attacks exploit this fact by ordering the sequence of guesses from simple to increasing complexity. The password “sunshine123” will be tried relatively early. Meanwhile “x3saUU5g0Y0t
” may be out of reach: there is not enough compute power available to the attacker to get that far down the list of candidates in the allotted time. Upshot of this constraint: users with weak passwords are at higher risk if an attacker gets hold of password hashes.
Not surprisingly websites are busy making up arbitrary password complexity rules, badgering user with random restrictions: include mixed case, sprinkle in numerical digits, finish off with a touch of special characters but not that one. This may look like an attempt to improve security for customers. Viewed from another perspective, it is an abdication of responsibility: instead of providing the same level of security for everyone, the burden of protecting password hashes from brute-force attack is shifted to customers.
To be clear, there is a minimal level of password quality helpful to resist online attacks. This is when the attacker tries to login through standard avenues, typically by entering the password into a form. Online attacks are very easy to detect and protect against: most services will lock out accounts after a handful of incorrect guesses or require CAPTCHA to throttle attempts. Because attackers can only get in a handful of tries this way, only the most predictable choices are at risk. (By contrast an attacker armed with stolen database of password hashes is only limited by the processing power available. Not to mention this attack is silent: because there is no interaction with the website— unlike the online guessing approach— the defenders have no idea that an attack is underway or which accounts need to be locked for protection.) Yet the typical password complexity rules adopted by websites go far beyond what is required to prevent online guessing. Instead they are aimed at the worst-case scenario of an offline attack against leaked password hashes.
Side-stepping the arms race: keyed hashing
One way to relieve end-users from the responsibility of securing their own password storage— as long as passwords remain in use, considering FIDO2 is going nowhere fast— is to mix in a secret into the hashing process. That breaks an implicit but fundamental assumption in the threat model: we have been assuming attackers have access to the same hash function as the defenders use so they can run it on their own hardware millions of times. That was certainly the case for the original UNIX password hashing function “crypt.” (Side-note: ironically crypt was based on the block cipher DES which uses a secret-key to encrypt data. “Crypt” itself does not incorporate a key.) But if the hash function requires access to some secret only known to the defender, then the attacker is out of luck:
For this approach to be effective, the “unknown” must involve a cryptographic secret and not the construction of the function. For example making some tweaks to bcrypt and hoping the attacker remains unaware of that change provides no meaningful benefit. That would be a case of the thoroughly discredited security-through-obscurity approach. As soon as the attacker gets wind of the changes— perhaps by getting access to source code in the same breach that resulted in the leak of password hashes— it would be game over.
Instead the unknown must be a proper cryptographic key that is used by the hash function itself. Luckily this is exactly what a keyed hash does. In the same way that a plain hash function transform input of arbitrary size into a fixed size output in an irreversible manner, a keyed hash does the same while incorporating a key.
KH(message, key) → short hash
HMAC is one of the better options but there are others including the older generation CMAC and newer alternatives such as Poly1305 and KMAC. All of these functions share the same underlying guarantee: without knowing the key, it is computationally difficult to produce the correct result for a new message. That holds true even when the attacker can “sample” the function on other messages. For example an attacker may have multiple accounts on the same website and observe the keyed hashes for his/her own accounts for which he/she has selected the password. Assuming our choice of keyed hash lives up to its advertised properties, that would still provide no advantage in being able to compute keyed-hashes on other candidate passwords.
Modern options for key management
The main challenge for deploying keyed hashing at scale comes down to managing the lifecycle of the key. There are three areas to address:
- Confidentiality is paramount. This key must be guarded well. If attackers get hold of it, it stops being a keyed hash and turns into a plain hash. (One way to mitigate that is to apply the keyed hash in series after an old-school, slow unkeyed hash. More on this in a follow up post.) That means the key can not be stored in say the same database where the password hashes are kept: otherwise any attack that could get at hashes would also divulge the key, losing out on any benefits.
- Availability is equally critical. Just as important as preventing attackers from getting their hands on the key is making sure defenders do not lose their copy. Otherwise users will not be able to login: there is no way to validate whether they supplied the right password without using this key to recompute the hash. That means it can not be some ephemeral secret generated on one server and never backed-up elsewhere. Failure at that single point would result in loss of the only existing copy and render all stored hashes useless for future validation.
- Key rotation is tricky. It is not possible to simply “rehash�� all passwords with a new key whenever the service decides it is time for change. There is no way to recover the original password out of the hash, even with possession of the current key. Instead there will be an incremental/rolling migration: as each customer logs in and submits their cleartext password, it will be validated against the current version of the key and then rehashed with the next version to replace the database entry.
Of these #3 is something an inevitable implementation twist, not that different in spirit from trying to migrate from one unkeyed hash to another. But there is plenty of help available in modern cloud environments to solve the first two problems.
Until about ~10 years ago using dedicated cryptographic hardware— specifically HSM or hardware security module— was the standard answer to any key management problem with this stringent combination of confidentiality and availability. HSMs allow “grounding” secrets to physical objects in such a way that the secret can be used but not disclosed. This creates a convenient oracle abstraction: a blackbox that can be asked to perform the keyed-hash computations on any input message of our choosing, but can not be coerced to divulge the secret involved in those computations. Unfortunately those security guarantees come with high operational overhead: purchasing hardware, setting them up in one or more physical data centers and working with the arcane, vendor-specific procedures to synchronize key material across across multiple units while retaining some backups in case the datacenter itself goes up in flames. While that choice is still available today and arguably provides the highest level of security/control, there are more flexible options with different tradeoffs along the curve:
- CloudHSM offerings from AWS and Google have made it easier to lease HSMs without dealing with physical hardware. These are close to the bare-metal interface of the underlying hardware but often throw-in some useful functionality such as replication and backup automatically.
- One level of abstraction up, major cloud platforms have all introduced “Key Management Service” or KMS offerings. These are often backed by an HSM but hide the operational complexity and offer simpler APIs for cryptographic operations compared to the native PKCS#11 interface exposed by the hardware. They also take care of backup and availability, often providing extra guard-rails that can not be removed. For example AWS imposes a mandatory delay for deleting keys. Even an attacker that temporarily gains AWS root permissions can not inflict permanent damage by destroying critical keys.
- Finally TPMs and virtualized TPMs have become common enough that they can be used often at a fraction of the cost of HMSs. TPM can provide the same blackbox interface for computing HMACs with a secret key held in hardware. TPMs do not have the same level of tamper resistance against attackers with physical access but that is often not the primary threat one is concerned with. A more serious limitation is that TPMs lack replication or backup capabilities. That means keys must be generated outside the production environment and imported into each TPM, creating weak points where keys are handled in cleartext. (Although this only has to be done once for the lifecycle of the hardware. TPM state is decoupled form the server. For example all disks can be wiped and OS reinstalled with no effect on imported keys.)
Threat-model and improving chances of detection
In all cases the threat model shifts. Pure “offline” attacks are no longer feasible even if an attacker can get hold of password hashes. (Short of a physical attack where the adversary walks into the datacenter and rips an HSM out of the rack or yanks the TPM out of the server board— hardly stealthy.) However that is not the only scenario where an attacker can access the same hash function used by the defenders to create those hashes. After getting sufficient privileges in the production environment, a threat actor can also make calls to the same HSM, KMS or TPM used by the legitimate system. That means they can still mount a guessing attack by running millions of hashes and comparing against known targets. So what did the defenders gain?
First note this attack is very much online. The attacker must interact with the gadget or remote service performing the keyed hash for each guess. It can not be run in a sealed environment controlled by the attacker. This has two useful consequences:
- Guessing speed is capped. It does not matter how many GPUs the attacker has at their disposal. The bottleneck is the number of HMAC computations the selected blackbox can perform. (Seen in this light, TPMs have an additional virtue: they are much less powerful than both HSMs and typical cloud KMSes. But this is a consequence of their limited hardware rather than any deliberate attempt to waste cycles.)
- Attackers risk detection. In order to invoke the keyed hash they must either establish persistence in the production environment where cryptographic hardware resides or invoke the same remote APIs on a cloud KMS service. In the first case this continued presence increases the signals available to defenders for detecting an intrusion. In some cases the cryptographic hardware itself could provide clues. For example many HSMs have an audit trail of operations. Sudden spike in number of requests could be a signal of unauthorized access. Similarly in the second case usage metrics from the cloud provider provide a robust signal of unexpected use. In fact since KMS offerings typically charge per operation, the monthly bill alone becomes evidence of an ongoing brute-force attack.
CP
You must be logged in to post a comment.