Take the 2-minute tour ×
Cryptography Stack Exchange is a question and answer site for software developers, mathematicians and others interested in cryptography. It's 100% free, no registration required.

My application can authenticate via openid and oauth (facebook, twitter, etc) and also with its own authentication system. I previously switched hashing from MD5 to SHA1 and during migration I had to customize so that accounts could verify both with MD5 and SHA1 and now I've removed the MD5 completely.

I read here somewhere that for passwords in fact SHA1 should not be used and bcrypt or something else instead since SHA1 is not specifically a password algorithm and instead is a hash function implementation while a password algorithm would have other more specific features. Can you tell me more about this? Should I switch from SHA1, is it important or premature / paranoia since I never even had a production security issue that I know? I can think of some possible actions:

  1. Staying with the current implementation (SHA1 and salt) since the main function of my service isn't user accounts anyway
  2. Passing the algorithm as a parameter to the encryption and thus being able to authenticate with different and new algorithm i.e. now using SHA1 and later its easy to switch
  3. Become an OAuth provider and then being able to add ourselves just like we add twitter, facebook and other OAuth providers
  4. Does the issue of "real" and "fake" salt mean a lot in practice or just for highest security? Am I using real or fake salt in detail, when the following is the implementation:

    def __encrypt(self, plaintext, salt=""):
        """returns the SHA1 hexdigest of a plaintext and salt"""
        phrase = hashlib.sha1()
        phrase.update("%s--%s" % (plaintext, salt))
        return phrase.hexdigest()
    def set_password(self, new_password):
        """sets the user's crypted_password"""
        import datetime
        if not self.salt:
            self.salt = self.__encrypt(str(datetime.datetime.now()))
        self.crypted_password = self.__encrypt(new_password, self.salt)
    def check_password(self, plaintext):
        return self.__encrypt(plaintext, self.salt) == self.crypted_password
    

Please tell me if you can comment.

Thank you

share|improve this question
add comment

3 Answers

up vote 7 down vote accepted

If your password database will never be compromised, you can store plaintext passwords and nobody will be bothered. The only thing about plaintext passwords is that they can be accidentally remembered by admins who see them - a simple base64 will fix that. Equivalently, MD5 or SHA1, with salt or without, is just as fine.

If your password database is possibly compromised at some point in the future, a single SHA-1 with salt still means that 99% of the user passwords are crackable on a laptop - basically no protection at all, except inconvenience. So if this is the attack you are worried about, you need to switch to something which has a sequential iteration count that isn't trivially small. PBKDF2, bcrypt and scrypt are all good choices for this.

