π | .gitignore |
π | CHANGELOG |
π | COPYING |
π | README |
π | Rakefile |
π | ext |
π | lib |
π | spec |
README
1 | = bcrypt-ruby |
2 | |
3 | An easy way to keep your users' passwords secure. |
4 | |
5 | * http://bcrypt-ruby.rubyforge.org/ |
6 | * http://github.com/codahale/bcrypt-ruby/tree/master |
7 | |
8 | == Why you should use bcrypt |
9 | |
10 | If you store user passwords in the clear, then an attacker who steals a copy of your database has a giant list of emails |
11 | and passwords. Some of your users will only have one password -- for their email account, for their banking account, for |
12 | your application. A simple hack could escalate into massive identity theft. |
13 | |
14 | It's your responsibility as a web developer to make your web application secure -- blaming your users for not being |
15 | security experts is not a professional response to risk. |
16 | |
17 | bcrypt allows you to easily harden your application against these kinds of attacks. |
18 | |
19 | == How to install bcrypt |
20 | |
21 | sudo gem install bcrypt-ruby |
22 | |
23 | You'll need a working compiler. (Win32 folks should use Cygwin or um, something else.) |
24 | |
25 | == How to use bcrypt in your Rails application |
26 | |
27 | === The +User+ model |
28 | |
29 | require 'bcrypt' |
30 | |
31 | class User < ActiveRecord::Base |
32 | # users.password_hash in the database is a :string |
33 | include BCrypt |
34 | |
35 | def password |
36 | @password ||= Password.new(password_hash) |
37 | end |
38 | |
39 | def password=(new_password) |
40 | @password = Password.create(new_password) |
41 | self.password_hash = @password |
42 | end |
43 | |
44 | end |
45 | |
46 | === Creating an account |
47 | |
48 | def create |
49 | @user = User.new(params[:user]) |
50 | @user.password = params[:password] |
51 | @user.save! |
52 | end |
53 | |
54 | === Authenticating a user |
55 | |
56 | def login |
57 | @user = User.find_by_email(params[:email]) |
58 | if @user.password == params[:password] |
59 | give_token |
60 | else |
61 | redirect_to home_url |
62 | end |
63 | end |
64 | |
65 | === If a user forgets their password? |
66 | |
67 | # assign them a random one and mail it to them, asking them to change it |
68 | def forgot_password |
69 | @user = User.find_by_email(params[:email]) |
70 | random_password = Array.new(10).map { (65 + rand(58)).chr }.join |
71 | @user.password = random_password |
72 | @user.save! |
73 | Mailer.create_and_deliver_password_change(@user, random_password) |
74 | end |
75 | |
76 | == How to use bcrypt-ruby in general |
77 | |
78 | require 'bcrypt' |
79 | |
80 | my_password = BCrypt::Password.create("my password") #=> "$2a$10$vI8aWBnW3fID.ZQ4/zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa" |
81 | |
82 | my_password.version #=> "2a" |
83 | my_password.cost #=> 10 |
84 | my_password == "my password" #=> true |
85 | my_password == "not my password" #=> false |
86 | |
87 | my_password = BCrypt::Password.new("$2a$10$vI8aWBnW3fID.ZQ4/zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa") |
88 | my_password == "my password" #=> true |
89 | my_password == "not my password" #=> false |
90 | |
91 | Check the rdocs for more details -- BCrypt, BCrypt::Password. |
92 | |
93 | == How bcrypt() works |
94 | |
95 | bcrypt() is a hashing algorithm designed by Niels Provos and David Mazières of the OpenBSD Project. |
96 | |
97 | === Background |
98 | |
99 | Hash algorithms take a chunk of data (e.g., your user's password) and create a "digital fingerprint," or hash, of it. |
100 | Because this process is not reversible, there's no way to go from the hash back to the password. |
101 | |
102 | In other words: |
103 | |
104 | hash(p) #=> <unique gibberish> |
105 | |
106 | You can store the hash and check it against a hash made of a potentially valid password: |
107 | |
108 | <unique gibberish> =? hash(just_entered_password) |
109 | |
110 | === Rainbow Tables |
111 | |
112 | But even this has weaknesses -- attackers can just run lists of possible passwords through the same algorithm, store the |
113 | results in a big database, and then look up the passwords by their hash: |
114 | |
115 | PrecomputedPassword.find_by_hash(<unique gibberish>).password #=> "secret1" |
116 | |
117 | === Salts |
118 | |
119 | The solution to this is to add a small chunk of random data -- called a salt -- to the password before it's hashed: |
120 | |
121 | hash(salt + p) #=> <really unique gibberish> |
122 | |
123 | The salt is then stored along with the hash in the database, and used to check potentially valid passwords: |
124 | |
125 | <really unique gibberish> =? hash(salt + just_entered_password) |
126 | |
127 | bcrypt-ruby automatically handles the storage and generation of these salts for you. |
128 | |
129 | Adding a salt means that an attacker has to have a gigantic database for each unique salt -- for a salt made of 4 |
130 | letters, that's 456,976 different databases. Pretty much no one has that much storage space, so attackers try a |
131 | different, slower method -- throw a list of potential passwords at each individual password: |
132 | |
133 | hash(salt + "aadvark") =? <really unique gibberish> |
134 | hash(salt + "abacus") =? <really unique gibberish> |
135 | etc. |
136 | |
137 | This is much slower than the big database approach, but most hash algorithms are pretty quick -- and therein lies the |
138 | problem. Hash algorithms aren't usually designed to be slow, they're designed to turn gigabytes of data into secure |
139 | fingerprints as quickly as possible. bcrypt(), though, is designed to be computationally expensive: |
140 | |
141 | Ten thousand iterations: |
142 | user system total real |
143 | md5 0.070000 0.000000 0.070000 ( 0.070415) |
144 | bcrypt 22.230000 0.080000 22.310000 ( 22.493822) |
145 | |
146 | If an attacker was using Ruby to check each password, they could check ~140,000 passwords a second with MD5 but only |
147 | ~450 passwords a second with bcrypt(). |
148 | |
149 | === Cost Factors |
150 | |
151 | In addition, bcrypt() allows you to increase the amount of work required to hash a password as computers get faster. Old |
152 | passwords will still work fine, but new passwords can keep up with the times. |
153 | |
154 | The default cost factor used by bcrypt-ruby is 10, which is fine for session-based authentication. If you are using a |
155 | stateless authentication architecture (e.g., HTTP Basic Auth), you will want to lower the cost factor to reduce your |
156 | server load and keep your request times down. This will lower the security provided you, but there are few alternatives. |
157 | |
158 | == More Information |
159 | |
160 | bcrypt() is currently used as the default password storage hash in OpenBSD, widely regarded as the most secure operating |
161 | system available. |
162 | |
163 | |
164 | For a more technical explanation of the algorithm and its design criteria, please read Niels Provos and David Mazières' |
165 | Usenix99 paper: |
166 | http://www.usenix.org/events/usenix99/provos.html |
167 | |
168 | If you'd like more down-to-earth advice regarding cryptography, I suggest reading <i>Practical Cryptography</i> by Niels |
169 | Ferguson and Bruce Schneier: |
170 | http://www.schneier.com/book-practical.html |
171 | |
172 | = Etc |
173 | |
174 | Author :: Coda Hale <coda.hale@gmail.com> |
175 | Website :: http://blog.codahale.com |
176 |
Built with git-ssb-web