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