(Note: assuming your password database will never be compromised is a really bad idea, if it wasn't already obvious from the answer.)

share|improve this answer
    
Thank you for the insights. Both code and password database could be compromised with email addresses and a user may use the same password for different services so I want to protect from brute force attacks. Now I understand what the weakness with SHA1 can be compared to crypt. I look at how django implemented passwords with python and django supports SHA1, old MD5 for backwards-compatibility and also crypt (from code.djangoproject.com/browser/django/trunk/django) –  909 Niklas Sep 5 '11 at 11:56
    
Actually, using crypt(3) doesn't actually fix the algorithm to anything specific - there are many different implementations of the UNIX crypt and some of them provide really weak security. Please see more at: en.wikipedia.org/wiki/Crypt_(Unix) and manpages.courier-mta.org/htmlman3/crypt.3.html Also, for example the SHA-256 and SHA-512 algorithms for crypt(3) are really weird: akkadia.org/drepper/SHA-crypt.txt I'm not sure if I can recommend those fully. –  Nakedible Sep 5 '11 at 18:57
    
If it wasn't obvious, import crypt in Python is just a wrapper for the crypt(3) function in the underlying platform - and as such, behaves differently on different UNIX systems. –  Nakedible Sep 5 '11 at 18:59
1  
The very very very old DES crypt was vulnerable to just about everything and ignores everything after the first 8 characters. The crypt(3) in modern Linuxes uses SHA-256, has no password length limits, has a configurable amount of rounds just like PBKDF2, uses a 16 character salt, etc. The same function on OpenBSD is bcrypt. crypt(3) is just an interface. –  Nakedible Sep 5 '11 at 20:11
2  
@Nakedible: Which would IMHO be a good argument not to use crypt - it may not provide adequate security on some systems, and there's no easy way to tell. –  Piskvor Sep 8 '11 at 13:47
show 3 more comments

The distinction between real and fake salt is arbitrary. I suppose that your method would be called "fake salt" by those who make the distinction (concatenation versus "separate input parameter to the algorithm), but there is not really a difference in practice, as long as the hash algorithm is secure.

A bit more secure (in the sence of "provenly secure") would be using HMAC(password, salt), i.e. HASH(key2 || HASH(key1 || salt)), where key1 and key2 are somehow derived from your password (see RFC 2104 for details).

But the real problem with using any fast hash function (like SHA-1) for password hashing is that it allows a brute-force attack by iterating only the password space (instead of a large part of the whole input space, i.e. on average $2^{\text{output size} - 1}$ tries to find a preimage). As SHA-1 is really fast (you can do millions of hashes in a second), and also easily massively parallelizable on GPUs, anyone who gets a copy of your password database can crack most of the passwords in a relatively short time.

Bcrypt avoids this by making the hashing slow. Based on Blowfish's key derivation function, it uses a configurable work factor, so you can make the login just that slow that your users still accept it, at the same time making it really hard for attackers to brute-force many such passwords. Bcrypt also uses some amount of frequently accessed and changed memory (4 KB), which makes it less easily parallelizable on GPUs (which usually do not have enough such space quickly accessible for each core).

A later development is scrypt, which makes the parallelization even more difficult, by also using a (configurable) large amount of RAM (which is nowadays the most costly resource for brute-force attackers, but available on normal PCs).

So, for your questions:

  1. If you can make sure that your user database can't fall in wrong hands, stay with your SHA-1. Since you in reality can't be sure, don't. (For example, often old backups are much less secured than the life data.) Even if the access to your service is not that important, users often reuse passwords for different services, and you don't want to be the one which will be blamed if one of your users' online-banking account gets hacked.
  2. Making the algorithm configurable seems like a good idea. You could have a look at how crypt stores the scheme identifier (and parameters, for some schemes) together with the salt and hash, as an inspiration.
  3. Becoming an OAuth provider yourself does not really solve the problem, it only shifts it to the part of the program which finally implements the provider. It might have its own advantages (having only one way of handling this instead of two), but these are not of cryptographic nature.
  4. As said before, the distinction between real and fake salt is not the important one.
share|improve this answer
    
Thank you Paulo for a great answer. Today I looked at how django has implemented a solution and they are making algorithm configurable: if algorithm == 'crypt': ... elif algorithm == 'sha1': in the method django.contrib.auth.utils.get_hexdigest so I can look how django allows different algorithms to be used (crypt, sha1, md5) and therefore I may do an import crypt similarly to django (code.djangoproject.com/browser/django/trunk/django). –  909 Niklas Sep 5 '11 at 11:46
1  
Actually bcrypt was designed by Provos ans Mazières, while scrypt is from Percival. Not the same author. I do not know where the confusion comes from, although I know that I used to be guilty of it, too. –  Thomas Pornin Sep 5 '11 at 19:24
1  
@Thomas: Looks like I misremembered or misunderstood the last paragraph in your answer to "Why can't one implement bcrypt in Cuda?", and didn't care to look it up. Thanks for the correction. –  Paŭlo Ebermann Sep 5 '11 at 21:24
add comment

Regarding your question about choice of password hashing algorithms, this has been covered well on this site. See the following posts for excellent advice:

In short, yes, you should use bcrypt, PBKDF2, or scrypt to hash your passwords.

(I differ a bit from other questioners who said SHA1 is fine if your database will never be compromised. History has taught us that database compromise is a real threat, and not one that you can be confident won't happen to you, so you should probably include fallback defenses against it. Since it is relatively easy to use bcrypt/PBKDF2/scrypt instead of SHA1, I suggest you do so.)

share|improve this answer
    
I agree. It worked being comptaible with SHA1 and MD5 at the same time so I suppose I can stay compatible with many algorithms and definitely use those that are specifically recommended as password hashing algorithms (not just a hashing algorithm but spec designed for passwords) –  909 Niklas Sep 9 '11 at 23:57
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.