views:

1461

answers:

4

I have a large database of users (~200,000) that I'm transferring from a ASP.NET application to a Ruby on Rails application. I don't really want to ask every user to reset their password and so I'm trying to re-implement the C# password hashing function in Ruby.

The old function is this:

public string EncodePassword(string pass, string saltBase64)
 {
     byte[] bytes = Encoding.Unicode.GetBytes(pass);
     byte[] src = Convert.FromBase64String(saltBase64);
     byte[] dst = new byte[src.Length + bytes.Length];
     Buffer.BlockCopy(src, 0, dst, 0, src.Length);
     Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
     HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");
     byte[] inArray = algorithm.ComputeHash(dst);
     return Convert.ToBase64String(inArray);
 }

An example hashed password and salt is (and the password used was "password"):

Hashed password: "weEWx4rhyPtd3kec7usysxf7kpk=" Salt: "1ptFxHq7ALe7yXIQDdzQ9Q==" Password: "password"

Now with the following Ruby code:

require "base64"
require "digest/sha1"


password = "password"
salt = "1ptFxHq7ALe7yXIQDdzQ9Q=="

concat = salt+password

sha1 = Digest::SHA1.digest(concat)

encoded = Base64.encode64(sha1)

puts encoded

I'm not getting the correct password hash (I'm getting "+BsdIOBN/Vh2U7qWG4e+O13h3iQ=" instead of "weEWx4rhyPtd3kec7usysxf7kpk="). Can anyone see what the problem might be?

Many thanks

Arfon

A: 

You need to unencode the salt to convert it back to it's byte representation and then concatenate that with the password to get the hashed password value. You're using the encoding salt string directly (which is a different salt) and thus it is hashing to something different.

require "base64"
require "digest/sha1"
password = "password"
salt = Base64.decode64("1ptFxHq7ALe7yXIQDdzQ9Q==")
concat = salt+password
sha1 = Digest::SHA1.digest(concat)
encoded = Base64.encode64(sha1)
puts encoded
tvanfosson
You're skipping an important step: converting password into bytes.
Adam Lassek
OK, so looks like I'll have to drop to the byte level to do this then?
arfon
@Adam -- not sure. In .NET it's putting them in a byte array to hand off to the hashing algorithm. Ruby may actually do the same thing using a simple string. If they are stored as null-terminated it is essentially the same as the buffer in the .NET example. I'd give it whirl and see if it works.
tvanfosson
One potential problem here I guess is that the Ruby Digest library can't hash a byte array, only strings.
arfon
+1  A: 

You are pretty close. Unfortunately Ruby has no built-in unicode support at the moment, and your hashing function relies on it. There are workarounds. Look around the site on how to do unicode in Ruby. BTW, I think you forgot to base64 decode the salt, it looks like the ASP.net function does that.

Aram Verstegen
A: 

I also tried this: (looking for hash of "weEWx4rhyPtd3kec7usysxf7kpk=")

require "base64"
require "digest/sha1"
salt = "1ptFxHq7ALe7yXIQDdzQ9Q=="
password = "password"

salt_byte_array = []

salt.each_byte do |b|
  salt_byte_array << b
end

password_byte_array = []

password.each_byte do |b|
  password_byte_array << b
end

full = password + salt

hash = Digest::SHA1.digest("#{full}")
encode = Base64.encode64(hash)
puts "Digest: #{encode}"

But to no avail...

arfon
+3  A: 

Just a quick update, a colleague of mine has solved this:

require "base64"
require "digest"
require "jcode"


def encode_password(password, salt)
 bytes = ""
 password.each_char { |c| bytes += c + "\x00" }
 salty = Base64.decode64(salt)
 concat = salty+bytes
 sha1 = Digest::SHA1.digest(concat)
 encoded = Base64.encode64(sha1).strip()
 puts encoded
end
arfon