$ rails console
>> require 'digest'
>> def secure_hash(string)
>> Digest::SHA2.hexdigest(string)
>> end
=> nil
>> password = "secret"
=> "secret"
>> encrypted_password = secure_hash(password)
=> "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b"
>> submitted_password = "secret"
=> "secret"
>> encrypted_password == secure_hash(submitted_password)
=> true
Here we’ve defined a function called
secure_hash that uses a cryptographic
hash function called SHA2, part of the
SHA family of hash functions, which we
include into Ruby through the digest
library.7 It’s not important to know
exactly how these hash functions work;
for our purposes what’s important is
that they are one-way: there is no
computationally tractable way to
discover that
2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b
is the SHA2 hash of the string
"secret".
If you think about it, though, we
still have a problem: if an attacker
ever got hold of the hashed passwords,
he would still have a chance at
discovering the originals. For
example, he could guess that we used
SHA2, and so write a program to
compare a given hash to the hashed
values of potential passwords:
>> hash = "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b"
>> secure_hash("secede") == hash
=> false
>> secure_hash("second") == hash
=> false
>> secure_hash("secret") == hash
=> true
So our attacker has a match—bad news
for any users with password "secret".
This technique is known as a rainbow
attack.
To foil a potential rainbow attack, we
can use a salt, which is a different
unique string for each user.8 One
common way to (nearly) ensure
uniqueness is to hash the current time
(in UTC to be time zone–independent)
along with the password, so that two
users will have the same salt only if
they are created at exactly the same
time and have the same password. Let’s
see how this works using the
secure_hash function defined in the
console above:
>> Time.now.utc
=> Fri Jan 29 18:11:27 UTC 2010
>> password = "secret"
=> "secret"
>> salt = secure_hash("#{Time.now.utc}--#{password}")
=> "d1a3eb8c9aab32ec19cfda810d2ab351873b5dca4e16e7f57b3c1932113314c8"
>> encrypted_password = secure_hash("#{salt}--#{password}")
=> "69a98a49b7fd103058639be84fb88c19c998c8ad3639cfc5deb458018561c847"
In the last line, we’ve hashed the salt with the password, yielding an
encrypted password that is virtually
impossible to crack. (For clarity,
arguments to hashing functions are
often separated with --.)