chapter 7.2
again, let's start from TDD again!!!!!!!!!!!!!!
1. since we define the encrypt_password into private area, how do we test it????????
ok, we need some public interface to use it. (TDD by acting as a client, the test motivate us to design a useful interface right from the start)
authentication involves comparing the encrypted version of submitted password to the encreptyed_password in the database. This means we need to define a method:
has_password?
this will be public interface.
def has_password?(submitted_password) # to do end
for the test:
describe "has_password? method" do it "should be true if the passwords match" do @user.has_password?(@attr[:password]).should be_true end it "should be false if the passwords don't match" do @user.has_password?("invalid").should be_false end end
2. study some secure password theory:
a. instead of storing the raw password, we store a hashed password,
b. this hashed password is inreversible, this means even the hacher get the encrypted password, he can't infer the original.
c. to do the authentication, we first encrypt the submitted password, then do compare.
for examle, we use SHA2 to hash the password:
require "digest"
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
password = "secret"
encrypted_password = secure_hash(password)
submitted_password = "secret"
encrypted_password == secure_hash(submitted_password) ======> true
Digest::SHA2.hexdigest(string)
this is one-way, it is impossible to deduce the original password from the hashed value.
But:
we still have a problem: if the hacker ever hold the hashed password, and he can guess we use SHA2, and write a program to compare the given hash to the hashed values of many common password, to find the original password!!
so we are still in a security hole!!!
how to solve it?
the answer is "salt"!!!
for example:
Time.now.utc
password = "secret"
salt = secure_hash("#{Time.now.utc}--#{password}")
encrypted_password = secure_hash("#{salt}--#{password}")
This password is impossible to crack!!
For clarity, arguments to hashing functions are often separated with "--"
3. now we are ready to implement has_password? method:
def has_password?(submitted_password) encrypted_password == encrypt(submitted_password) end
as long as the submitted password using the same salt, it will work fine.
5. we need to add a new column called salt to the users table.
rails g migration add_salt_to_users salt:string
rake db:migrate
rake db:test:prepare
6. finally, we will implement the full.
require 'digest' class User < ActiveRecord::Base before_save :encrypt_password def has_password?(submitted_password) encrypted_password == encrypt(submitted_password) end private def encrypt_password self.salt = make_salt unless has_password?(password) self.encrypted_password = encrypt(password) end def encrypt(string) secure_hash("#{salt}--#{string}") end def make_salt secure_hash("#{Time.now.utc}--#{password}") end def secure_hash(string) Digest::SHA2.hexdigest(string) end end
ok, let's run the test, it will pass:
rspec spec/models/user_spec.rb -e "has_password\? method"