8.2. Hashing Passwords
A common security practice is to create a hash (also known as a digest) of users' passwords before storing them. A hash is like a digital fingerprintit is a small piece of information that serves as a unique identifier for a larger piece of information. There are many hash algorithms, and some of them are very difficultif not practically impossibleto reverse. They're called cryptographic hashes, and the most common algorithms are MD5 and SHA-1.
The most common application of hashes in web applications is storing passwords. The idea is simple: when the user signs up and provides a password for their account, you hash it (say, using MD5) and store the hash in the database. The next time the users signs in, he provides the password again, and the application hashes the input and compares it with the stored hash. If the hashes match, the passwords must matcheven though the password itself is unknown.
Interesting, but why go through this trouble? The advantage is that the user's password is never stored anywhere in the systemreducing the risk that it could be compromised. For example, you build a community site that becomes popular. Thousands of users register, and all is well until the day an attacker gains access to your database. In one fell swoop, the attacker (perhaps even someone inside your organization) not only has access to your site, but every other account where your users use the same passwordemail, bank accounts, everything. In contrast, by only storing hashed passwords, the potential for damage is greatly contained (which translates into better sleep).
Incorporating hashed passwords into your application isn't difficult. Here is a simple example User model that provides password hashing. It works by creating a virtual attribute called password that doesn't have a corresponding database column. Instead, a database column called hashed_password is expected. Any time the password attribute is set, ActiveRecord updates the hashed version automatically. And the User.authenticate method can be used when a user signs in to check the provided password against your records.
require 'digest/sha1' class User < ActiveRecord::Base # Virtual attribute for the plaintext password attr_accessor :password validates_uniqueness_of :login validates_presence_of :password, :if => :password_required? validates_confirmation_of :password, :if => :password_required? before_save :hash_password # Authenticates a user by login/password. Returns the user or nil. def self.authenticate login, password find_by_login_and_hashed_password(login, Digest::SHA1.hexdigest(login+password)) end protected def hash_password return if password.blank? self.hashed_password = Digest::SHA1.hexdigest(login+password) end def password_required? hashed_password.blank? || !password.blank? end end
One last thing: notice that in this implementation, the hash isn't just computed from the password alone, but from the login concatenated with the password. As a result, even if two users have the same password, the stored hash will be differentand if the database is compromised, even a brute-force dictionary attack will be far more difficult.