Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add argon2 password hashing feature #23

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gems
Original file line number Diff line number Diff line change
Expand Up @@ -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
30 changes: 24 additions & 6 deletions lib/shield.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "armor"
require "argon2"
require "uri"

module Shield
Expand Down Expand Up @@ -98,6 +99,8 @@ def password=(password)
end

module Password
attr_reader :encryption_mode

Error = Class.new(StandardError)

# == DOS attack fix
Expand All @@ -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)
Expand All @@ -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
1 change: 1 addition & 0 deletions shield.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
9 changes: 8 additions & 1 deletion test/password.rb
Original file line number Diff line number Diff line change
@@ -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)
Expand Down