diff --git a/Gemfile b/Gemfile index cbf54dd63..43688b367 100644 --- a/Gemfile +++ b/Gemfile @@ -38,7 +38,6 @@ gem "omniauth-twitter" gem "omniauth-github" gem "omniauth-browserid", :git => "git://github.com/callahad/omniauth-browserid.git", :branch => "observer_api" gem 'oj' -gem 'pbkdf2' gem 'pg' gem 'rails' gem 'rake' diff --git a/Gemfile.lock b/Gemfile.lock index 47ebbb691..c5135e26c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -307,7 +307,6 @@ GEM openid-redis-store (0.0.2) redis ruby-openid - pbkdf2 (0.1.0) pg (0.14.1) polyglot (0.3.3) progress (2.4.0) @@ -506,7 +505,6 @@ DEPENDENCIES omniauth-openid omniauth-twitter openid-redis-store - pbkdf2 pg pry-rails rack-mini-profiler! diff --git a/app/models/user.rb b/app/models/user.rb index 1300cc3d3..f22ac88e2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,5 +1,6 @@ require_dependency 'email_token' require_dependency 'trust_level' +require_dependency 'pbkdf2' class User < ActiveRecord::Base attr_accessible :name, :username, :password, :email, :bio_raw, :website @@ -495,7 +496,7 @@ class User < ActiveRecord::Base end def hash_password(password, salt) - PBKDF2.new(password: password, salt: salt, iterations: Rails.configuration.pbkdf2_iterations).hex_string + Pbkdf2.hash_password(password, salt, Rails.configuration.pbkdf2_iterations) end def add_trust_level diff --git a/lib/pbkdf2.rb b/lib/pbkdf2.rb new file mode 100644 index 000000000..9027edbb0 --- /dev/null +++ b/lib/pbkdf2.rb @@ -0,0 +1,39 @@ +# Note: the pbkdf2 gem is bust on 2.0, the logic is so simple I am not sure it makes sense to have this in a gem atm (Sam) +# +# Also PBKDF2 monkey patches string ... don't like that at all +# +# Happy to move back to PBKDF2 ruby gem provided: +# +# 1. It works on Ruby 2.0 +# 2. It works on 1.9.3 +# 3. It does not monkey patch string + +require 'openssl' + +class Pbkdf2 + + def self.hash_password(password, salt, iterations) + + h = OpenSSL::Digest::Digest.new("sha256") + + u = ret = prf(h, password, salt + [1].pack("N")) + + 2.upto(iterations) do + u = prf(h, password, u) + ret = xor(ret, u) + end + + ret.bytes.map{|b| ("0" + b.to_s(16))[-2..-1]}.join("") + end + + protected + + def self.xor(x,y) + x.bytes.zip(y.bytes).map{|x,y| x ^ y}.pack('c*') + end + + def self.prf(hash_function, password, data) + OpenSSL::HMAC.digest(hash_function, password, data) + end + +end diff --git a/spec/components/pbkdf2_spec.rb b/spec/components/pbkdf2_spec.rb new file mode 100644 index 000000000..05d03c77f --- /dev/null +++ b/spec/components/pbkdf2_spec.rb @@ -0,0 +1,9 @@ +require 'pbkdf2' + +describe Pbkdf2 do + # trivial test to ensure this does not regress during extraction + it "hashes stuff correctly" do + Pbkdf2.hash_password('test', 'abcd', 100).should == "0313a6aca54dd4c5d82a699a8a0f0ffb0191b4ef62414b8d9dbc11c0c5ac04da" + Pbkdf2.hash_password('test', 'abcd', 101).should == "c7a7b2891bf8e6f82d08cf8d83824edcf6c7c6bacb6a741f38e21fc7977bd20f" + end +end