[ACCEPTED]-Encrypting/Hashing plain text passwords in database-security-by-obscurity

Accepted answer
Score: 50

EDIT (2016): use Argon2, scrypt, bcrypt, or PBKDF2, in that order 18 of preference. Use as large a slowdown factor 17 as is feasible for your situation. Use a 16 vetted existing implementation. Make sure 15 you use a proper salt (although the libraries 14 you're using should be making sure of this 13 for you).


When you hash the passwords use 12 DO NOT USE PLAIN MD5.

Use PBKDF2, which basically means using a random 11 salt to prevent rainbow table attacks, and iterating 10 (re-hashing) enough times to slow the hashing 9 down - not so much that your application 8 takes too long, but enough that an attacker 7 brute-forcing a large number of different 6 password will notice

From the document:

  • Iterate at least 1000 times, preferably more - time your implementation to see how many iterations are feasible for you.
  • 8 bytes (64 bits) of salt are sufficient, and the random doesn't need to be secure (the salt is unencrypted, we're not worried someone will guess it).
  • A good way to apply the salt when hashing is to use HMAC with your favorite hash algorithm, using the password as the HMAC key and the salt as the text to hash (see this section of the document).

Example 5 implementation in Python, using SHA-256 4 as the secure hash:

EDIT: as mentioned by Eli 3 Collins this is not a PBKDF2 implementation. You 2 should prefer implementations which stick 1 to the standard, such as PassLib.

from hashlib import sha256
from hmac import HMAC
import random

def random_bytes(num_bytes):
  return "".join(chr(random.randrange(256)) for i in xrange(num_bytes))

def pbkdf_sha256(password, salt, iterations):
  result = password
  for i in xrange(iterations):
    result = HMAC(result, salt, sha256).digest() # use HMAC to apply the salt
  return result

NUM_ITERATIONS = 5000
def hash_password(plain_password):
  salt = random_bytes(8) # 64 bits

  hashed_password = pbkdf_sha256(plain_password, salt, NUM_ITERATIONS)

  # return the salt and hashed password, encoded in base64 and split with ","
  return salt.encode("base64").strip() + "," + hashed_password.encode("base64").strip()

def check_password(saved_password_entry, plain_password):
  salt, hashed_password = saved_password_entry.split(",")
  salt = salt.decode("base64")
  hashed_password = hashed_password.decode("base64")

  return hashed_password == pbkdf_sha256(plain_password, salt, NUM_ITERATIONS)

password_entry = hash_password("mysecret")
print password_entry # will print, for example: 8Y1ZO8Y1pi4=,r7Acg5iRiZ/x4QwFLhPMjASESxesoIcdJRSDkqWYfaA=
check_password(password_entry, "mysecret") # returns True
Score: 38

The basic strategy is to use a key derivation 54 function to "hash" the password with some 53 salt. The salt and the hash result are stored 52 in the database. When a user inputs a password, the 51 salt and their input are hashed in the same 50 way and compared to the stored value. If 49 they match, the user is authenticated.

The 48 devil is in the details. First, a lot depends 47 on the hash algorithm that is chosen. A 46 key derivation algorithm like PBKDF2, based 45 on a hash-based message authentication code, makes 44 it "computationally infeasible" to find 43 an input (in this case, a password) that 42 will produce a given output (what an attacker 41 has found in the database).

