Skip to content

Commit

Permalink
Make bcrypt the default encryptor and automatically add a pepper on g…
Browse files Browse the repository at this point in the history
…eneration.
  • Loading branch information
josevalim committed Jun 12, 2010
1 parent bece09c commit 0333cae
Show file tree
Hide file tree
Showing 12 changed files with 76 additions and 68 deletions.
21 changes: 16 additions & 5 deletions CHANGELOG.rdoc
@@ -1,4 +1,19 @@
== 1.1.rc1
== 1.1.0 (to be released)

* enhancements
* Allow to set cookie domain for the remember token. (by github.com/mantas)
* Added navigational formats to specify when it should return a 302 and when a 401.
* Added authenticate(scope) support in routes (by github.com/wildchild)
* Added after_update_path_for to registrations controller (by github.com/thedelchop)
* Added anybody_signed_in? helper (by github.com/SSDany)

* bug fix
* confirmation_required? is properly honored on active? calls. (by github.com/paulrosania)

* deprecations
* bcrypt is now the default encryptor

== 1.1.rc

* enhancements
* Rails 3 compatibility.
Expand All @@ -17,10 +32,6 @@
* Allow :unlock_strategy to be :none and add :lock_strategy which can be :failed_attempts or none. Setting those values to :none means that you want to handle lock and unlocking by yourself.
* No need to append ?unauthenticated=true in URLs anymore since Flash was moved to a middleware in Rails 3.
* :activatable is included by default in your models.
* Allow to set cookie domain for the remember token. (by github.com/mantas)
* Added navigational formats to specify when it should return a 302 and when a 401.
* Added authenticate(scope) support in routes (by github.com/wildchild)
* Added after_update_path_for to registrations controller (by github.com/thedelchop)

* bug fix
* Fix a bug with STI.
Expand Down
1 change: 1 addition & 0 deletions Rakefile
Expand Up @@ -46,6 +46,7 @@ begin
s.files = FileList["[A-Z]*", "{app,config,lib}/**/*"]
s.extra_rdoc_files = FileList["[A-Z]*"] - %w(Gemfile Rakefile)
s.add_dependency("warden", "~> 0.10.5")
s.add_dependency("bcrypt-ruby", "~> 2.1.2")
end

Jeweler::GemcutterTasks.new
Expand Down
2 changes: 1 addition & 1 deletion lib/devise.rb
Expand Up @@ -97,7 +97,7 @@ module Strategies

# Used to define the password encryption algorithm.
mattr_accessor :encryptor
@@encryptor = :sha1
@@encryptor = nil

# Store scopes mappings.
mattr_accessor :mappings
Expand Down
2 changes: 0 additions & 2 deletions lib/devise/encryptors/authlogic_sha512.rb
Expand Up @@ -7,15 +7,13 @@ module Encryptors
# Warning: it uses Devise's stretches configuration to port Authlogic's one. Should be set to 20 in the initializer to silumate
# the default behavior.
class AuthlogicSha512 < Base

# Gererates a default password digest based on salt, pepper and the
# incoming password.
def self.digest(password, stretches, salt, pepper)
digest = [password, salt].flatten.join('')
stretches.times { digest = Digest::SHA512.hexdigest(digest) }
digest
end

end
end
end
2 changes: 0 additions & 2 deletions lib/devise/encryptors/bcrypt.rb
Expand Up @@ -5,7 +5,6 @@ module Encryptors
# = BCrypt
# Uses the BCrypt hash algorithm to encrypt passwords.
class Bcrypt < Base

# Gererates a default password digest based on stretches, salt, pepper and the
# incoming password. We don't strech it ourselves since BCrypt does so internally.
def self.digest(password, stretches, salt, pepper)
Expand All @@ -15,7 +14,6 @@ def self.digest(password, stretches, salt, pepper)
def self.salt
::BCrypt::Engine.generate_salt
end

end
end
end
2 changes: 0 additions & 2 deletions lib/devise/encryptors/clearance_sha1.rb
Expand Up @@ -7,13 +7,11 @@ module Encryptors
# Warning: it uses Devise's pepper to port the concept of REST_AUTH_SITE_KEY
# Warning: it uses Devise's stretches configuration to port the concept of REST_AUTH_DIGEST_STRETCHES
class ClearanceSha1 < Base

