12

You have a plain text password and salt. The plain text password is assumed to be securely random and only known by the user and the salt is no secret and not necessarily unique but stored alongside the resulting hash. Is calling the following on its own and storing the result sufficient?

password_hash = Hash ( password || salt )

Where Hash is currently considered a "secure" hash function (sha512, BCrypt, etc)

Do people do more? Such as:

  1. Multiple Iterations: Is there a huge benefit in running multiple iterations? If yes, is it purely a cost advantage? Is the number of iterations dynamic or static? How many iterations?
  2. Multiple hash functions: Would there be an advantage in using multiple hash functions? For example, three rounds, sha512 -> BCrypt -> sha512?
  3. Salt mixing Do people nest salts within a password or just concatenate on the end/start? For example, break the plain password into four sections, the salt into three and join each salt section in-between each password section.
  4. Unique schemes: Would writing a unique password_hash function for each different project be of any benefit? For example, Project A's password_hash function runs SHA512 20 times and Project B's password_hash function runs BCrypt 10 times applying the salt at different ends of the password on each iteration.
  5. "Black Magic": Is there a benefit in adding your own "black magic" to a hashing function? For example, before each iteration you apply some basic bitwise actions or mix up the characters based on a simple algorithm.
  6. Obfuscation: Would encoding or obfuscating the generated hash to hide its originating hash function make a huge difference? Such as adding or removing N characters to the hash to hide its original length.

Are these actions really necessary, would they make a huge difference (if yes, in what way) and do people use any extra actions like the ones mentioned above in practice?


My Thoughts:

In the end whatever is done with the password will not prevent someone using a stupid password (like 'password' or '123'); other methods not relevant to this question will have to be taken. But it is possible through multiple iterations make the computation more costly thus slowing down or rendering an attack unfeasible by brute force.

Most actions mentioned above will make a large difference to someone who has already gained access to the list of users - emails, password hashes and salts, and wishes to recover the plain passwords. It would not prevent a determined hacker but will deter and hopefully slow them down to give you enough time to make appropriate changes or actions to secure the compromised accounts.

However a lot of this goes against the idea that "the attacker knows all". Assuming the attacker can see how the password hashes were generated (source code and schema) it would make little difference. It would be as significant as changing a database tables column name from password to ahoy

Regardless, just using password_hash = Hash ( password || salt ) seems too straightforward. Surely people do more?

4
  • 5
    I recommend browsing the questions tagged passwords and reading through the material there. Many of the topics you ask about here are well-covered elsewhere on this site, and most of them will be accessible through that tag.
    – D.W.
    Commented Aug 2, 2012 at 5:26
  • "In the end whatever is done with the password it will not prevent someone using a stupid password (like 'password' or '123');" You can use client side code to reject passwords like this.
    – Ramhound
    Commented Aug 2, 2012 at 15:38
  • @Ramhound - I am aware of this. "Other methods not relevant to this question will have to be taken". I wanted to keep this question related to the password hashing process. So my thoughts were ignoring other interventions such as a good password policy. I really should of worded that section better :)
    – Kurt
    Commented Aug 2, 2012 at 23:14
  • 1
    I just want to mention -- You mention SHA512 and bcrypt as if they're comparable, but they're not. bcrypt is significantly better because it does key strengthening and salting for you, in an easy-to-maintain way. Commented Aug 3, 2012 at 17:08

2 Answers 2