A pre-computed 40 dictionary attack uses pre-computed index, or 39 dictionary, from hash outputs to passwords. Hashing 38 is slow (or it's supposed to be, anyway), so 37 the attacker hashes all of the likely passwords 36 once, and stores the result indexed in such 35 a way that given a hash, he can lookup a 34 corresponding password. This is a classic 33 tradeoff of space for time. Since password 32 lists can be huge, there are ways to tune 31 the tradeoff (like rainbow tables), so that 30 an attacker can give up a little speed to 29 save a lot of space.

Pre-computation attacks 28 are thwarted by using "cryptographic salt". This 27 is some data that is hashed with the password. It doesn't need to be a secret, it 26 just needs to be unpredictable for a given 25 password. For each value of salt, an attacker 24 would need a new dictionary. If you use 23 one byte of salt, an attacker needs 256 22 copies of their dictionary, each generated 21 with a different salt. First, he'd use the 20 salt to lookup the correct dictionary, then 19 he'd use the hash output to look up a usable 18 password. But what if you add 4 bytes? Now 17 he needs 4 billion copies of the the dictionary. By 16 using a large enough salt, a dictionary 15 attack is precluded. In practice, 8 to 16 14 bytes of data from a cryptographic quality 13 random number generator makes a good salt.

With 12 pre-computation off the table, an attacker 11 has compute the hash on each attempt. How 10 long it takes to find a password now depends 9 entirely on how long it takes to hash a 8 candidate. This time is increased by iteration 7 of the hash function. The number iterations 6 is generally a parameter of the key derivation 5 function; today, a lot of mobile devices 4 use 10,000 to 20,000 iterations, while a 3 server might use 100,000 or more. (The bcrypt 2 algorithm uses the term "cost factor", which 1 is a logarithmic measure of the time required.)

Score: 19

I would imagine you will have to add a column 36 to the database for the encrypted password 35 then run a batch job over all records which 34 gets the current password, encrypts it (as 33 others have mentiond a hash like md5 is 32 pretty standard edit: but should not be used on its own - see other answers for good discussions), stores it in the new 31 column and checks it all happened smoothly.

Then 30 you will need to update your front-end to 29 hash the user-entered password at login 28 time and verify that vs the stored hash, rather 27 than checking plaintext-vs-plaintext.

It 26 would seem prudent to me to leave both columns 25 in place for a little while to ensure that 24 nothing hinky has gone on, before eventually 23 removing the plaintext passwords all-together.

Don't 22 forget also that anytime the password is 21 acessed the code will have to change, such 20 as password change / reminder requests. You 19 will of course lose the ability to email 18 out forgotten passwords, but this is no 17 bad thing. You will have to use a password 16 reset system instead.

Edit: One final point, you 15 might want to consider avoiding the error 14 I made on my first attempt at a test-bed 13 secure login website:

When processing the 12 user password, consider where the hashing 11 takes place. In my case the hash was calculated 10 by the PHP code running on the webserver, but 9 the password was transmitted to the page 8 from the user's machine in plaintext! This 7 was ok(ish) in the environment I was working 6 in, as it was inside an https system anyway 5 (uni network). But, in the real world I 4 imagine you would want to hash the password 3 before it leaves the user system, using 2 javascript etc. and then transmit the hash 1 to your site.

Score: 4

Follow Xan's advice of keeping the current password 4 column around for a while so if things go 3 bad, you can rollback quick-n-easy.

As far 2 as encrypting your passwords:

  • use a salt
  • use a hash algorithm that's meant for passwords (ie., - it's slow)

See Thomas 1 Ptacek's Enough With The Rainbow Tables: What You Need To Know About Secure Password Schemes for some details.

Score: 4

That was a problem of mine couple of weeks 45 ago. We were deploying a large MIS project 44 to 975 different geographical locations 43 where our own user credential store will 42 be used as an authenticator for different 41 set of already implemented and in-use applications. We 40 already provided both REST and SOAP based 39 authentication service but customer insisted 38 to be able to reach the user credential 37 store from other applications with just 36 a a DB connection to read-only view of related 35 table or view. Sigh... (this highly coupled bad design decision is a subject of another question).

That forced us to 34 sit and convert our salted and iteratively 33 hashed password storage scheme to a specification 32 and provide some different language implementations 31 for easy integration.

We called it Fairly 30 Secure Hashed Passwords or FSHP in short. Implemented 29 it in Python, Ruby, PHP5 and released it 28 to Public Domain. Available to be consumed, forked, flamed 27 or spit on GitHub at http://github.com/bdd/fshp