# Gererates a default password digest based on salt, pepper and the
# incoming password.
def self.digest(password, stretches, salt, pepper)
Digest::SHA1.hexdigest("--#{salt}--#{password}--")
end

end
end
end
14 changes: 6 additions & 8 deletions lib/devise/encryptors/sha1.rb
Expand Up @@ -5,7 +5,6 @@ module Encryptors
# = Sha1
# Uses the Sha1 hash algorithm to encrypt passwords.
class Sha1 < Base

# Gererates a default password digest based on stretches, salt, pepper and the
# incoming password.
def self.digest(password, stretches, salt, pepper)
Expand All @@ -14,14 +13,13 @@ def self.digest(password, stretches, salt, pepper)
digest
end

private
private

# Generate a SHA1 digest joining args. Generated token is something like
# --arg1--arg2--arg3--argN--
def self.secure_digest(*tokens)
::Digest::SHA1.hexdigest('--' << tokens.flatten.join('--') << '--')
end

# Generate a SHA1 digest joining args. Generated token is something like
# --arg1--arg2--arg3--argN--
def self.secure_digest(*tokens)
::Digest::SHA1.hexdigest('--' << tokens.flatten.join('--') << '--')
end
end
end
end
14 changes: 6 additions & 8 deletions lib/devise/encryptors/sha512.rb
Expand Up @@ -5,7 +5,6 @@ module Encryptors
# = Sha512
# Uses the Sha512 hash algorithm to encrypt passwords.
class Sha512 < Base

# Gererates a default password digest based on salt, pepper and the
# incoming password.
def self.digest(password, stretches, salt, pepper)
Expand All @@ -14,14 +13,13 @@ def self.digest(password, stretches, salt, pepper)
digest
end

private
private

# Generate a Sha512 digest joining args. Generated token is something like
# --arg1--arg2--arg3--argN--
def self.secure_digest(*tokens)
::Digest::SHA512.hexdigest('--' << tokens.flatten.join('--') << '--')
end

# Generate a Sha512 digest joining args. Generated token is something like
# --arg1--arg2--arg3--argN--
def self.secure_digest(*tokens)
::Digest::SHA512.hexdigest('--' << tokens.flatten.join('--') << '--')
end
end
end
end
23 changes: 14 additions & 9 deletions lib/devise/rails.rb
Expand Up @@ -16,24 +16,29 @@ class Engine < ::Rails::Engine
# Force routes to be loaded if we are doing any eager load.
config.before_eager_load { |app| app.reload_routes! }

config.after_initialize do
Devise.encryptor ||= begin
warn "[WARNING] config.encryptor is not set in your config/initializers/devise.rb. " \
"Devise will then set it to :bcrypt. If you were using the previous default " \
"encryptor, please add config.encryptor = :sha1 to your configuration file."
:bcrypt
end
end

config.after_initialize do
flash = [:unauthenticated, :unconfirmed, :invalid, :invalid_token, :timeout, :inactive, :locked]

translations = begin
I18n.available_locales
I18n.backend.send(:translations)
I18n.t("devise.sessions", :raise => true).keys
rescue Exception => e # Do not care if something fails
{}
end

translations.each do |locale, translations|
keys = flash & (translations[:devise][:sessions].keys) rescue []
keys = flash & translations

if keys.any?
ActiveSupport::Deprecation.warn "The following I18n messages in 'devise.sessions' " <<
"for locale '#{locale}' are deprecated: #{keys.to_sentence}. Please move them to " <<
"'devise.failure' instead."
end
if keys.any?
ActiveSupport::Deprecation.warn "The following I18n messages in 'devise.sessions' " \
"are deprecated: #{keys.to_sentence}. Please move them to 'devise.failure' instead."
end
end
end
Expand Down
2 changes: 2 additions & 0 deletions lib/generators/devise_install/devise_install_generator.rb
@@ -1,3 +1,5 @@
require 'active_support/secure_random'

class DeviseInstallGenerator < Rails::Generators::Base
desc "Creates a Devise initializer and copy locale files to your application."

