Skip to content

Instantly share code, notes, and snippets.

@mystix
Created April 15, 2014 05:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mystix/10703810 to your computer and use it in GitHub Desktop.
Save mystix/10703810 to your computer and use it in GitHub Desktop.
Secure implementation of salted PBKDF2 password hashing in Ruby (see https://crackstation.net/hashing-security.htm)
# Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm).
# Copyright (c) 2013, Taylor Hornby
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
require 'securerandom'
require 'openssl'
require 'base64'
# Salted password hashing with PBKDF2-SHA1.
# Authors: @RedragonX (dicesoft.net), havoc AT defuse.ca
# www: http://crackstation.net/hashing-security.htm
module PasswordHash
# The following constants can be changed without breaking existing hashes.
PBKDF2_ITERATIONS = 1000
SALT_BYTE_SIZE = 24
HASH_BYTE_SIZE = 24
HASH_SECTIONS = 4
SECTION_DELIMITER = ':'
ITERATIONS_INDEX = 1
SALT_INDEX = 2
HASH_INDEX = 3
# Returns a salted PBKDF2 hash of the password.
def self.createHash( password )
salt = SecureRandom.base64( SALT_BYTE_SIZE )
pbkdf2 = OpenSSL::PKCS5::pbkdf2_hmac_sha1(
password,
salt,
PBKDF2_ITERATIONS,
HASH_BYTE_SIZE
)
return ["sha1", PBKDF2_ITERATIONS, salt, Base64.encode64( pbkdf2 )].join( SECTION_DELIMITER )
end
# Checks if a password is correct given a hash of the correct one.
# correctHash must be a hash string generated with createHash.
def self.validatePassword( password, correctHash )
params = correctHash.split( SECTION_DELIMITER )
return false if params.length != HASH_SECTIONS
pbkdf2 = Base64.decode64( params[HASH_INDEX] )
testHash = OpenSSL::PKCS5::pbkdf2_hmac_sha1(
password,
params[SALT_INDEX],
params[ITERATIONS_INDEX].to_i,
pbkdf2.length
)
return pbkdf2 == testHash
end
# Run tests to ensure the module is functioning properly.
# Returns true if all tests succeed, false if not.
def self.runSelfTests
puts "Sample hashes:"
3.times { puts createHash("password") }
puts "\nRunning self tests..."
@@allPass = true
correctPassword = 'aaaaaaaaaa'
wrongPassword = 'aaaaaaaaab'
hash = createHash(correctPassword)
assert( validatePassword( correctPassword, hash ) == true, "correct password" )
assert( validatePassword( wrongPassword, hash ) == false, "wrong password" )
h1 = hash.split( SECTION_DELIMITER )
h2 = createHash( correctPassword ).split( SECTION_DELIMITER )
assert( h1[HASH_INDEX] != h2[HASH_INDEX], "different hashes" )
assert( h1[SALT_INDEX] != h2[SALT_INDEX], "different salt" )
if @@allPass
puts "*** ALL TESTS PASS ***"
else
puts "*** FAILURES ***"
end
return @@allPass
end
def self.assert( truth, msg )
if truth
puts "PASS [#{msg}]"
else
puts "FAIL [#{msg}]"
@@allPass = false
end
end
end
PasswordHash.runSelfTests
@wconrad
Copy link

wconrad commented Mar 1, 2016

Thanks for posting this gist. It's gotten me past a frustrating morning of trying to match .NET's password hash algorithm in in Ruby. This code runs in Ruby 2.3 and passes its self test. The way OpenSSL::PKCS5::pbkdf2_hmac_sha1 is used here works perfectly for hashing .NET 4.X format version 2 passwords. The way this code represents the hashed password is different than the way .NET does it, that's all.

@lzap
Copy link

lzap commented May 10, 2021

There is a tiny bug - method encode64 adds CRLF when you increase HASH_BYTE_SIZE. Use strict_encode64 encode instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment