git ssb

0+

dangerousbeans / %aPBe2k3ugtjBr4rrsU1…



Tree: 649682ef658d3dfcdecdb116a0a36fffc4678542

Files: 649682ef658d3dfcdecdb116a0a36fffc4678542 / lib / bcrypt.rb

5262 bytesRaw
1# A wrapper for OpenBSD's bcrypt/crypt_blowfish password-hashing algorithm.
2
3$: << "ext"
4require "bcrypt_ext"
5require "openssl"
6
7# A Ruby library implementing OpenBSD's bcrypt()/crypt_blowfish algorithm for
8# hashing passwords.
9module BCrypt
10 module Errors # :nodoc:
11 # The salt parameter provided to bcrypt() is invalid.
12 class InvalidSalt < Exception; end
13 # The hash parameter provided to bcrypt() is invalid.
14 class InvalidHash < Exception; end
15 # The cost parameter provided to bcrypt() is invalid.
16 class InvalidCost < Exception; end
17 end
18
19 # A Ruby wrapper for the bcrypt() extension calls.
20 class Engine
21 DEFAULT_COST = 10 # The default computational expense parameter.
22 MAX_SALT_LENGTH = 16 # Maximum possible size of bcrypt() salts.
23
24 # C-level routines which, if they don't get the right input, will crash the
25 # hell out of the Ruby process.
26 private_class_method :__bc_salt
27 private_class_method :__bc_crypt
28
29 # Given a secret and a valid salt (see BCrypt::Engine.generate_salt) calculates
30 # a bcrypt() password hash.
31 def self.hash(secret, salt)
32 if valid_salt?(salt)
33 __bc_crypt(secret, salt)
34 else
35 raise Errors::InvalidSalt.new("invalid salt")
36 end
37 end
38
39 # Generates a random salt with a given computational cost.
40 def self.generate_salt(cost = DEFAULT_COST)
41 if cost.to_i > 0
42 __bc_salt(cost, OpenSSL::Random.random_bytes(MAX_SALT_LENGTH))
43 else
44 raise Errors::InvalidCost.new("cost must be numeric and > 0")
45 end
46 end
47
48 # Returns true if +salt+ is a valid bcrypt() salt, false if not.
49 def self.valid_salt?(salt)
50 salt =~ /^\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}$/
51 end
52
53 # Returns the cost factor which will result in computation times less than +upper_time_limit_in_ms+.
54 #
55 # Example:
56 #
57 # BCrypt.calibrate(200) #=> 10
58 # BCrypt.calibrate(1000) #=> 12
59 #
60 # # should take less than 200ms
61 # BCrypt::Password.create("woo", :cost => 10)
62 #
63 # # should take less than 1000ms
64 # BCrypt::Password.create("woo", :cost => 12)
65 def self.calibrate(upper_time_limit_in_ms)
66 40.times do |i|
67 start_time = Time.now
68 Password.create("testing testing", :cost => i+1)
69 end_time = Time.now - start_time
70 return i if end_time * 1_000 > upper_time_limit_in_ms
71 end
72 end
73 end
74
75 # A password management class which allows you to safely store users' passwords and compare them.
76 #
77 # Example usage:
78 #
79 # include BCrypt
80 #
81 # # hash a user's password
82 # @password = Password.create("my grand secret")
83 # @password #=> "$2a$10$GtKs1Kbsig8ULHZzO1h2TetZfhO4Fmlxphp8bVKnUlZCBYYClPohG"
84 #
85 # # store it safely
86 # @user.update_attribute(:password, @password)
87 #
88 # # read it back
89 # @user.reload!
90 # @db_password = Password.new(@user.password)
91 #
92 # # compare it after retrieval
93 # @db_password == "my grand secret" #=> true
94 # @db_password == "a paltry guess" #=> false
95 #
96 class Password < String
97 # The hash portion of the stored password hash.
98 attr_reader :hash
99 # The salt of the store password hash (including version and cost).
100 attr_reader :salt
101 # The version of the bcrypt() algorithm used to create the hash.
102 attr_reader :version
103 # The cost factor used to create the hash.
104 attr_reader :cost
105
106 class << self
107 # Hashes a secret, returning a BCrypt::Password instance. Takes an optional <tt>:cost</tt> option, which is a
108 # logarithmic variable which determines how computational expensive the hash is to calculate (a <tt>:cost</tt> of
109 # 4 is twice as much work as a <tt>:cost</tt> of 3). The higher the <tt>:cost</tt> the harder it becomes for
110 # attackers to try to guess passwords (even if a copy of your database is stolen), but the slower it is to check
111 # users' passwords.
112 #
113 # Example:
114 #
115 # @password = BCrypt::Password.create("my secret", :cost => 13)
116 def create(secret, options = { :cost => BCrypt::Engine::DEFAULT_COST })
117 Password.new(BCrypt::Engine.hash(secret, BCrypt::Engine.generate_salt(options[:cost])))
118 end
119 end
120
121 # Initializes a BCrypt::Password instance with the data from a stored hash.
122 def initialize(raw_hash)
123 if valid_hash?(raw_hash)
124 self.replace(raw_hash)
125 @version, @cost, @salt, @hash = split_hash(self)
126 else
127 raise Errors::InvalidHash.new("invalid hash")
128 end
129 end
130
131 # Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise.
132 def ==(secret)
133 super(BCrypt::Engine.hash(secret, @salt))
134 end
135 alias_method :is_password?, :==
136
137 private
138 # Returns true if +h+ is a valid hash.
139 def valid_hash?(h)
140 h =~ /^\$[0-9a-z]{2}\$[0-9]{2}\$[A-Za-z0-9\.\/]{53}$/
141 end
142
143 # call-seq:
144 # split_hash(raw_hash) -> version, cost, salt, hash
145 #
146 # Splits +h+ into version, cost, salt, and hash and returns them in that order.
147 def split_hash(h)
148 b, v, c, mash = h.split('$')
149 return v, c.to_i, h[0, 29], mash[-31, 31]
150 end
151 end
152end

Built with git-ssb-web