16
  1. Multiple Iterations - This helps. Its called key-strengthening and it raises the computational complexity. E.g., if you key-strengthen by a factor of a 1000, an attacker can try 1000 less possible passwords in the same amount of GPU/CPU time.

  2. Multiple hash functions - In theory this helps but not really in practice. The case would be if one hash function used is somehow broken (e.g., you can reverse any 256-bit hash by trying significantly less than 2256 passwords or can significantly simplify the brute-forcing process while trying common passwords), but the other hash functions used in the chain are not broken. E.g., that your attacker has broken iterated sha512, so can quickly come up with a password that works for sha512 key-strengthened N times, but not sha512(salt||bcrypt(salt||sha512(salt||pw))). However in my view with modern cryptographic hash functions and typical attackers, that's a fairly unlikely attack scenario -- there are much lower hanging fruit that don't require the attacker to be make fundamental advances in computer science. Possibly NSA or serious academic computer scientist may come up with clever ways to break hashing algorithms, but not your average malicious attacker. Also even among hash algorithms that are broken and should generally be avoided (e.g., MD5 and SHA-1), they are usually only broken to collision attacks (find a pair of distinct strings s1, s2 that satisfies hash(s1) == hash(s2) much quicker than expected), but not preimage attacks (given a hash h find an s that satisfies hash(s)=h), which is the relevant attack on a password hash.

  3. Salt mixing - This is an implementation detail, and doing it adds no complexity to an attacker. (Precomputed rainbow tables for complex key-strengthened salted hashes don't exist.)

  4. Unique schemes - Again an implementation detail, and adds no security. Also don't run bcrypt multiple times; that's silly. Bcrypt is already key-strengthened. E.g., if you hash using bcrypt with a cost of 16, that means the key went through 2^16 ~ 65536 rounds, so if you want a stronger hash just increase the cost (every increase by 1; means bcrypt will be twice as slow).

  5. Black Magic - As a programmer, I wouldn't suggest doing this; as black magic is a maintainance nightmare for you and your team, and again not too significant for an attacker to overcome. Decision comes down to adopt a new technology, so you have to rewrite the non-standard hash routine in a new language on a new platform and unless its very well-documented you may do it slightly differently breaking the system. And if it is well-documented, an attacker may steal your source code; see what the function does to a password and then can use for their brute forcing. If they manage to steal your hashes, they probably have at least one account where they know a password and stole the hash, so can try reverse engineering it (even if they didn't get the source code). Its like obsfucating your source code on your own server. Its more a hassle for you the developer/maintainer, not really for attackers.

  6. Obsfuscation - Don't do this. See black magic. Or just operate on Kerckhoff's principle aka Shannon's maxim: make the system strong even if the enemy perfectly knows the system. Obfuscation/black magic can be overcome with a little analysis and provides some security (defense-in-depth), but not much. However, a strong key-strengthened cryptographic hash with a high-entropy initial passphrase/password can easily be built to have a computational complexity that is completely unfeasible to attack with all the computers in the universe for thousands of years, without major advances in computer science (e.g., quantum computer or breaking the hash function).


In the end whatever is done with the password it will not prevent someone using a stupid password (like 'password' or '123')

Actually I'll disagree on this one. Depending on how captive the audience is (e.g., workers must use it for their job), you could invalidate simple passwords whenever a new password is setup (e.g., check against a leaked password list of a ~million common passwords and invalidate all matches, or doesn't pass other minimum standards: must be 8+ characters with upper/lower/number/symbol or a 20+ character passphrase). Basically, if its a secure application that has serious repercussions for your end if an account is breached, something like this is a great idea.

Granted users will have negative user experience if you are too strict (so if you are a simple web app or an online retailer that wants lots of users and doesn't really worry about accounts being stolen from weak passwords, you probably do not want to implement strict password rules; but the retailer should do things like require re-entering credit card information after the email/shipping address changes and should send an receipt email after all purchases). Also, beware this ultimately weakening security by users posting the password on their monitor in a common work environment; so make it clear that there are consequences to a user's account being breached.

-2

I add label to the hash, for example, ip address:

10.10.10.10:bDcxWMe1v81o97HbRrVF1:1234

Also, in the DB you can add timestamp:

bDcxWMe1v81o97HbRrVF1:Fri Aug  3 12:42:48 UTC 2012

Plus, sometimes I add the version and name of the function which created it:

bDcxWMe1v81o97HbRrVF1:bcrypt1.0

As a result, you have a hash with additional SECURITY information, which then you encrypt with asymmetric public key as in RSA:

For example:

RSA(private, 10.10.10.10:bDcxWMe1v81o97HbRrVF1:1234:Fri Aug  3 12:42:48 UTC 2012:bcrypt1.0)

Result is encrypted:

7UnUO98lbDcxWMe1v817UnUO98lbDcxWMe1v817UnUO98lbDcxWMe1v817UnUO98lbDcxWMe1v817UnUO98lbDcxWMe1v81

When you submit the password, the hash is computed as normal, along with other plain-text data, for example ip address obtained from the connection or cookie-id, the result is crypted with RSA, and validated if the user can obtain a session, e.g. if the value is matching. The data on the server is non-decryptable, so you cannot crack it as normal hash value, because it's encrypted with RSA, and you cannot decrypt RSA that easy as hash value, and the additional plain-text data is hidden too, and can be reversed if required for example to perform validation of the connection on another system, so this way the hash value is bound to the connection between e.g. load balancer where it decrypts the request without computing complex hash but doing RSA decryption and the web where it validates the password by making full hash calculation.

Particular usage scenario for this could be very difficult, but maybe it could use the password, I'll think about this he.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .