Note that if you store your passwords in a fast hash format like salted SHA1, an attacker with 32MM database rows can publish a very credible sample of usernames and passwords, and leave you scrambling to explain how the breach isn't really as bad as it looks. Not a great position to be in.
The right answers to this problem are BCrypt, SCrypt, PBKDF, or (at a minimum) salted "stretched" SHA1, iterated many thousands of times.
The "salt" --- which, so far as I can tell, is a term used almost never in academic crypto research --- defeats one very effective exotic attack: the "rainbow table", where an attacker builds a database of perfect hash :: string correspondances.
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.
I certainly get why a slower hash would curb brute force attacks, but when I'm talking about unique salts, slowing down the hash is not the purpose I have in mind.
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.
Every time I bring this up, which is often because it is, apparently, all I ever talk about here, people misconstrue what I'm saying as some kind of argument against "salted" hashes.
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.
I've read your explanation of this problem several times in the past and only now I figured out (I think) the problem you are pointing out. This seems to be substance of your argument, as I understood it:
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.
Yes. Think also of it this way: poor user password selection (and here we mean "ugh&8eat" is a weak password) sabotages the complexity of the attack, and adaptive hashing (like SCrypt) fixes that problem algorithmically.
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.)
But we force users to choose obscure passwords to discourage dictionary attacks (so I thought) not to protect them in case our dbs are hacked. A computer may be able to obscure a password better than the average user but at the end of the day the user still has to commit that password to memory, which may be why people stick with the same simple passwords to begin with.
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.
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.
I'm not misconstruing your argument as against salted hashes, but as downplaying the advantages of using a unique salt over an install-specific salt (not sure that's the best term, but I'm referring to the type of salt you might find in a config.py file).
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.
"Salts" have been password-specific since Ken Thompson invented them in the '70s. We're not talking about different approaches: the approach you're talking about is unsafe.
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.
> "Salts" have been password-specific since Ken Thompson invented them in the '70s.
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.
My understanding was that hashing a hash was stupid because you've effectively limited your attacker's search space. That is, in the beginning the password or pass phrase can be any length (probably up to 255), but after hashing you've restricted it to a known length composed of known characters. Therefore, you've decimated the overall search space. It seems like iteratively rehashing your hash 1000 times would make things significantly more insecure.
"salting" also prevents brute-forcing. Assuming a bare-basics implementation (20-char salt in config file, 20-char salt per user, prepended to the password), the only way an attacker can brute-force passwords in a reasonable timeframe is to compromise the server itself. At that point, it would be just as easy to make the login form mail him passwords.
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.
If you can't understand the significance of these two results (and this is with the default cost factor for BCrypt), you're just arguing to hear yourself talk.
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.
Using these numbers, it would take your computer roughly 12 years to check 32M passwords against that string. That's a long time, but feasible (with additional power), and more specialized systems can bring it within reach.
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.
There are lots of ideas which are beneficial, but not particularly useful. For example, requiring passwords to be at least 120 characters would (in theory) make passwords more difficult to compromise, but in practice users are just going to type "password" 15 times.
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.
Calculating a BCrypt hash with the default cost factor takes about as long as reading an uncached file off a conventional filesystem. What a silly thing to try to optimize. Really? It's killing you to spend 100ms on password hashing? Ok, dial it down to 50ms. BCrypt is tunable.
His computer is taking 12 seconds for 100 iterations.
The time spent checking the password would be more like 120 ms, totally in the order of a decently fast http request.
If the attacker can get the list of password hashes, what's stopping him from getting your salts too?
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.
Salts are usually stored in two components; one is in the database and unique per row, to prevent cracking multiple rows at once. The other component is in a config file in the server -- it adds a constant number of bytes to the input for any brute force attempt.
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.
If your webserver machines are so secure, why put them in the database at all? Just store the passwords on disk there, rsync the password file across all machines and you're set.
I kid, but I really think you and tptacek just have different standards. tptacek's is higher.
Lots more web app flaws allow you to read arbitrary files than allow you to get a shell on the app server. There are cases where SQL Injection by itself is sufficient to get the contents of files. I think that's what bothers me most about using SHA1(k, nonce, password) as a hash.
If your webserver machines are so secure, why put them in the database at all?
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)
I Am Not A Cryptographer, so take this with a grain of salt. Using a unique salt for each user "helps", but is still insecure. The problem is that hash functions such as SHA or MD5 are designed to be very fast. This means that even with unique salts it is still fairly trivial to try a large number of passwords on a large number of users and crack many or them.
The parent poster has the right idea. For passwords, use bcrypt, scrypt, PBKDF, or a stretched hash.
The salt is used for the original encryption, after which the plaintext is thrown away, then when you need to compare what the user enters next time, you use the same salt with the password entered every time.
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.
What's wrong with using a salted hash? Assuming a competent implementation, it should be computationally infeasible to brute-force any digests in less than a few billion years using current models of computation.
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.
That first paragraph is a funny statement, since every Unix security book ever written explains how to use Crack, a tool written before I even got into high school, to rip through Unix salted passwords. I don't think it even bears arguing.
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.
I said "competent"; the UNIX crypt() and Windows LanMan schemes do not count. If you have any information about defeating a properly implemented salt+hash system, I (and every other developer in the world) would be eager to hear it.
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.
[Updated: You're not using SHA1, you're using keyed SHA1 with a secret random key. Presumably it's long, because you made your per-password salt ludicrously long, because you don't really understand how salts work. Note that every "read any file the web app can" vulnerability on your system is now "recover tens of thousands of passwords". It would be extra funny if you hashed your config file salt second instead of first, but I'll leave it up to the experts on your team to figure out why that is.].
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.
> You're not using SHA1, you're using keyed SHA1 with a secret random key.
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.
You are sending a crackable password hash on every request. Stop doing that. Create a session table, key it off a 48-byte random string from /dev/random, and store authenticators in that. In other words, JSESSIONID or RACK.SESSION or PHPSESSIONID or ASPSESSIONID are all stronger approaches than the one you've chosen. You are going through extra effort to make your system less secure.
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.
thanks. every time someone uses the password as input to some "secure" hash, i cry inside. why is the password even relevant to a user session identifier? it's not.
And the next time someone logs in as you, and clicks "keep me logged in" - and you have no way to un-log them in, you should also cry inside.
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).
Jmilikin already stated the config salt is not present, that the password IS 4 ASCII digits and he assumes a database breach.
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.
This is a learning exercise, not an attack. Posting some details is quite reasonable. I might spend lot of time on a real attack, if I want the passwords, but I won't for an exercise.
He stated the opposite of what you said, so I wasted 0.9 seconds of runtime and 15 seconds of coding time verifying that he was not crazy enough to promise $1000 for trying 10,000 SHA1 iterations, which at the time seemed possible. He owes me 0.4% of my bill rate!
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.
Actually I thought I paraphrased it pretty well but to I guess a direct quote is better.
"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.
He implied he was using SHA1(nonce, password). He was actually using SHA1(k, nonce, password). You're taking a later clarifying comment he made and adding it to the main comment.
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.
He said he was using SHA1 which to me is not an implication. Is SHA1(k.nonce,password) not a form of SHA1? All you had to do was say, with the information available, you would not have been able to determine the password.
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.
It takes 10 hours for two fast cores to attack one single password across 32MM BCrypt hashes with the default cost factor. What does 320 cores get you? 160 passwords in 10 hours, or 0.06% of /usr/share/dict/words. No, you cannot simply scale your way to a BCrypt cracker using cloud servers.
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.
It's traditional to also including a testing row, revealing the password (along with the digest), so that people can verify that their cracking code works.
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.
It's funny -- I remember a self-appointed security guru here saying that the fact that 37 Signals doesn't encrypt passwords was perfectly fine, a minor detail. You can't half-ass basic security measures. It's like leaving your car unlocked in east Oakland.
Wait a sec, something is wrong here. I suspect the title is misleading. RockYou has maybe a couple thousand developers tops using their services. They might have 32 million Facebook customers, but those don't have passwords. They're Facebook app users.
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.
This is just some amateur hour shit. They're a well established and well funded company. How do venture funds not do better technical due diligence(or any at all)? I wouldn't release an app for 32 of my friends to test, let alone 32 million people to use with plain text passwords. Enough of my bitching, because it's inaction. Now for words of action/openings for suggestion: How do we make sure companies don't do dumb things like this?
"... How do venture funds not do better technical due diligence(or any at all)? ... How do we make sure companies don't do dumb things like this? ..."
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.
The few times I've been through VC and acquirer due diligence, the actual technical depth to it was extremely minimal. They aren't validating your design; they're just determining whether you can actually build working stuff.
How do venture funds not do better technical due diligence(or any at all)?
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.
I'm not shocked. As I just wrote on my Posterous, RockYou has consistently shown themselves to be just amazingly stupid. I've had the misfortune of dealing with these morons for over a year now. This is far less bad (since I use Roboform) than the time they CC'ed me in with thousands of people on a Merry Christmas email.
What's the plumber equivalent of storing passwords in plaintext, the things where if you heard that a plumber did X, you would be in complete shock at their negligence?
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 agree with you, in that you're saying it's silly for RockYou to assume their customer's passwords are unique to their service, but I vehemently disagree with your unstated assumption--that 99.99% of other communities aren't just as likely to have users pick dumb passwords.
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.
They didn't lose them, someone just noticed that they would send you your original password if you requested it and made a blog post about it.
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.
I said it wasn't a vulnerability, or, if it was, it was a minor one compared to a real vulnerability; sev:low compared to sev:medium for a stored XSS flaw and sev:high for SQL Injection. And I believe that.
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!
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.
There is a difference between storing plaintext passwords and actually losing them, and as much as I hate to give someone a pass on insecure password storage (it is, apparently, all I ever talk about here), you have to be intellectually honest.
And, like I say every time this comes up, FedEx and several banks also store plaintext passwords.
37signals no longer stores easily attacked passwords.
There is a difference between storing plaintext passwords and actually losing them, and as much as I hate to give someone a pass on insecure password storage (it is, apparently, all I ever talk about here), you have to be intellectually honest.
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.
They're doing it as we speak. They had a 5 hour down time that stretched out to 10 hours on Saturday night getting systems migrated; they're announcing batches of accounts converted in Twitter over the week.
(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).
Both of those links are not proof of any lax security at 37signals. The first link makes the assumption that the ability to email a user's password means the passwords are in plain text. There is such as thing as secure two-way encryption. (Granted, if the hacker gets the encryption key, you're hosed.) You can read more about that in the comments on that blog.
[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.]
The first link makes the assumption that the ability to email a user's password means the passwords are in plain text. There is such as thing as secure two-way encryption.
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."
It's more common to get access to the database through sql-injection rather than hacking the server. So having the encryption key on disk is more secure than plain text in a database but not much more.
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.
(a) You don't have to "hack the server" to read a config file that the web app can also read.
(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.
Yikes, I didn't realize databases included the ability to edit the filesystem.
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.
37 Signals is on record as having stored passwords in plain text. They thought it was just fine at the time. I'm glad to hear they finally caught up with the new millenium.
I'm guessing tree5 is talking about "Agile Web Development With Rails," in which the authors do give examples of creating hashed passwords with a unique salt and running a SHA1 digest. The book also talks about preventing plain text passwords from showing up in log files from form submissions.
From what I understand however, AWDWR is only tangentially related to 37signals.
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.
The right answers to this problem are BCrypt, SCrypt, PBKDF, or (at a minimum) salted "stretched" SHA1, iterated many thousands of times.