diff --git a/.gems b/.gems index 4bc6d8a..f669e3e 100644 --- a/.gems +++ b/.gems @@ -2,3 +2,4 @@ cutest -v 1.2.1 rack-test -v 0.6.2 cuba -v 3.1.0 armor -v 0.0.3 +argon2 -v 0.1.4 diff --git a/lib/shield.rb b/lib/shield.rb index 47985e3..ad9c1e0 100644 --- a/lib/shield.rb +++ b/lib/shield.rb @@ -1,4 +1,5 @@ require "armor" +require "argon2" require "uri" module Shield @@ -98,6 +99,8 @@ def password=(password) end module Password + attr_reader :encryption_mode + Error = Class.new(StandardError) # == DOS attack fix @@ -108,18 +111,29 @@ module Password # @see: https://www.djangoproject.com/weblog/2013/sep/15/security/ MAX_LEN = 4096 - def self.encrypt(password, salt = generate_salt) - digest(password, salt) + salt + def self.encrypt(password, salt = generate_salt, mode: :argon2) + @encryption_mode = mode + + case @encryption_mode + when :armor then digest_with_armor(password, salt) + salt + when :argon2 then digest_with_argon2(password) + end end def self.check(password, encrypted) - sha512, salt = encrypted.to_s[0...128], encrypted.to_s[128..-1] - - Armor.compare(digest(password, salt), sha512) + case @encryption_mode + when :armor + sha512, salt = encrypted.to_s[0...128], encrypted.to_s[128..-1] + Armor.compare(digest_with_armor(password, salt), sha512) + when :argon2 + Argon2::Password.verify_password(password, encrypted) + else + raise Error, ":armor and :argon2 are the only supported encryption methods at this time." + end end protected - def self.digest(password, salt) + def self.digest_with_armor(password, salt) raise Error if password.length > MAX_LEN Armor.digest(password, salt) @@ -128,5 +142,9 @@ def self.digest(password, salt) def self.generate_salt Armor.hex(OpenSSL::Random.random_bytes(32)) end + + def self.digest_with_argon2(password) + Argon2::Password.hash(password) + end end end diff --git a/shield.gemspec b/shield.gemspec index cb4f801..d1edaf1 100644 --- a/shield.gemspec +++ b/shield.gemspec @@ -15,6 +15,7 @@ Gem::Specification.new do |s| s.files = `git ls-files`.split("\n") s.add_dependency "armor" + s.add_dependency "argon2" s.add_development_dependency "cutest" s.add_development_dependency "rack-test" s.add_development_dependency "cuba" diff --git a/test/password.rb b/test/password.rb index 8dd92a1..4accdc2 100644 --- a/test/password.rb +++ b/test/password.rb @@ -1,11 +1,18 @@ +require "pry" require_relative "helper" scope do - test "encrypt" do + test "armor encryption" do encrypted = Shield::Password.encrypt("password") assert Shield::Password.check("password", encrypted) end + test "argon2 encryption" do + encrypted = Shield::Password.encrypt("password", mode: :argon2) + assert encrypted.include? 'argon2' + assert Shield::Password.check("password", encrypted) + end + test "with custom 64 character salt" do encrypted = Shield::Password.encrypt("password", "A" * 64) assert Shield::Password.check("password", encrypted)