All 32mil RockYou accounts hacked. Passwords were stored in plaintext.
techcrunch.com
techcrunch.com
The right answers to this problem are BCrypt, SCrypt, PBKDF, or (at a minimum) salted "stretched" SHA1, iterated many thousands of times.
But too many people forget that before the popular Windows cracking tools were released, passwords were cracked exclusively by tools like JtR, which simply contain highly optimized loops for brute forcing passwords. "Salts" do nothing to slow this attack down, because they add insignificant time (really, none) to a single hash iteration.
Hashes like MD5, SHA1, and even SHA256 are designed to be fast. To succeed, they need to be able to handle multi-gigabit per-packet hash rates. They are designed explicitly both to be fast on general-purpose hardware and to be straightforward to optimize in purpose-built hardware. This is a bad, bad property for a password hash.
BCrypt is "optimized" to be slow - tuneably slow. SCrypt improves on BCrypt by being both slow on general purpose hardware and resistant to simple hardware speedups. PBKDF and "stretched" SHA1 are very simple contructions that are weaker than BCrypt and SCrypt but more buzzword compliant. All of them make it difficult to recover hundreds of thousands of passwords from a database dump.
See:
http://www.matasano.com/log/958/enough-with-the-rainbow-tabl...
Merely choosing a slower digest doessn't help, because the digest would have to be extremely slow (minutes or hours per password) to prevent somebody from running it against "password", "12345", etc for every row in the table. And that's the only kind of attack worth preventing -- if somebody can brute-force a 20-character alpha-num-symbol password in realtime, your choice of digest algorithm is irrelevant.
t1 = Time.now.to_i
100.times { BCrypt::Password.create("ugh8&eat") }
puts Time.now.to_i - t1
=> 12
t1 = Time.now.to_i
100.times { Digest::SHA1.hexdigest("ugh8&eat") }
puts Time.now.to_i - t1
=> 0
I could raise the number of iterations to bring SHA1 above the measurement floor, but I don't want to lock my computer up with pointless BCrypt cycles.In contrast, assuming a 40-character (20 in config, 20 in database) alphanumeric salt, an attacker would have to perform 704423425546998022968330264616370176 digests per row to check 32M passwords.
Unless you believe that is an insufficient barrier, implementing BCrypt is merely degrading the user experience (12-second logins? come on) for no real improvement.
t = Time.now.to_i
1000000.times {Digest::SHA1.hexdigest("ugh8&eat")}
puts (Time.now.to_i - t).to_s
=>3
Sure, 120ms is a bit long, but I think it would be beneficial to security to require more than 3µs.Edit: I match my parent's problem with 32M checks taking 12 years with my personal computer taking 132.86 seconds to calculate 32M SHA1s.
Increasing the digest time prevents an attacker with simultaneous access to the server and database from cracking very weak passwords, but at the cost of tripling or quadrupling how much time each request takes. There are some cases where this could be useful -- for example, running a dissident website in an authoritarian country -- but it's user-hostile to implement it anywhere else.
You're basically just splitting the passwords into two components--two-factor security--except that two-factor security requires two separate concepts, not simply requiring two passwords.
If an attacker can achieve a root login to your server, then they can read your config file, but they can also just change the login form to email them passwords. For this reason, most password defense is aimed at the assumption that the attacker gained access to the database server, but not the webserver. Historically, this has proven to be a safe assumption.
I kid, but I really think you and tptacek just have different standards. tptacek's is higher.
Well, that and the obnoxious trick question.
You do put them on a web server. It just happens to run software optimized for extracting a particular entry from a large data set :)
Ironically the way you suggest is more secure: because to compromise it (assuming the web app is out of the equation for the moment) requires a system exploit. Whereas you have the added complexity of the database as an additional weak point.
EDIT: im confused about the downvote.. what in particular appears wrong (so I can explain it). Having Database software certainly lowers security on any system :) it's another failure point (any good security text book will explain that)
If you have a table of 32 million hashed passwords with no salt (or they have the same salt and you know what it is), you can try a bunch of combinations, take the resulting hash, and look it up in the hashed password database. If you try the password "foobar" and any user has the password "foobar", you've found a username/password pair. Because there are 32 million potential matches, your chances are pretty good of eventually finding a bunch of matches by iterating over a bunch of potential passwords.
On the other hand, if you have a table of 32 million hashed passwords and salt combinations, where the salts are unique, you have to check the hash of "foobar" in combination with every user's salt before you can say that no user has the password "foobar". This is 32 million times slower, which is a significant difference regardless of the speed of the hashing algorithm.
If you want to brute force a particular user's password, the unique hash doesn't matter, but if you want to maximize the number of username/password pairs you can get, it seems to me like it would.
It is true that not even bothing to randomize your hashes is worse than doing so. But when we're talking about degrees of grave badness, I stop being super interested in the conversation.
If, for reasons passing my understanding, you are attached to the idea of using straight SHA1 to hash passwords, iterate SHA1 1000 times. That takes 2 extra lines of code (the opening and closing of the for loop) and significantly improves your security.
Let me put it this way: give me a database of 32 million username/password_hash combinations and the hash(password) function used and I can give you a valid username/password combination fairly quickly. Even if the hash function takes 10 seconds, because chances are one of those 32 million users has used "password" as a password, and it will only take me 10 seconds to compute the hash and find out which ones did.
If instead you give me 32 million username/password_hash/salt combinations, and the hash(password, salt) function used, just to list the users who have the password "password" will take 10 years.
The attack you're talking about --- searching for a radically reduced set of passwords --- is so fundamental to password security that you don't need any crypto to do it. Just open 50 concurrent connections to the login page and rip through the user list. At the same time, with the default BCrypt cost factor, just doing the password 'password' takes 1000 hours against a 32MM row data set.
Thanks, that's where my misunderstanding was. I've seen "salt" used (apparently, improperly) in cases where the hash is the same for all passwords. Even the wikipedia article seems to imply that use case.
The other part of my misunderstanding was that I was thinking of BCrypt as a deterministic function. Looking at an implementation, it looks like multiple calls to BCrypt::Password.create with the same value can result in different values, unlike how sha1($x) = sha1($x) for any $x.
The computational expense of a brute-force attack against a hash is the lesser of (hash length, text length). Well-known hash functions (md5, sha*) are optimized for large blocks of text, which leads to two properties: 1) they are made fast 2) them being fast is not a problem - if the text is long the strength against the brute-force attack is determined by the hash length which is very respectable for sha256 or even sha512.
However, the passwords themselves are small - the typical password is 8 characters and assuming 7 bits per character you're looking at search space of 56 bit, at which point it doesn't matter if your hash is 64, 128 or 512 bit because your strength is 56 bit. The search space can be further reduced by accounting fr various password selection biases, for example phonetic bias, tack-number-at-the-end bias, keyboard layout bias and so on.
To deal with small search spaces we need algorithmically slow hash functions - their slowness is not a problem because text is small, but it's a benefit against the brute-force attack.
Which is how it should work. Users shouldn't have to pick absurd passwords when the computer can do a better job of obscuring their password.
(Note: "them being fast", for "them" in SHA1, SHA256, etc, is not even considered a "problem"; it's considered a "huge feature", because these things are protecting individual data packets. It just happens that this primitive by itself is not useful for protecting passwords.)
So how much time does it normally take you to get this message across?
Even a small change in the message will (with an extremely high probability of 1-10^(-154)) result in a different hash, which will usually look completely different just like two unrelated random numbers do.
http://en.wikipedia.org/wiki/Whirlpool_(cryptography)
I used it for a while, then learned that using a fast hash, even looped plenty of times, is a bad idea for password storage when you have easy access to B/scrypt.
http://msdn.microsoft.com/en-us/library/system.security.cryp...
I did not find a bcrypt/scrypt implementation in standard .NET and I would be reluctant to use third-party security code I googled up somewhere.
I don't blame you for not wanting to use unverified third party security code though. RFC2898 is fine.
http://en.wikipedia.org/wiki/Salt_%28cryptography%29
Edit: On second thought, I might have misread the question, perhaps you're intending to store the unique salt for each user, although that seems impractical to me since it would have to be just as obtainable as the password database.
The parent poster has the right idea. For passwords, use bcrypt, scrypt, PBKDF, or a stretched hash.
I'm particularly curious about how using (for example) BCrypt will prevent brute-forcing "12345", "password", or any of the other simple strings many people use.
The second point is valid but not particularly meaningful. You could also try "12345" and "password" over the network using the login page as an oracle, given a list of usernames.
But with SHA1, you can feasibly do large combinations of phonemes with numbers and punctuation across millions of accounts. People with passwords like "ugh8&eat" will lose their accounts.
You can tune BCrypt so that just "12345", by itself, is too painful to run across 32MM rows to be worth a PR stunt.
For example, I just generated a salt/digest combination for a simple example password. It is digested using SHA-1, and the password is four ASCII digits. Here is the database row -- '|' is a delimiter between the fields.
algo | user_salt | digest
----------------------------------------------------------------------
sha1 | lyhsus1eizh815xz69pv | d418a6088847d6f2e5b0e3d2ecf2e300454a0885
If you (or, anybody) can determine the original password from this database row, I will mail you a check for a thousand dollars.It takes 5us on this system to do one SHA1 calculation. You're not using ASCII "digits" (if you were, I'd know, because brute forcing all the 4-digit numeric strings took less than a second); you meant characters. There are ~130MM 4-digit character combinations in standard ASCII.
Maybe I'm misunderstanding something about this challenge, but, if I'm not, post the code you used to generate this comment so I don't waste my time attacking a salt that is secretly Base64'd, and in 10 hours, you can mail your check to Amnesty International.
Does that mean that doing so is OK, except for the read any file breach?
Because I've been doing that cookie authentications. Database and file reads are not something I'm worried about (for this situation), but I don't want the cookie to be easy to crack.
I.e. auth = sha1('long secret string' . 'user_id' . 'password') /* I include the password to invalidate existing cookies if someone changes their password */
I then set a cookie to the user_id, and another cookie to auth, and check them on every page.
Also, I'm not him, so I'd like to know why doing the config salt second instead of first is bad. I thought you could do a partial hash, i.e. hash the config salt once, then just keep updating it with the rest. Meaning, the config salt should go second, not first.
If the config salt is the second term hashed, the scheme has a basic crypto flaw, one you can't make if you just use BCrypt or PBKDF like a reasonable developer instead of designing your own vanity scheme.
If the password is changed, all session identifiers must be invalidated. If it's a session table, delete all entries, if it's a hash - well your hash had better include the password somehow (a hash of the password hash, i.e. the one stored in the db, is fine, as long as changing the password changes the final hash).
i just don't see it as a good idea to have the password be one of the inputs to the value of the session identifier in any way, which was my point.
you can handle every scenario you bring up without tying password to session id.
Isn't it up to the attacker to figure out the rest of the details? As an attacker, do you think he has a separate config salt in a file or a separate table or secondary system? That's why you DO spend 10 hours on attacking it if you want the results. His posting seems quite legitimite to me.
If your second graf is valid, then it is equally valid to say that rot13'ing your passwords before you hash them is an effective security measure, because you're right, I'd never guess you'd be that dumb.
"The config salt isn't present in my post -- I'm assuming a database breach, like that which usually occurs. The digest does include a config salt, of course -- this isn't the '70s."
I do agree this is a learning exercise. I also agree that as an attacker you still have to make a lot of presumptions such as maybe they did use a keyed SHA1 process or use Base64. Again, why is this not a legitimite question? He was not trying to trick you anymore than an attacker would have to guess/try/reason what the dev/secofr implemented.
You're a post count leader here but either I am overly sensitive to reading snarkiness or I simply don't understand. I have no problem admitting I'm not a security expert but again there are some good questions from others here. Help us out here.
But who cares? This is silly. Secure password schemes don't need to protect a key file to avoid being broken, and they don't need you to know how Merkle-Damgaard works to implement without blowing up. Any password scheme that has a "config salt" is a vanity scheme. Adding a "config salt" is less secure than literally just looping 1000 times around SHA1.
Security schemes designed as exercises in vanity have a poor track record.
On a different note, why aren't more webapps tracking login attempts? Is this too difficult to implement based on simple but sane "timeout" rules?
Also, if an attacker does have access to the db through an injection and is able to dump/download the db what is really stopping the attacker from using a multitude of cheap machines? I'm just a small shop and I turn up 19 or 20(half cab)u dual density servers(16 cores/u) every few months. So I'm looking at 320 cores that a 32 million record database distributed over is actually pretty small. Does any kind of encryption matter at that point? In this day, that kind of processing power is cheap.
When you start worrying about attackers who will lease massive numbers of servers to crack your passwords, you dial up the cost on your hashes. You can do that transparently with BCrypt, migrating users as they log in. Every tiny incremental increase to the cost of a single login you make creates drastic cost increases for your attackers. You are on the right side of the scaling problem, the attacker is on the wrong side, and you can play the game indefinitely.
Because you've been assuming that the database will be breached and that no other kind of attack can happen.
Facebook does not permit developers to store private info without special permissions, which I've never seen apps of their ilk ask for. So while this hacker could maybe see what digital birthday card was sent to whom, that's probably about the extent of the damage.
Because it's not seen by "management" or "VC's" as a priority. Extra programming is seen as a time and monetary cost, not a necessity. One company I worked for used plain-text for customer passwords and mandated Microsoft technology in the same breath. When I talked to the lead tech, I got a shrug. Not because of lack of technical smarts but politically tied hands from management. When I contacted the MBA minted CEO I got a blunt - "it's not a problem, don't worry about it".
A physical loss is perceived more
tangible than something ephemeral.
Things did happen when people walking off the street stole "stuff" from the office. Swipe cards were quickly introduced. I'd broached such arrangements before the incident but that wasn't seen as a problem either.They trust people and credentials -- it's not about the technical details. There's a great chapter in Randall Stross's "EBoys" about TriStrata -- which raised millions based on essentially nonsense claims of an "unbreakable" encryption technology.
(The chapter is excerpted at the bottom of this message: http://www.xent.com/FoRK-archive/april00/0460.html )
Why do you want to know?
Edit: I picked this example carefully. Someone who knowns nothing about plumbing will not be shocked at this, having no idea what it is (until it's explained to them). Just like someone who is not a programmer will have no idea what the problem with plain text password is (until it's explained to them).
PS. A drain without a vent (or at least an anti-siphoning device) can be life threatening.
I don't mean this to come off as ad hominem, because I don't think it was your intention to leave the opposite side of the argument open and I think you personally understand the issue. I just think that we shouldn't have a mentality of "aww, we need to take special care of these unsavvy users." Not having basic levels of security in place for any user-base is simply unacceptable.
The conclusion was that they must be storing passwords plaintext, although that's not necessarily true. IIRC, your opinion was that this was not a big deal in the grand scheme of things.
But if we're comparing one password storage mechanism to another, I get religious fast. There's a right and a wrong way to do it.
37s did it the wrong way. Now I understand they do it the right way. Good for them!
37s has a great track record of listening to and acting on community feedback, which is all you can really ask for. I second your kudos.
EDIT: I'm specifically referring to DHH's "Agile Web Development With Rails", Chapter 11, Administration
We've covered 37signals poor approach to security before: http://news.ycombinator.com/item?id=804257
It's my understanding that they've since reformed.
I'd be interested in what books you think they've written that provide credible security advice. Blind "fanboyism" (if I may invent a word) is a bad thing.
def self.encrypted_password(password, salt)
string_to_hash = password + "wibble" + salt # 'wibble' makes it harder to guess
Digest::SHA1.hexdigest(string_to_hash)
end
They are using SHA1 with a salt, the exact method that security experts recommend against when storing passwords: http://news.ycombinator.com/item?id=995645. And obviously they didn't even use this insecure method internally.EDIT: tptacek makes a good point. I haven't quite been fair. 37signals did what most companies never do when confronted with a security issue: they quickly acknowledged the problem, fixed it based on the suggestions of the security researchers, and moved on.
From what I understand however, AWDWR is only tangentially related to 37signals.
And, like I say every time this comes up, FedEx and several banks also store plaintext passwords.
37signals no longer stores easily attacked passwords.
I was intellectually honest. I explicitly said they didn't actually lose them.
37signals no longer stores easily attacked passwords.
I was under the impression that this was true as well. However I just checked and I got my Backpack password emailed to me in plain text. So at least the Backpack application is still incorrect.
The "honesty" comment wasn't directed to you, but it's obvious why you would think it was. Sorry.
Given your history of quality and logical posts, I pretty much knew the comment wasn't directed towards me. I just wanted to go on record to be sure. I'm sorry as well, I should have just let it fly.
Either they haven't gotten to you in the rollout yet, or they aren't doing Backpack (which would surprise me).
They're launching unified accounts soon for all services (you have to pick a new user name and password if I remember correctly). Perhaps this is when they'll roll out the password security? Seems like a logical time to me.
(NB: I'm friendly with several 37s people online, and I've talked to them about what they're doing, and while I'll leave it to them to talk up their security, I think they're OK on this issue now).
[ignore] The second link is about a security problem in ruby on rails. Unfortunately ruby doesn't have proper utf-8 support so ruby on rails needs to monkey-patch the ruby string classes to provide proper support. It is unsurprising that there was a bug in this patching. This is a framework issue that has nothing to do with 37signal's security practices. Hindsight is 20/20. Frameworks always end up with security issues. At least you can see that it's been fixed for some time.
Okay, issue was with the lack of a secure channel. Point taken. That was a mistake.
[Removed section on procedures and mistakes, as non-relavent.]
(Disclosure: I use ruby on rails)
Using symmetric encryption to encrypt passwords in a database would not be smart. Where are you going to keep the key? If the hacker gets the dbase, then they've probably got the key as well.
There's no point in dancing around this issue. The only acceptable way to store passwords is a slow one way hash. If you get your password in plaintext, the security is lax.
The second link is about a security problem in ruby on rails. Unfortunately ruby doesn't have proper utf-8 support so ruby on rails needs to monkey-patch the ruby string classes to provide proper support. It is unsurprising that there was a bug in this patching. This is a framework issue that has nothing to do with 37signal's security practices. Hindsight is 20/20. Frameworks always end up with security issues. At least you can see that it's been fixed for some time.
The bad part about the second link isn't that the vulnerability happened, its that 37signals had no infrastructure in place for security researchers to report problems. Of course vulnerabilities are going to happen. It's a certainty. But you need to have a system in place for the good guys to report problems. I'm not aware of any informed person that criticized them for the actual vulnerability. If you read the link I provided, you'll notice this quote:
"It is literally the-simplest-thing-not-to-fuck-up. Nobody's asking you not to have security vulnerabilities. In fact: nobody's even asking you to fix vulnerabilities. We just need a reliable way to communicate with you about them."
They've got a communication channel now: http://37signals.com/security-response.
So, is there any less sensitivity (security-wise) regarding the key, instead of the password?
It's also possible for the encrypted passwords to be stored in a separate server that only provides a simple web service with two operations: update_password(email, password), mail_password(email) and store passwords hashed in the main database for authentication.
That said, it is far more dangerous to store passwords in a reversible manner than in an irreversible one. I was just noting it was possible to have modestly secure two-way storage.
Personally, I don't even want to deal with storing user passwords so I prefer using OpenID.
(b) Independent of all the other flaws that will get you arbitrary file read, if you don't know what the cases are where SQLI gets you arbitrary file read, you're probably not qualified to design your own password storage. I really mean that with all due respect.
I don't ever intend to get into the area of writing my own password storage until I learn a lot more in the area of security. I am only aware of how little I don't know about what I don't know.
CREATE TABLE foo ( x TEXT );
LOAD DATA INFILE "/etc/whatever" INTO TABLE foo ;
SELECT x FROM foo;
Did you know about INTO DUMPFILE in SELECT statements? =)