10

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

arfon
  • 1,131
  • 11
  • 25

4 Answers4

9

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
  • 1,131
  • 11
  • 25
3

I have been tasked with migrating an existing .NET app to Ruby on Rails. I am using the code below to mimic the .NET password hashing. I am very new to Ruby, and don't know .NET at all. The code may not be as clean as it could, but it's a start.

To test, save this as a Ruby script and run with:

ruby script plain_text_password salt_in_base64

e.g.

ruby dotNetHash.rb password123 LU7hUk4MXAvlq6DksvP9SQ==

require "base64"
require "digest"

# Encode password as double-width characters
password_as_text = ARGV.first
double_width_password = []
double_width_password = password_as_text.encode("UTF-16LE").bytes.to_a

# Unencode the salt
salt = Base64.decode64(ARGV[1])

# Concatenate salt+pass
salt_pass_array = []
salt_pass_array = salt.bytes.to_a + double_width_password

# Repack array as string and hash it. Then encode.
salt_pass_str = salt_pass_array.pack('C*')
sha1_saltpass = Digest::SHA1.digest(salt_pass_str)
enc_sha1_saltpass = Base64.encode64(sha1_saltpass).strip()
puts "Encoded SHA1 saltpass is " + enc_sha1_saltpass
  • Wow man, I was playing with this forever and your code did it. I think you are trying to replicate very similar code. I was trying so many different things and was very close to yours, but it was just slightly different. I was packing and unpacking with "S*". Thanks a lot. – Amala Feb 05 '13 at 21:08
2

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
  • 2,407
  • 1
  • 17
  • 15
1

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
  • 524,688
  • 99
  • 697
  • 795
  • You're skipping an important step: converting password into bytes. – Adam Lassek Feb 09 '09 at 23:18
  • OK, so looks like I'll have to drop to the byte level to do this then? – arfon Feb 09 '09 at 23:24
  • @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 Feb 09 '09 at 23:28
  • One potential problem here I guess is that the Ruby Digest library can't hash a byte array, only strings. – arfon Feb 09 '09 at 23:58