33

On web applications I build I don't hash the passwords. Instead I symmetrically encrypt the username using the user's password and store the result in the password field. I use RijndaelManaged on dotnet.

On login I do it again and check if there is a match.

My question is, how secure is this now when more and more databases are hacked? I do not know at any given time the user's real password. What the hacker would get from db is the username encrypted with an unknown password. With hashing there is the rainbow tables problem, but with this method not.

Knowing the decrypted value already (username) makes it easier to find the password?

Rudy
  • 487
  • 4
  • 8
  • 30
    In broad terms this is the same as using a fast hash with the user name as a salt. Not horrifically insecure, but you would do better to use a standard approach. [Don't roll your own](http://security.stackexchange.com/questions/18197/why-shouldnt-we-roll-our-own) – paj28 Sep 23 '15 at 11:30
  • I don't understand, if you are using a different password exclusive to the server to encrypt the user name, and as you said you never know the user's real password, then how are you able to confirm a given login (with a valid user name) is being submitted with the user's valid password? – WDS Sep 23 '15 at 11:39
  • 1
    @WDS - On login I do it again and check if there is a match. – Rudy Sep 23 '15 at 11:44
  • @paj28 - I don't roll my own. I use RijndaelManaged class provided by Microsoft in .Net, even the implementation of that is from an example in a high circulation website. If it's as you say "Not horrifically insecure", I am OK with it. – Rudy Sep 23 '15 at 11:49
  • 5
    @Rudy - Although you're using a library for the raw crypto, you're still rolling your own scheme. I think this is explained if you read down the link, or it might be in another answer linked off that. – paj28 Sep 23 '15 at 11:54
  • @Rudy: You wrote >>even the implementation of that is from an example in a high circulation website.<< Can you provide that weblink? – StackzOfZtuff Sep 23 '15 at 12:18
  • When you can get out of the box implementations of things like `bcrypt` and his friends, why would you waste time thinking up different things? I'd rather let the experts tell me an easy and secure way to do it and be done with the whole thing. – corsiKa Sep 23 '15 at 15:50
  • 3
    Related: http://security.stackexchange.com/questions/73766/password-storage-self-encryption-vs-hashing – Rahil Arora Sep 24 '15 at 00:51
  • I understand that you store an encrypted version of the username. Do you also store the username in clear? If you store the username in clear as well, your scheme is a bit weak. If you do not store the username in clear, your scheme is very weak. – kasperd Sep 24 '15 at 17:39
  • Thank you all for your answers. Now I know it's not OK and on applications where I still have access and on the new ones I will create I will upgrade them to scrypt or bcrypt. – Rudy Sep 25 '15 at 18:57

3 Answers3

84

What you do really is hashing: you built a hashing function out of a block cipher. Incidentally, "encryption of a known value with the password as key" is how the traditional DES-based crypt scheme for Unix was designed.

On the plus side, your construction includes the user name, which partially provides the effect of a salt: parallel cracking is discouraged because two users using the same password will end up with two distinct hash outputs. "Partially", because upon password change, the user keeps his username, so some form of parallelism is still feasible here; and, perhaps more importantly, the same username may be used in several Web apps (e.g. 'admin'), which makes precomputed tables worthwhile (and the point of the salt is to prevent parallelism, in particular the efficient use of precomputed tables).

On the minus side:

  • This is a homemade construction. 50 years of research in cryptography have taught us that homemade crypto is very rarely good. In fact, we can "know" that a given algorithm is correct only through years of review by other skilled researchers, and homemade schemes, by definition, never got much of a review.

  • This is limited. Rijndael is a block cipher that can accept keys up to 256 bits; thus, if you use the password as key, then it must fit within 256 bits. This prevents using so-called "passphrases" which can be strong but easy to memorize passwords by using a lot of space for its entropy (human brains don't like concentrated entropy, but are happy with diluted entropy).

  • This is way too fast, and this is the biggest immediate problem in your scheme. With a basic PC, an attacker can try a lot of passwords per second, in particular since modern PC have dedicated opcodes for AES. With a quad-core PC, one should be able to try up to about half a billion of passwords per second. Not many user-chosen password can survive such an onslaught for long.

If you are interested in how password hashing works, then read this. The short answer, though, is still the same: don't use a homemade scheme; use bcrypt.

Thomas Pornin
  • 322,884
  • 58
  • 787
  • 955
  • Thank you, I will research more about bcrypt for dotnet. – Rudy Sep 23 '15 at 13:13
  • As you say in the linked answer, bcrypt shares the limited input problem. – otus Sep 23 '15 at 15:06
  • @otus: that's true, but with a higher limit (at least 50 characters, more for most implementations). – Thomas Pornin Sep 23 '15 at 15:29
  • 2
    @otus You can get around the limited input problem by performing a hash of the whole string in secure hashing environment that doesn't have the limited input problem to condense the entropy and bcrypting that. – corsiKa Sep 23 '15 at 15:53
  • I don't agree that using a fast hashing function is the biggest problem. I consider the use of the username as salt rather than a random string with high entropy to be a bigger problem. – kasperd Sep 24 '15 at 17:31
  • @kasperd: There is no requirement that salt be random if one can assure by other means that it is vanishingly unlikely that an attacker who knows the password associated with a hash+salt pair will ever find the same hash+salt pair associated with something else. Low-entropy randomness may not be sufficient, but high-entropy randomness will. On the other hand, there's no need for *any* randomness if uniqueness can be assured by other methods [username alone would not suffice, but username plus a unique host name would guard against everything but the ability to... – supercat Sep 24 '15 at 18:19
  • ...examine two password records which were generated at different times and see that they represented the same password (so if a user uses password "Foo", then changes it to "Bar", then back to "Foo", an attacker who could see the hashed passwords at the different times could tell the password was changed and changed back--information the attacker shouldn't have, but which is probably not directly harmful). – supercat Sep 24 '15 at 18:22
  • @supercat If the salt is predictable, an attacker can start computing hashes of common passwords before a database of hashed passwords is leaked. This will give the attacker longer time to find the password matching a specific hash than he would have in case the salt was unpredictable. If the salt is unpredictable, the attacker only have the time between the leak happens and the password is changed to brute force the hash. – kasperd Sep 24 '15 at 19:05
  • @kasperd: If the attacker knows the system and an account name of interest for which the database is going to get leaked, an attacker could make use of that information in advance of getting the database, but in most threat models I would expect the likelihood of that scenario to be too small to be of interest relative to other more serious risks. – supercat Sep 24 '15 at 19:09
  • @supercat The risk might be small. But the protection against it is so cheap, that there is no excuse for not doing it properly. It really boils down to choose the salt at random and make it as long as the final hash value. – kasperd Sep 24 '15 at 19:16
  • 1
    @kasperd: If there is no good source of entropy, using a "purely-random" salt may yield results inferior to combining username+hostname or username+hostname+passwordChangeDatestamp. Adding a little extra randomness to the latter form wouldn't hurt, but I'd consider it a small-cost-small-benefit proposition. – supercat Sep 24 '15 at 19:29
  • There's one other point that I don't know if it belongs in this list, but is worth mentioning: as a *developer*, you generally don't want to spend more time on each feature than you need to in order to ship the product. When a username field and a bcrypted password "just simply work" why burn client hours doing this? – corsiKa Sep 24 '15 at 21:01
  • @supercat You can take some bytes from every random source you have access to (even if they are not very good) and concatenate the results with hostname, username, and timestamp. A single hashing of the final concatenation will produce a good salt. But if your access to entropy is that constrained, then producing a good salt is the least of your problems. How would you even set up a secure connection to receive the password in the first place, if you don't have an entropy source? – kasperd Sep 24 '15 at 21:32
  • @kasperd: Many protocols require a nonce that is never reused but don't require that it be random. If one has a counter with a reliable "increment and report" function [which guarantees that a power loss which prevents the counter from being incremented will also prevent it from being reported], such a counter will be sufficient for that purpose. – supercat Sep 24 '15 at 22:14
  • @supercat You are not going to achieve forward secrecy that way. – kasperd Sep 25 '15 at 06:45
  • @kasperd: That is true. Different applications have different security requirements; if you want to ensure that nobody will ever be able to discover that you set your thermostat to 68F last Tuesday, forward secrecy may be important. If you merely want to prevent other people from changing your thermostat, it may be less important. I think shared-secret authentication can be secure against passive discovery, even brute-force, if *either* side is capable of supplying a source of entropy which is unknown to the attacker. I think man-in-the-middle is perhaps a bigger issue, but... – supercat Sep 25 '15 at 16:14
  • ...if both parties have a shared secret I would think one could send some entropy to the other who would then encrypt it using the shared secret and use that in establishing a session key, which could then be validated using the shared secret. A MITM attacker could send phony entropy, but without knowing the shared secret would have no way of knowing how to interpret the result, and if the attacker has the shared secret an attacker wouldn't need to use phony entropy. – supercat Sep 25 '15 at 16:21
11

The other answers already say No, "roll your own" is rarely a good password storage scheme and yours is no exception.

I'd like to add: It isn't a good scheme for storing/communicating the username either.
You're basically hashing. Nothing wrong with that except:

  • You're using a function not designed as a secure hash function. Being a cipher it's surely on the "secure" end of scale but hashing also involves avoiding hash collisions. A cipher isn't typically optimized for having no collision. One-Time Pads are known to be unbreakable, because they have the maximal number of collisions. So a strong cipher makes a poor hash in terms of collisions.
  • If you store username and username-encrypted-with-password there are more vulnerabilities:
    • In this case, if your database is hacked the attacker doesn't only get the username encrypted with an unknown password. He also gets the clear text aka. username. So it's a clear text analysis, which for some ciphers is much easier than brute force.
    • Hash collisions: The attacker doesn't need to know the password but any that produces the same encryption for a short and given clear text. This might be even easier than clear text analysis.
  • If you don't store the username but rely on a single combined transmitted value, there are still drawbacks:
    • In this case, you probably didn't consider hash collisions. What about two users, who have the same username? What about two idiots, who have the same username and the same password? What about hash collisions of different usernames and passwords by pure chance (remember: ciphers are not optimized to avoid collisions!)?
  • In any case there's the "Pass the hash" attack: An attacker sniffing network communication can simply emulate your application to pass the same data as the user. Did you do anything about that? If not, that's why you shouldn't "roll your own".

Often times the protocol of transmission is as important to security as a strong cryptographic function, independent of hashing vs. encrypting. A bad protocol can expose your function to attacks it's unsuited for, especially "side channel attacks" like "pass the hash" or "man in the middle" that are circumventing the actual crypto-analysis on the side of the attacker.

Your password storage scheme may be prone to such attacks, because you didn't specify, if the username is also stored in clear text. Doing so exposes your scheme to the attacks described above. Not storing it in clear text may require a transmission protocol prone to attacks as described above.

Here are some additional thoughts on transmission protocols in your case, slightly generalized:

If you didn't do anything about the "pass the hash" attack, then your scheme is worse than a whole lot of others. An attacker neither needs to hack your data base nor crack your cipher. He gets what he needs for free by sniffing network traffic.

HTTPS may suffice, although it can be worked around by "man in the middle": Users aren't checking for authentication certificates, are they? An attacker simply opens an unauthenticated but encrypted connection to your user and whatever is expected connection to you.

An additional Challenge-Response scheme may add security. Prior to login the client receives a one-time random string (challenge) that he's required to build into the hash (response). An attacker couldn't reuse a sniffed hash, because the challenge isn't reused either.
But again, no need to "roll you own".

NoAnswer
  • 111
  • 3
  • 1
    Welcome to Security Stack Exchange. Your answer shows some good writing skills and knowledge. However, there's some dubious parts. For example, duplicate user names would not normally be allowed. Can I encourage you to recheck your facts, and focus on the question. The question did not involve transmission of credentials, so HTTPS and "pass the hash" are not strictly relevant. – paj28 Sep 24 '15 at 11:55
  • 1
    Reconsider the indention of paragraphs: More indented bullet points are applying only in case of the previous normal bullet point, i.e. you can only check for duplicate usernames when storing the username in plain text => Plain Text + Hash collision attacks. The question doesn't specify, if the username is stored in plain text. Actually it indicates otherwise, because it claims hackers would only get the encrypted username. This is wrong, if username is stored. If username isn't stored the other problems arise, because of transmission. Not involving transmission can open any cipher for attack. – NoAnswer Sep 24 '15 at 12:08
  • Edited to clarify your points. – NoAnswer Sep 24 '15 at 12:23
  • Ok, that's an improvement. I still have issues with some of your claims, but overall this is a reasonable answer. Issues: Clear text analysis is not known to be a problem with Rijndael; nothing he has done is particularly bad for hash collisions; you've mentioned pass-the-hash repeatedly, but as a web app it's highly likely the server takes an unhashed password over SSL so PTH is not applicable. Anyway, don't worry too much, I've been accused of being a pedant in the past :-) – paj28 Sep 24 '15 at 12:39
  • I'm being accused a pedant regularly. I'm proud of it. That's why I think you can't leave anything out unless it's clearly specified to not be an issue. The question specifies neither storage of username nor transmission. So there might be an issue. SSL should be ok, too. HTTPS is problematic for unauthenticated connections. If that's a problem, depends on interpretation of "web application". I didn't expect clear text analysis to be a problem. Still ciphers are rarely optimized to reduce collisions. Those are just points to be considered, which could be skipped by not "rolling your own". – NoAnswer Sep 24 '15 at 12:55
  • Ok Mr Pedant, I think you'll fit in round here :-) I wouldn't normally comment so heavily, but your post popped up on the First Post Queue. BTW, when there's missing info from a question, you can comment on the question and request additional info. Anyway, you've won me over, have your upvote! – paj28 Sep 24 '15 at 13:01
  • Let us [continue this discussion in chat](http://chat.stackexchange.com/rooms/29490/discussion-between-noanswer-and-paj28). – NoAnswer Sep 24 '15 at 13:04
5

TL;DR: use scrypt. Why roll your own when there's a perfectly good standard?

(I consider scrypt superior to bcrypt, but bcrypt is still miles better than grow-your-own.)

One thing you're missing so far is stretching. Imagine you get hacked and your database gets dumped on pastebin - it can happen to the best of us.

The people who use 123456 or one of the other top 20 passwords will get found out whatever you do, and they deserve no better. For the people who've choosen moderately good passwords, it makes a difference if the time the attacker has to spend per entry is on the order of microseconds or seconds.

You're also not salting - imagine that two of your web applications get hacked and one person has accounts under the same username with the same password for both of them. It sounds to me like the attacker will get identical ciphertexts in both dumps, telling them that there's a crack-one-get-one-free offer.

  • My one comment on the final paragraph is this - use a different Salt for each site so two sites using the same technique wouldn't have the same data :-) – Rycochet Sep 25 '15 at 08:27
  • @Rycochet: Um, you should always be using a different salt **per-password** (randomly generated via a secure random number source) not per-site. – R.. GitHub STOP HELPING ICE Sep 25 '15 at 16:52
  • True - but you still need to generate a salt in a repeatable way - so if that part is generated then the way it's generated should be different - so long as it's not the same salt on the two (or more) sites then that's the goal, regardless of the method used ;-) – Rycochet Sep 25 '15 at 22:00
  • Repeatable? I'm not sure I understand. I usually draw some bits from /dev/random and store them in a separate column next to their name; when someone logs in, use the name to look up their row and read out the salt. No need to regenerate it. –  Sep 26 '15 at 20:21