The web is nothing if not a maze of user accounts and logins. Almost everywhere you go on the web requires yet another new set of credentials. Unified login seems to elude us at the moment, so the status quo is an explosion of usernames and passwords for every user. As a consequence of all this siloed user identity data, Facebook and most other web apps encourage us to give out our credentials like Halloween candy, as Dare Obasanjo notes:
On Facebook, there is an option to import contacts from Yahoo! Mail, Hotmail, AOL and Gmail which requires me to enter my username and password from these services into their site. Every time I login to Yahoo! Mail there is a notice that asks me to import my contacts from other email services which requires me to give them my credentials from these services as well.
This is a deplorable state of affairs. We're teaching users that their credentials are of little value and should be freely handed out to any passing website that catches their fancy. It's an incredibly dangerous habit to inculcate in users; it makes them far more vulnerable to phishing:
If users get comfortable with entering their credentials in all sorts of random places then it makes them more susceptible to phishing attacks. This is one of the reasons services like Meebo are worrying to me.
The last thing we should be doing is coming up with ways to make phishing more powerful. Phishing is a social engineering exploit so timeless, so effective, and so powerful, I call it the forever hack. Rainbow table and brute force attacks can be defeated through judicious use of technology. Phishing can't.
Users collect usernames and passwords like they do Pokemon. It's a sorry state of affairs, but for better or worse, that's the way it is. We, as software developers, are trusted with storing all these usernames and passwords in some sort of database. The minute we store a user's password, we've taken on the responsibility of securing their password, too. Let's say a hacker somehow obtains a list of all our usernames and passwords. Either it was an inside job by someone who had access to the database, or the database was accidentally exposed to the public web. Doesn't matter how. It just happened.
Even if hackers have our username and password table, we're covered, right? All they'll see is the hash values. Only the most grossly incompetent of developers would actually store passwords as plaintext in the database, right? Right?
Recently, the folks behind Reddit.com confessed that a backup copy of their database had been stolen. Later, spez, one of the Reddit developers, confirmed that the database contained password information for Reddit's users, and that the information was stored as plain, unprotected text. In other words, once the thief had the database, he had everyone's passwords as well.
Wrong.
I'm only a mouth-breathing Windows developer, not one of the elite Y-combinating Lisp, oops, Python developers working on Reddit. And even I know better than that.
You might think it's relatively unimportant if someone's forum password is exposed as plain text. After all, what's an attacker going to do with crappy forum credentials? Post angry messages on the user's behalf? But most users tend to re-use the same passwords, probably because they can't remember the two dozen unique usernames and passwords they're forced to have. So if you obtain their forum password, it's likely you also have the password to something a lot more dangerous: their online banking and PayPal.
My point here is not to drag the good names of the developers at Reddit through the mud. We're all guilty. I'm sure every developer reading this has stored passwords as plain text at some point in their career. I know I have. Forget the blame. The important thing is to teach our peers that storing plaintext passwords in the database is strictly forbidden-- that there's a better way, starting with basic hashes.
Hashing the passwords prevents plaintext exposure, but it also means you'll be vulnerable to the astonishingly effective rainbow table attack I documented last week. Hashes alone are better than plain text, but barely. It's not enough to thwart a determined attacker. Fortunately, the kryptonite for rainbow table attacks is simple enough-- add a salt value to the hashes to make them unique. I provided an example of a salted hash in my original post:
hash = md5('deliciously-salty-' + password)
But IANAC -- I Am Not A Cryptographer. I meant this only as an example, not as production code that you should copy and paste into that hugely popular enterprise banking solution you're working on. In fact, I ripped it almost directly from Rich Skrenta's excellent post on using MD5 hashes as utility functions.
Thomas Ptacek, on the other hand, is a cryptographer, and he has a bone to pick with my choice of salting techniques.
What have we learned? We learned that if it's 1975, you can set the ARPANet on fire with rainbow table attacks. If it's 2007, and rainbow table attacks set you on fire, we learned that you should go back to 1975 and wait 30 years before trying to design a password hashing scheme.*We learned that if we had learned anything from this blog post, we should be consulting our friends and neighbors in the security field for help with our password schemes, because nobody is going to find the game-over bugs in our MD5 schemes until after my Mom's credit card number is being traded out of a curbside stall in Tallinn, Estonia.
We learned that in a password hashing scheme, speed is the enemy. We learned that MD5 was designed for speed. So, we learned that MD5 is the enemy. Also Jeff Atwood and Richard Skrenta.
Finally, we learned that if we want to store passwords securely we have three reasonable options: PHK's MD5 scheme, Provos-Maziere's Bcrypt scheme, and SRP. We learned that the correct choice is Bcrypt.
* editor's note: Not quite true. Most "modern" software does not use a modern password scheme. Windows XP/2000/NT, phpBB, the majority of custom password schemes; all vulnerable to rainbow table precomputation attacks.
In summary, if we're storing passwords, we're probably storing those passwords incorrectly. If it isn't obvious by now, cryptography is hard, and the odds of us getting it right on our own are basically nil. That's why we should rely on existing frameworks, and the advice of experts like Thomas. What higher praise is there than that of praise from your sworn enemy?
Let's recap:
Of course, none of this guarantees you'll be able to prevent someone from deducing that Joe User's Myspace account password is "myspace1".
But when they do, at least it won't be your fault.
Appending (as is my habit...)
This perspective (salts are primarily for mutation rather than extension) also allows you to understand why early salts were often pretty short.
It is not the case that the makers of those early systems thought a couple-of-bytes salt would be enough to /extend/ the password in a meaningful way, making it more immune against brute-force attacks. Instead, they correctly recognized that it would be enough mutation to serve the primary purpose of using a salt as a mutator, preventing reuse of hacked passwords between systems.
Since then, salts have grown as this allows the secondary bi-effect of making the passwords longer and harder to crack to be exploited - which is, of course, all fine and good.
The problem comes when the primary purpose is forgotten and someone correctly observes that using longer passwords would then make salts unecessary - that's true with regards to the secondary effect of making passwords longer, but it is not true with regards to mutating passwords between systems.
/Mats
Mats Helander on September 19, 2007 5:34 AM Not storing passwords in plaintext is good in theory, but
in practice it's not always possible.
In practice it is always possible. I know of no mail client that does not support either TLS or SSL connections; there has not been a need to support plaintext authentication for mail relay for some years now.
And if you tell me that "I have a user that will only use a particular piece of broken software", the answer to that question is "plaintext passwords are no longer an acceptable security risk in today's internet, sorry."
You can't use telnet for remote administration any more. You ought not to be using unencrypted POP to get your mail, or unencrypted SMTP to send your mail.
Regarding salt, Mr. Helander explains correctly.
Pat Cahalan on September 19, 2007 5:58 AMIf you are using .NET, you may find the SecureString class to be helpful in storing passwords. SecureString keeps sensitive data encrypted in memory so that it cannot be easily stolen. SecureString plugs a specific security hole but does not guard against all threats while securing information in applications.
http://www.devtopics.com/securestring-safe-from-forensics-but-not-surveillance/
DevTopics on September 19, 2007 7:02 AMOne problem with hashed passwords: challenge-response authentication?
Sickboy on September 20, 2007 2:45 AMIt's really simple, Joe User puts his password in wrong 3 times and his password is locked, changed,
An attacker is likely to convert your account lockout setting into a denial-of-service attack, by locking out every account on the system. And a scheme like this is a massive burden on the helpdesk.
Jeff Atwood on September 20, 2007 4:29 AMWhy would you not list account lock-outs as a defense against brute force attacks?
It's really simple, Joe User puts his password in wrong 3 times and his password is locked, changed, and he is sent an e-mail telling him to come set a new password or call customer service to create a new one, then let the brute attacker into a honeypot with a whole crapload of tantalizing information while you log the evidence.
Mattkins on September 20, 2007 12:36 PMMattkins,
Normally a brute force attack does not assume that the hacker tries passwords out via the login mechanism. It concerns the case when the hacker already has your hashed password, knows the hashing mechanism used and the salt, and is now down to running different combinations of characters (brute forcing) against the hacker's own installation of the hash function until a string producing the correct hash output is found - not the login page on the system.
/Mats
Mats Helander on September 20, 2007 1:35 PMTheDave"Not storing passwords in plaintext is good in theory ... access plaintext passwords because the APOP and CRAM-MD5"
Side stepping the discussion slightly --
Both APOP and CRAM-MD5 are horribly insecure... You are only fooling yourself if you deploy a security solution based on these protocols.
If you read POP3 "Security considerations", it is painfully obvious that no one even took five minutes to think about what to write there. It is just a short section of wishful thinking.
http://www.ietf.org/rfc/rfc1939.txt
The APOP way of doing things known to be insecure in 1995,
http://citeseer.ist.psu.edu/433722.html
... why POP3 RFC fail to mention this, or why APOP hasn't been deprecated yet, who knows?
APOP being being way too insecure has been discussed since at least 2001, and those discussions didn't even include MitM nor rainbow tables. I wager the knowledge about this was known far longer back by the experts.
Today when rainbow tables are being deployed, digest authentication without additional security mechanisms are a laugh.
http://www.ietf.org/rfc/rfc2069.txt is an excellent reference of how to push digest authentication from "horribly insecure" to "good enough for our needs";
- Quality of Protection modes introduce client nounce and client counters into the scheme digest to stop choosen plaintext attacks (however, the vulnerability will continue to exist until browsers stop MitM attacks by rejecting any
- Servers can choose if they want to store cleartext or HA1 hash.
"Do not invent your own "clever" password storage scheme"
Ok, I agree. So what's a good hash method to store passwords with PHP?
hash() with sha512 [and salt]? good enough?
PSilva on September 29, 2007 9:38 AMHow about FIRST protecting your system enough to avoid people to get the hashes??
Nicolas on September 29, 2007 12:54 PMI wasn't able to find a managed .NET implementation of BCrypt, so I ported jBCrypt (using C#). Hopefully some of you find it useful.
Derek on October 2, 2007 10:02 AMGreat work, Derek - I already blogged about your solution.
Adi on October 7, 2007 3:51 AMIt's really simple, Joe User puts his password in wrong 3 times and his password is locked, changed,
An attacker is likely to convert your account lockout setting into a denial-of-service attack, by locking out every account on the system. And a scheme like this is a massive burden on the helpdesk.
A middle solution may be to temporarily (say one minute) lock out the user with the appropriate message.
Adi on October 7, 2007 3:54 AMi forgot my password pls help me what should i do
manuel on October 18, 2007 7:35 AMWe don’t need to store users’ passwords at all. We must identify a user by SSL client certificate or a PGP signature, it is more secure.
beroal on October 23, 2007 6:14 AMNobody has mentioned one time passwords yet. Seems odd?
SSL certs are great in conjuction with a password but not really the answer. For a reasonably secure site, just go for SSL between client and server (no sniffing, spoofing, man in the middle, proxying etc) and use a password + salt combo in the DB with a password complexity requrement.
If you want to take it further, use pin + one time passwords with SSL and spend some money on RSA, Cryptocard etc.
adrian on January 14, 2008 7:25 AMBob Armour wrote:
"If the salt is random, how do we reliably generate the same salt value the next time the user logs in?"
Bob - This is how I do it:
(for example purpose, I'm use plain text)
salt = asaltvalue (this will be a random string of fixed length)
password = apassword
password hash = sha1(salt+password)
password value to store in DB = password hash + salt
Conceivably, if someone were to see the password in your DB, they wouldn't know if the salt is part of the password, or if the password is just a hash of something. Even if they had some idea that the salt is part of the password, they wouldn't know the salt length. So in effect, your masking the salt in the password. To compare input password with password in DB, do:
password in = apassword
salt = last 10 characters of the password value in DB
password hash = first 40 characters of the password value in DB
is sha1(password + salt) = password hash?
YES - do something
NO - do something else
Not only does plentyoffish.com store your password as plain text, it includes it with every e-mail notification:
Don't forget, your password is 'myspace1'.
Ian Boyd on June 6, 2008 7:45 AMdaniel,
password value to store in DB = password hash + salt
Conceivably, if someone were to see the password in your DB, they wouldn't know if the salt is part of the password, or if the password is just a hash of something. Even if they had some idea that the salt is part of the password, they wouldn't know the salt length. So in effect, your masking the salt in the password.
In general, this is silly. Just a false sense of security. It is only providing obfuscation in the database. If the code is compromised, they'll easily see the concatenation and salt length. It will also cost you performance with all the concatenation and splitting.
alex on June 23, 2008 2:08 AMDoes anyone have any suggestions for how to protect your data at rest while allowing code transparency? This is especially a concern for free and open source software (FOSS), as well as distributed systems.
Is username, salt, hash(password + salt) with a strong algorithm and large keys good enough?
What if other data (not just the password) needs to be encrypted? Should we model PGP: http://www.pgpi.org/doc/pgpintro?
Client encryption seems to be workable, albeit with inaccessible JavaScript: http://clipperz.com. In essence, the user is even shielded from malevolent server admins (I would never ...).
I've experimented with password-protected services/daemons, where a system admin has to enter a password to start the web service. This password is used in the cryptography services, so that there are no secrets in server configuration files or code. Trouble is server restarts are extremely manual. There are still some forensic issues with holding secret information in memory, but it's not as bad as disk/backups.
Thoughts?
alex on June 23, 2008 2:29 AMSo then, in short, the password hash you store in the database should be: hash = md5(sitewidesalt + username + password).
The sitewide salt is there to lengthen the plaintext that is hashed, and if that remains secure then each individual hash is several orders of magnitude harder to crack (read: near impossible).
The username is there to defeat rainbow-table attacks - cracking one hash gives you no benefit whatsoever in attacking other hashes.
(Have I missed anything?)
As long as you wrote it in RED ink, and not BLUE ink; you're fine.
Brandon on February 6, 2010 10:14 PM"And securing the password within a password-protected store leads to a chicken-egg-problem: How to secure the password being used for unlocking the store."
Not sure what the problem here is. Encrypt your store using a key derived from a given password. You don't need to store the password anywhere, you have the user input it, use it to try and decrypt the store. If the password was wrong, the store will fail to decrypt.
Alright, I'm sold on bcrypt. Anybody know of a .NET implementation? All Google's giving me is some Ruby module.
@Bob: You save the salt. It doesn't need to be a secret.
@David: You probably should actually read the preceding discussion, because a salt based on the username is far worse than a "static" salt, which in turn is far worse than a random one. Jeff said it right in this post - don't try to be clever.
OT: I thought it was pretty funny in the Skrenta post, where someone made a comment about how using an identical salt for every password wouldn't make the system much more secure, and he "corrected" his entry by putting the salt at the beginning instead of the end. Whoosh!
Aaron G on February 6, 2010 10:14 PMIn what circumstances does the password need to remain what it currently is if the user forgets it?
I really don't understand the 'email me my current password' functionality, unless of course you're in control of someone elses email account and want to know their password for other services but also don't want them to figure it out when a password changes, but is that really a feature?
In any production system, especially systems with thousands or millions of accounts, you'll find a very high number of user names that are dictionary words. This is probably the most important reason not to use the user name as the salt.
If you're unlucky, the combination username+password or password+username is actually a dictionary word itself. You'd be amazed at how often this happens, and modern crackers all check for mutations, so passwords like "Bl4cKh0l3" won't be much more secure.
If you're extremely lucky, and none of these combinations appear in any cracking dictionary, you're still worse off. A cracker could simply create a dictionary or rainbow table combining two dictionary words, and they'd only ever have to do this once. It's not expensive to do - much cheaper than trying to brute-force 15 characters even once, and their dictionary is valid for your entire database.
It should be pretty obvious why "clever" schemes - like hashing the username before using it as a salt - don't afford much additional protection. Aside from the increased probability of collisions, a cracker can just hash every password in his dictionary and then follow the exact same steps. It doesn't take long to do this.
Just use a random string of 10 characters or so. It's really not that hard! It doesn't need to be cryptographically strong or highly randomized, though of course it should choose from a large character set that includes punctuation and special characters. The code to do this is about 5 lines long. If you're terrified of collisions then add the user name WITH the random string, but don't try to substitute it.
Aaron G on February 6, 2010 10:14 PMAll this talk of HASH's and Salts is great, but I have a lingering question.
Whatever you choose to do, you have to send something when you POST your form to the server. If you are using a SALT + HASH, you would send your password in the clear in the form post. Obviously, not every site uses/needs SSL, so this POST is could be intercepted.
Am I missing something? You cannot do the hash on the client and then send the hash, as then all the cracker has to do is send a matching hash , as its no different than sending a password in the clear.
What is the best way to send a password to the server for it to do a hash comparison anyways?
@Richard H.
No, I think your wrong. Nobody really cares if the salt is public. As long as it increases the length of the hash to the point where a rainbow table would have to be several terabytes in size then the cracker would be faced with a huge amount of effort to generate the table, as well as store it.
You can store the salt as normal value in your database column, generated for each user when they sign up, like:
SALT | HASH | USERNAME
Then, when you receive the password (apparently, in the clear, as my above question asks, thats what I'm not clear about), you do this:
SELECT SALT, HASH FROM authenticationTable WHERE USERNAME=@UserName;
Then Super Psuedo Code:
Salt = GetFromSQLQuery("SALT");
Hash = GetFromSQLQuery("HASH");
If ((Salt + hash(password)) == (Salt + Hash))
{
User.IsAuthentic = true;
}
The Salt is simply to make the values in the hash column obscenely long from what I can tell.
Jon H on February 6, 2010 10:14 PMErrr, my above code should be:
If (hash(Salt + hash(password)) == Hash)
etc
Assuming you store your salt in the clear, and the hash column is a hash of the salt and the password, not a concanation.
At least, thats what I believe we should be doing here. Otherwise, if the cracker had managed to get this table, it would be very simple to deconacate the salt from the hash's, and then build a table.
Jon H on February 6, 2010 10:14 PM@John,
I, like Richard, see the need for the server to send the SALT to the client, otherwise you will be sending either the password itself, or the hash of the password in the clear, which is easily picked up by someone intercepting the HTTP Post.
However, I am not sure how I would go about making that more secure. It seems to me like all this discussion is about server validation, it makes sense more or less. However, we are still sending either the password, or a hash of the password effectively in the clear. It makes me wonder if the only true security is using SSL.
Jon H on February 6, 2010 10:14 PMHmmm. I wonder how our project stores passwords. I better start asking around now. Security is everybody's concern.
jon on February 6, 2010 10:14 PM@Tjerk
"Salting password is not neccesary, an encryption algorithm like SHA is enough.. and offcourse a minimum length for the password."
You might want to reconsider. Any sort of hashed one way encryption can be cracked by finding any string that reproduces the same hash. While time consuming, it is not difficult.
Salting makes the generation of precomputed hashes difficult by increasing the size of the stored hash. A good salt means that a rainbow table to crack it would go from 3-4 gigs (for a simple SHA hashed password) to several terabytes in size.
Jon H on February 6, 2010 10:14 PMWow, a lengthy post, 80-something comments and still more confusion than clarity.
One comment tells you about all you need to know:
This is what a password looks like on disk in a Unix system:
$1$RsDzxp4K$y6ARJtx/TRDYnPWp.3vGy/
Three pieces of information delimited by $ signs. "1" is the hash algorithm (MD5 when using crypt() function as in this case). "RsDzxp4K" is the salt (just a simple random string). "y6ARJtx/TRDYnPWp.3vGy/" is the result of hashing the plaint text password with the algorithm specified and using the salt. This can all be stored in one field in the DB (no need to split into separate columns). It's useful to store the algorithm specification with the password to facilitate switching in the future. Bcrypt is better than SHA which is better than MD5.
The comments to this entry are closed.
|
|
Traffic Stats |