Skip to content

Commit 82f56d9

Browse files
tneems29decibel
authored andcommitted
Add a devise bcrypt encryptor for migrations to other hashing algorithms
1 parent 8b2649f commit 82f56d9

File tree

4 files changed

+74
-0
lines changed

4 files changed

+74
-0
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Support Rails 4.1 - 7.0, dropped support for Rails <= 4.0. (same support as Devise as of v4.8)
44
* Support Ruby 2.1 - 3.1, dropped support for Ruby <= 2.0. (same support as Devise as of v4.8)
55
* Add support to roll legacy encryptors
6+
* Add support for rolling from Devise BCrypt
67

78
### 0.2.0
89

lib/devise/encryptable/encryptable.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ module Encryptors
2222
autoload :AuthlogicSha512, 'devise/encryptable/encryptors/authlogic_sha512'
2323
autoload :Base, 'devise/encryptable/encryptors/base'
2424
autoload :ClearanceSha1, 'devise/encryptable/encryptors/clearance_sha1'
25+
autoload :DeviseBcrypt, 'devise/encryptable/encryptors/devise_bcrypt'
2526
autoload :Pbkdf2, 'devise/encryptable/encryptors/pbkdf2'
2627
autoload :RestfulAuthenticationSha1, 'devise/encryptable/encryptors/restful_authentication_sha1'
2728
autoload :Sha1, 'devise/encryptable/encryptors/sha1'
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
require 'bcrypt'
2+
3+
begin
4+
module Devise
5+
module Encryptable
6+
module Encryptors
7+
# Adapted from
8+
# https://github.com/heartcombo/devise/blob/8593801130f2df94a50863b5db535c272b00efe1/lib/devise/encryptor.rb
9+
class DeviseBcrypt < Base
10+
def self.compare(hashed_password, password, stretches, _salt, pepper)
11+
return false if hashed_password.blank?
12+
bcrypt = ::BCrypt::Password.new(hashed_password)
13+
if pepper.present?
14+
password = "#{password}#{pepper}"
15+
end
16+
password = ::BCrypt::Engine.hash_secret(password, bcrypt.salt)
17+
Devise.secure_compare(password, hashed_password)
18+
rescue BCrypt::Errors::InvalidHash
19+
# this probably means the password has already been migrated
20+
false
21+
end
22+
23+
def self.digest(password, stretches, _salt, pepper)
24+
if pepper.present?
25+
password = "#{password}#{pepper}"
26+
end
27+
::BCrypt::Password.create(password, cost: stretches).to_s
28+
end
29+
end
30+
end
31+
end
32+
end
33+
end

test/devise/encryptable/encryptors_test.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require "test_helper"
2+
require "ostruct"
23

34
class Encryptors < ActiveSupport::TestCase
45
include Support::Swappers
@@ -21,6 +22,44 @@ class Encryptors < ActiveSupport::TestCase
2122
assert_equal clearance, encryptor
2223
end
2324

25+
test 'Devise can verify passwords generated by DeviseBcrypt' do
26+
password = '123mudar'
27+
klass = OpenStruct.new(pepper: '65c58472c207c829f28c68619d3e3aefed18ab3f', stretches: 10)
28+
29+
hashed_password = Devise::Encryptable::Encryptors::DeviseBcrypt.digest(password, klass.stretches, nil, klass.pepper)
30+
31+
assert Devise::Encryptor.compare(klass, hashed_password, password)
32+
end
33+
34+
test 'DeviseBcrypt can verify bcrypt hashes created by Devise' do
35+
password = '123mudar'
36+
klass = OpenStruct.new(pepper: '65c58472c207c829f28c68619d3e3aefed18ab3f', stretches: 10)
37+
38+
devise_hash = Devise::Encryptor.digest(klass, password)
39+
40+
assert Devise::Encryptable::Encryptors::DeviseBcrypt.compare(devise_hash, password, klass.stretches, nil, klass.pepper)
41+
end
42+
43+
test 'Devise fails to verify passwords generated by DeviseBcrypt when the password is wrong' do
44+
password = '123mudar'
45+
different_password = 'theskyisfalling123'
46+
klass = OpenStruct.new(pepper: '65c58472c207c829f28c68619d3e3aefed18ab3f', stretches: 10)
47+
48+
hashed_password = Devise::Encryptable::Encryptors::DeviseBcrypt.digest(password, klass.stretches, nil, klass.pepper)
49+
50+
refute Devise::Encryptor.compare(klass, hashed_password, different_password)
51+
end
52+
53+
test 'DeviseBcrypt fails to verify bcrypt hashes created by Devise when the password is wrong' do
54+
password = '123mudar'
55+
different_password = 'theskyisfalling123'
56+
klass = OpenStruct.new(pepper: '65c58472c207c829f28c68619d3e3aefed18ab3f', stretches: 10)
57+
58+
devise_hash = Devise::Encryptor.digest(klass, password)
59+
60+
refute Devise::Encryptable::Encryptors::DeviseBcrypt.compare(devise_hash, different_password, klass.stretches, nil, klass.pepper)
61+
end
62+
2463
test 'digest should raise NotImplementedError if not implemented in subclass' do
2564
c = Class.new(Devise::Encryptable::Encryptors::Base)
2665
assert_raise(NotImplementedError) do

0 commit comments

Comments
 (0)