Expand Down
21 changes: 10 additions & 11 deletions lib/generators/devise_install/templates/devise.rb
Expand Up @@ -27,19 +27,18 @@
# config.http_authentication_realm = "Application"

# ==> Configuration for :database_authenticatable
# Invoke `rake secret` and use the printed value to setup a pepper to generate
# the encrypted password. By default no pepper is used.
# config.pepper = "rake secret output"
# For bcrypt, this is the cost for hashing the password and defaults to 10. If
# using other encryptors, it sets how many times you want the password re-encrypted.
config.stretches = 10

# Configure how many times you want the password re-encrypted. Default is 10.
# config.stretches = 10

# Define which will be the encryption algorithm. Supported algorithms are :sha1
# (default), :sha512 and :bcrypt. Devise also supports encryptors from others
# authentication tools as :clearance_sha1, :authlogic_sha512 (then you should set
# stretches above to 20 for default behavior) and :restful_authentication_sha1
# Define which will be the encryption algorithm. Devise also supports encryptors
# from others authentication tools as :clearance_sha1, :authlogic_sha512 (then
# you should set stretches above to 20 for default behavior) and :restful_authentication_sha1
# (then you should set stretches to 10, and copy REST_AUTH_SITE_KEY to pepper)
# config.encryptor = :sha1
config.encryptor = :bcrypt

# Setup a pepper to generate the encrypted password.
config.pepper = <%= ActiveSupport::SecureRandom.hex(64).inspect %>
# ==> Configuration for :confirmable
# The time you want to give your user to confirm his account. During this time
Expand Down
40 changes: 20 additions & 20 deletions test/models/database_authenticatable_test.rb
Expand Up @@ -3,10 +3,22 @@

class DatabaseAuthenticatableTest < ActiveSupport::TestCase

def encrypt_password(user, pepper=User.pepper, stretches=User.stretches, encryptor=::Devise::Encryptors::Sha1)
def encrypt_password(user, pepper=User.pepper, stretches=User.stretches, encryptor=User.encryptor_class)
encryptor.digest('123456', stretches, user.password_salt, pepper)
end

def swap_with_encryptor(klass, encryptor, options={})
klass.instance_variable_set(:@encryptor_class, nil)

swap klass, options.merge(:encryptor => encryptor) do
begin
yield
ensure
klass.instance_variable_set(:@encryptor_class, nil)
end
end
end

test 'should respond to password and password confirmation' do
user = new_user
assert user.respond_to?(:password)
Expand All @@ -28,8 +40,10 @@ def encrypt_password(user, pepper=User.pepper, stretches=User.stretches, encrypt
end

test 'should generate a base64 hash using SecureRandom for password salt' do
ActiveSupport::SecureRandom.expects(:base64).with(15).returns('friendly_token')
assert_equal 'friendly_token', new_user.password_salt
swap_with_encryptor User, :sha1 do
ActiveSupport::SecureRandom.expects(:base64).with(15).returns('friendly_token')
assert_equal 'friendly_token', new_user.password_salt
end
end

test 'should not generate salt if password is blank' do
Expand Down Expand Up @@ -71,24 +85,10 @@ def encrypt_password(user, pepper=User.pepper, stretches=User.stretches, encrypt
end
end

test 'should fallback to devise stretches default configuration' do
swap Devise, :stretches => 1 do
user = new_user
assert_equal encrypt_password(user, nil, 1), user.encrypted_password
assert_not_equal encrypt_password(user, nil, 2), user.encrypted_password
end
end

test 'should respect encryptor configuration' do
User.instance_variable_set(:@encryptor_class, nil)

swap Devise, :encryptor => :sha512 do
begin
user = create_user
assert_equal user.encrypted_password, encrypt_password(user, User.pepper, User.stretches, ::Devise::Encryptors::Sha512)
ensure
User.instance_variable_set(:@encryptor_class, nil)
end
swap_with_encryptor User, :sha512 do
user = create_user
assert_equal user.encrypted_password, encrypt_password(user, User.pepper, User.stretches, ::Devise::Encryptors::Sha512)
end
end

Expand Down

0 comments on commit 0333cae

Please sign in to comment.