2

Given the following situation: we have sensitive information and want to give a user access to it. At this point the user might not have interacted with our system yet.

We generate a random code that we send to a user to grant him access to his information. There would also be a second factor for the access. But this question only concerns how to hash and store the hash of the code.

The code acts as a password, with the important difference that it is random and not chosen by the user. We only want to store a cryptographic hash of the code, not the code itself.

However we need to be able to query the information by the hash of the code, so when the user submits the code, we compute the hash and lookup the information by the hash. This prevents us from using a salt. Given that the code is already random, it doesn't seem strictly necessary to salt the code, unlike a password. So we cannot use, for example, bcrypt, as we do for hashing user passwords, because bcrypt always uses a salt.

The code should also not be excessively long for usability reasons.

Now the question is, if we use, for example, SHA-256, which is fast to compute compared to password hashing functions like bcrypt, how long would the code have to be for it to be secure against brute forcing?

Am I right to think that, if our code is, for example, 12 digits alphanumeric, it would only take 36^12 attempts to guess the SHA-256 hash, instead of 2^256 guesses for arbitrary length input. (This question was also asking about that).

To protect against brute force attacks, is there another, slow-on-purpose cryptographic hash function that works without a salt so that the hash is queryable, that we could use in place of SHA-256?

0
1

MechMK1's solution is satisfactory and relies on the specially designed slow and memory-hard password hashing algorithms. In this case, your calculation will be slow, too. That can affect your system depending on the number of tickets generated and tested for validation. In Cryptography, we have better options for this, the keyed hash function like HMAC and KMAC if you can afford to secure the key probably with an HSM.

To produce a valid token the attacker needs the key and the security of the HMAC is depend on the size of the key. They are even not affected by the collisions of the used hash function. Even HMAC-MD5 and HMAC-SHA1 are safe, however, we generally use HMAC-SHA256 with a 256-bit key or HMAC-Blake, etc.

3
  • Thank you for your answer too! I know about the application of HMACs to check integrity and authenticity of a message, but haven't seen them used in a situation like this. I can see now how it fits well, given that the key makes brute force computation of hashes impossible. Do you happen to have an example of an HMAC being used that way? I would like to read more about that. Dec 18 '20 at 17:12
  • Requiring another key is also a downside though, we operate on rented servers in a data center and would have to store the key somewhere. An HSM is probably not an option for us. Dec 18 '20 at 17:15
  • 1
    Many cloud services provide HSM. Having a master key and deriving two keys from HMAC is always possible, or PBKDF, or Argon2 etc. I cannot provide a link for you now to read more about.
    – kelalaka
    Dec 18 '20 at 17:22
2

First of all, let us assume the code is in the form of XXXX-XXXX-XXXX-XXXX. Four groups of four alphanumeric symbols, kept apart by dashes for easier readability, is something that is not too bothersome for a user to type in. I will assume that the code will have to be typed in and can't otherwise be submitted to the application (e.g. an URL, copy/paste, etc.), because in such cases, there really is no reason not to use very long, completely random tokens instead.

Next, let's have a look at the structure. I said "alphanumeric" before, but there are some characters which make sense to exclude, such as 0O1IS5 and perhaps others. You don't want a user to mistake an O for a 0, for example. Sure, it may seem obvious to you, but not for everyone - so for the sake of usability, drop all ambiguous characters.

That leaves us with 16 characters and a 30 character alphabet, meaning we have 30^16 possible keys, or ~78 bits of security. In terms of a randomly generated key that a user should type, that's not bad. (By comparison, using 36 characters gives you ~82 bits). So that alone makes online brute-force attacks infeasible.

However, you also want to protect against offline brute-force attacks, in case someone steals the keys. To do that, you should store the first four characters in plain as an identifier. That allows you to generate 810,000 different keys at once, while still leaving ~58 bits of entropy for the secret part.

For the hashing algorithm, you have two sensible choices: bcrypt and Argon2id

bcrypt is the "you can't mess it up"-choice. If you have a good cryptographic library, you should have a function like password_hash(string password) and password_verify(Hash hash, string password) - or similar. These will take all the trouble of using good parameters for bcrypt from you.

Argon2id is the "better if you know what you are doing"-choice. It offers better resistance against offline-brute force attacks, but depends on the correct configuration. You can read about it in more detail, but if you're not already familiar with it, I would suggest using bcrypt instead.

By using these as designed, you will get a hash from a strong key-derivation function, designed to make offline brute-force attacks infeasible. It also makes pre-computation attacks infeasible, in which attackers calculate the hashes beforehand and then compare - since this would take ~22 Yotta-Byte of storage (~22,000,000,000,000 TB), that's not feasible.

To summarize

  • Generate four characters to "identify" the code and make sure that's unique
  • Hash the entire code using either bcrypt or Argon2id
  • Verify the entered code with the stored hash
2
  • Thank you! You are right to assume that (at least in some cases) the user would have to type the code in manually, because we cannot always send them a URL. As I was typing the question it also dawned on me that we could use two codes, one to query and one to grant access. I like your idea of having both parts in the same code, so that this is opaque to the user. We will consider doing it that way ... Dec 16 '20 at 15:57
  • It's definitely better to put it all in one code. You could also just make the first two segments the identifier if you need it, and make five segments in total
    – MechMK1
    Dec 16 '20 at 17:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.