Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support for the scrypt sequential memory hard password hashing function.
- Loading branch information
Showing
5 changed files
with
139 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# encoding: binary | ||
module RbNaCl | ||
# Password hashing functions | ||
# | ||
# These hash functions are designed specifically for the purposes of securely | ||
# storing passwords in a way that they can be checked against a supplied | ||
# password but an attacker who obtains a hash cannot easily reverse them back | ||
# into the original password. | ||
# | ||
# Unlike normal hash functions, which are intentionally designed to hash data | ||
# as quickly as they can while remaining secure, password hashing functions | ||
# are intentionally designed to be slow so they are hard for attackers to | ||
# brute force. | ||
# | ||
# All password hashing functions take a "salt" value which should be randomly | ||
# generated on a per-password basis (using RbNaCl::Random, accept no | ||
# subsitutes) | ||
# | ||
# All of them also take a CPU work factor, which increases the amount of | ||
# computation needed to produce the digest. | ||
module PasswordHash | ||
# scrypt: the original sequential memory hard password hashing function. | ||
# This is also the only password hashing function supported by libsodium, | ||
# but that's okay, because it's pretty awesome. | ||
# | ||
# @param [String] password to be hashed | ||
# @param [String] salt to make the digest unique | ||
# @param [Integer] opslimit the CPU cost (e.g. 2**20) | ||
# @param [Integer] memlimit the memory cost (e.g. 2**24) | ||
# @param [Integer] digest_size of the output | ||
# | ||
# @raise [CryptoError] If calculating the digest fails for some reason. | ||
# | ||
# @return [String] The scrypt digest as raw bytes | ||
def self.scrypt(password, salt, opslimit, memlimit, digest_size = 64) | ||
SCrypt.new(opslimit, memlimit, digest_size).digest(password, salt) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# encoding: binary | ||
module RbNaCl | ||
module PasswordHash | ||
# The scrypt sequential memory hard password hashing function | ||
# | ||
# scrypt is a password hash (or password based KDF). That is to say, where | ||
# most hash functions are designed to be fast because hashing is often a | ||
# bottleneck, scrypt is slow by design, because it's trying to "strengthen" | ||
# the password by combining it with a random "salt" value then perform a | ||
# series of operation on the result which are slow enough to defeat | ||
# brute-force password cracking attempts. | ||
# | ||
# scrypt is similar to the bcrypt and pbkdf2 password hashes in that it's | ||
# designed to strengthen passwords, but includes a new design element | ||
# called "sequential memory hardness" which helps defeat attempts by | ||
# attackers to compensate for their lack of memory (since they're typically | ||
# on GPUs or FPGAs) with additional computation. | ||
class SCrypt | ||
extend Sodium | ||
|
||
sodium_type :pwhash | ||
sodium_primitive :scryptxsalsa208sha256 | ||
|
||
sodium_constant :SALTBYTES | ||
|
||
sodium_function :scrypt, | ||
:crypto_pwhash_scryptxsalsa208sha256, | ||
[:pointer, :ulong_long, :pointer, :ulong_long, :pointer, :ulong_long, :size_t] | ||
|
||
|
||
|
||
# Create a new SCrypt password hash object | ||
# | ||
# @param [Integer] opslimit the CPU cost (e.g. 2**20) | ||
# @param [Integer] memlimit the memory cost (e.g. 2**24) | ||
# | ||
# @return [RbNaCl::PasswordHash::SCrypt] An SCrypt password hasher object | ||
def initialize(opslimit, memlimit, digest_size = 64) | ||
# TODO: sanity check these parameters | ||
@opslimit, @memlimit = opslimit, memlimit | ||
|
||
# TODO: check digest size validity | ||
#raise LengthError, "digest size too short" if @digest_size < BYTES_MIN | ||
#raise LengthError, "digest size too long" if @digest_size > BYTES_MAX | ||
|
||
@digest_size = digest_size | ||
end | ||
|
||
# Calculate an scrypt digest for a given password and salt | ||
# | ||
# @param [String] password to be hashed | ||
# @param [String] salt to make the digest unique | ||
# | ||
# @return [String] scrypt digest of the string as raw bytes | ||
def digest(password, salt) | ||
digest = Util.zeros(@digest_size) | ||
salt = Util.check_string(salt, SALTBYTES, "salt") | ||
|
||
self.class.scrypt(digest, @digest_size, password, password.bytesize, salt, @opslimit, @memlimit) || raise(CryptoError, "scrypt failed!") | ||
digest | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# encoding: binary | ||
require 'spec_helper' | ||
|
||
describe RbNaCl::PasswordHash::SCrypt do | ||
let(:reference_password) { vector :scrypt_password } | ||
let(:reference_salt) { vector :scrypt_salt } | ||
let(:reference_opslimit) { RbNaCl::TestVectors[:scrypt_opslimit] } | ||
let(:reference_memlimit) { RbNaCl::TestVectors[:scrypt_memlimit] } | ||
let(:reference_digest) { vector :scrypt_digest } | ||
|
||
it "calculates the correct diest for a reference password/salt" do | ||
digest = RbNaCl::PasswordHash.scrypt( | ||
reference_password, | ||
reference_salt, | ||
reference_opslimit, | ||
reference_memlimit | ||
) | ||
|
||
expect(digest).to eq reference_digest | ||
end | ||
end |