FSHP is a salted, iteratively 26 hashed password hashing implementation.

Design 25 principle is similar with PBKDF1 specification 24 in RFC 2898 (a.k.a: PKCS #5: Password-Based Cryptography Specification Version 2.0.) FSHP allows choosing the salt 23 length, number of iterations and the underlying 22 cryptographic hash function among SHA-1 21 and SHA-2 (256, 384, 512). Self defining 20 meta prefix at the beginning of every output 19 makes it portable while letting the consumer 18 to choose its own password storage security 17 baseline.

SECURITY:

Default FSHP1 uses 8 byte salts, with 16 4096 iterations of SHA-256 hashing. - 8 15 byte salt renders rainbow table attacks 14 impractical by multiplying the required 13 space with 2^64. - 4096 iterations causes 12 brute force attacks to be fairly expensive. - There 11 are no known attacks against SHA-256 to 10 find collisions with a computational 9 effort of fewer than 2^128 operations at 8 the time of this release.

IMPLEMENTATIONS:

  • Python: Tested with 2.3.5 (w/ hashlib), 2.5.1, 2.6.1
  • Ruby : Tested with 1.8.6
  • PHP5 : Tested with 5.2.6

Everyone is 7 more than welcome to create missing language 6 implementations or polish the current ones.

BASIC OPERATION (with Python):

>>> fsh = fshp.crypt('OrpheanBeholderScryDoubt')
>>> print fsh
{FSHP1|8|4096}GVSUFDAjdh0vBosn1GUhzGLHP7BmkbCZVH/3TQqGIjADXpc+6NCg3g==
>>> fshp.validate('OrpheanBeholderScryDoubt', fsh)
True

CUSTOMIZING THE CRYPT:

Let's 5 weaken our password hashing scheme. - Decrease 4 the salt length from default 8 to 2. - Decrease 3 the iteration round from default 4096 to 2 10. - Select FSHP0 with SHA-1 as the underlying 1 hash algorithm.

>>> fsh = fshp.crypt('ExecuteOrder66', saltlen=2, rounds=10, variant=0)
>>> print fsh
{FSHP0|2|10}Nge7yRT/vueEGVFPIxcDjiaHQGFQaQ==
Score: 3

I think you should do the following:

  1. Create a new column called HASHED_PASSWORD or something similar.
  2. Modify your code so that it checks for both columns.
  3. Gradually migrate passwords from the non-hashed table to the hashed one. For example, when a user logs in, migrate his or her password automatically to the hashed column and remove the unhashed version. All newly registered users will have hashed passwords.
  4. After hours, you can run a script which migrates n users a time
  5. When you have no more unhashed passwords left, you can remove your old password column (you may not be able to do so, depends on the database you are using). Also, you can remove the code to handle the old passwords.
  6. You're done!

0

Score: 2

As the others mentioned, you don't want 13 to decrypt if you can help it. Standard 12 best practice is to encrypt using a one-way 11 hash, and then when the user logs in hash 10 their password to compare it.

Otherwise you'll 9 have to use a strong encryption to encrypt 8 and then decrypt. I'd only recommend this 7 if the political reasons are strong (for 6 example, your users are used to being able 5 to call the help desk to retrieve their 4 password, and you have strong pressure from 3 the top not to change that). In that case, I'd 2 start with encryption and then start building 1 a business case to move to hashing.

Score: 2

For authentication purposes you should avoid 26 storing the passwords using reversible encryption, i.e. you 25 should only store the password hash and 24 check the hash of the user-supplied password 23 against the hash you have stored. However, that 22 approach has a drawback: it's vulnerable 21 to rainbow table attacks, should an attacker get hold 20 of your password store database.

What you 19 should do is store the hashes of a pre-chosen 18 (and secret) salt value + the password. I.e., concatenate 17 the salt and the password, hash the result, and 16 store this hash. When authenticating, do 15 the same - concatenate your salt value and 14 the user-supplied password, hash, then check 13 for equality. This makes rainbow table attacks 12 unfeasible.

Of course, if the user send passwords 11 across the network (for example, if you're 10 working on a web or client-server application), then 9 you should not send the password in clear 8 text across, so instead of storing hash(salt 7 + password) you should store and check against 6 hash(salt + hash(password)), and have your 5 client pre-hash the user-supplied password 4 and send that one across the network. This 3 protects your user's password as well, should 2 the user (as many do) re-use the same password 1 for multiple purposes.

Score: 1
  • Encrypt using something like MD5, encode it as a hex string
  • You need a salt; in your case, the username can be used as the salt (it has to be unique, the username should be the most unique value available ;-)
  • use the old password field to store the MD5, but tag the MD5 (i.e.g "MD5:687A878....") so that old (plain text) and new (MD5) passwords can co-exist
  • change the login procedure to verify against the MD5 if there is an MD5, and against the plain password otherwise
  • change the "change password" and "new user" functions to create MD5'ed passwords only
  • now you can run the conversion batch job, which might take as long as needed
  • after the conversion has been run, remove the legacy-support

0

Score: 1

Step 1: Add encrypted field to database

Step 21 2: Change code so that when password is 20 changed, it updates both fields but logging 19 in still uses old field.

Step 3: Run script 18 to populate all the new fields.

Step 4: Change 17 code so that logging in uses new field and 16 changing passwords stops updating old field.

Step 15 5: Remove unencrypted passwords from database.

This 14 should allow you to accomplish the changeover 13 without interruption to the end user.

Also: Something 12 I would do is name the new database field 11 something that is completely unrelated to 10 password like "LastSessionID" or something 9 similarly boring. Then instead of removing 8 the password field, just populate with hashes 7 of random data. Then, if your database 6 ever gets compromised, they can spend all 5 the time they want trying to decrypt the 4 "password" field.

This may not actually accomplish 3 anything, but it's fun thinking about someone 2 sitting there trying to figure out worthless 1 information

Score: 0

As with all security decisions, there are 16 tradeoffs. If you hash the password, which 15 is probably your easiest move, you can't 14 offer a password retrieval function that 13 returns the original password, nor can your 12 staff look up a person's password in order 11 to access their account.

You can use symmetric 10 encryption, which has its own security drawbacks. (If 9 your server is compromised, the symmetric 8 encryption key may be compromised also).

You 7 can use public-key encryption, and run password 6 retrieval/customer service on a separate 5 machine which stores the private key in 4 isolation from the web application. This 3 is the most secure, but requires a two-machine 2 architecture, and probably a firewall in 1 between.

Score: 0

MD5 and SHA1 have shown a bit of weakness 16 (two words can result in the same hash) so 15 using SHA256-SHA512 / iterative hashes is 14 recommended to hash the password.

I would 13 write a small program in the language that 12 the application is written in that goes 11 and generates a random salt that is unique 10 for each user and a hash of the password. The 9 reason I tend to use the same language as 8 the verification is that different crypto 7 libraries can do things slightly differently 6 (i.e. padding) so using the same library 5 to generate the hash and verify it eliminates 4 that risk. This application could also then 3 verify the login after the table has been 2 updated if you want as it knows the plain 1 text password still.

  1. Don't use MD5/SHA1
  2. Generate a good random salt (many crypto libraries have a salt generator)
  3. An iterative hash algorithm as orip recommended
  4. Ensure that the passwords are not transmitted in plain text over the wire
Score: 0

I would like to suggest one improvement 5 to the great python example posted by Orip. I would redefine the random_bytes function to be:

def random_bytes(num_bytes):
    return os.urandom(num_bytes)

Of 4 course, you would have to import the os module. The 3 os.urandom function provides a random sequence of 2 bytes that can be safely used in cryptographic 1 applications. See the reference help of this function for further details.

More Related questions