#!/usr/bin/ruby require 'digest' # Copyright (c) 2009 Noa Resare (noa@voxbiblia.com) # This code is licensed using the GPLv3 license. class PasswordHasher # Converts a plaintext password into hashed form suitable for persistent # storage. def PasswordHasher.hash(plaintext) d = Digest::SHA1.new salt = get_random_bytes(4) d << salt d << plaintext result = "\001" result << salt result << ITERATIONS / 128 result << iterate_sha1(d.digest!, ITERATIONS) return [result].pack('m')[0..-2] end # Verifies that the plaintext password matches the supplied password hash created # with the PasswordHasher.hash method def PasswordHasher.verify(plaintext, hash) hash = hash.unpack('m')[0] if hash[0] != 1 fail "unknown hash format, doesn't start with \\001" end d = Digest::SHA1.new d << hash[1..4] d << plaintext s = iterate_sha1(d.digest!, hash[5] * 128) return s == hash[6..-1] end private # number of SHA-1 iterations. Need to be a multiple of 128 ITERATIONS = 1024 def PasswordHasher.get_random_bytes(count) bytes = [] count.times { bytes << rand(0xff) } return bytes.pack('C*') end def PasswordHasher.iterate_sha1(input, iterations) d = Digest::SHA1.new iterations.times { d << input input = d.digest!() } return input end end