CWitty
CWitty

Reputation: 4536

Convert C# SHA256 hash to Ruby

I have the C# code:

byte[] bytes = new UnicodeEncoding().GetBytes(input);
return Convert.ToBase64String(new SHA256Managed().ComputeHash(bytes));

that encodes a string to an SHA2 hash, which is then base 64 encoded. I need to convert this into Ruby.

I tried several ways. This is one of them:

hash = Digest::SHA256.digest(val.encode('utf-8'))
encoded = Base64.urlsafe_encode64(hash)

My code all produce the same result that doesn't match. I cannot get them to work. Any help on converting this would be appreciated.

Update

After some messing around I was able to get it working with a hard coded array, the issue is that the C# code adds a 0 after every element in the array. Here is the working ruby code (with the hard coded array):

Digest::SHA256.base64digest([99,0,104,0,97,0,100,0].pack('C*').force_encoding('utf-16'))

I suppose I could iterate through the array but that seems unnecessary.

Upvotes: 4

Views: 1249

Answers (3)

Wand Maker
Wand Maker

Reputation: 18772

Solution:

You need to use UTF-16LE encoding to get same SHA-256 Hash

p hash = Digest::SHA256.base64digest("Hello".encode('UTF-16LE'))

How did I find that?

I used Online C# Tool to create the hash of a string "Hello" using the code below:

using System.IO;
using System;
using System.Text;
using System.Security.Cryptography;

class Program
{
    static void Main()
    {
        byte[] bytes = new UnicodeEncoding().GetBytes("Hello");
        String s =  Convert.ToBase64String(new SHA256Managed().ComputeHash(bytes));
        Console.WriteLine(s);
    }
}

Above program produced the output oH5Pc0MkbIKybzLlb4VBjVGNiy8trnfx1W/nr1Dbl68=

Then, I wrote a Ruby Program to compute Base64-encoded SHA-256 hash of string "Hello", by encoding the string in all the supported char encodings of Ruby to figure out which char encoding will give exact same result.

require "digest/sha2"
require "base64"
s = "Hello"
Encoding.list.each { |e| puts "Encoding: #{e.to_s} => Hash: #{Digest::SHA256.base64digest(s.encode(e)) rescue "error"}"}

After running the program, I came to conclude that if you use UTF-16LE encoding, you will get exact same output.

p hash = Digest::SHA256.base64digest("Hello".encode('UTF-16LE'))

As per documentation of UnicodeEncoding, it:

Represents a UTF-16 encoding of Unicode characters.

Upvotes: 5

CWitty
CWitty

Reputation: 4536

K so it was a multitude of items. In C# UnicodeEncoding().GetBytes(str) apparently adds a 0 after each character to the byte array. So I had to work around that in Ruby. Luckily I was able to use a little ruby magic to do this. The full working solution is below.

byte_array = input.bytes.zip(input.bytes.map{|b| 0}).flatten.compact
Digest::SHA256.base64digest(byte_array.pack('C*').force_encoding('utf-16'))

This gave me an identical hash output matching the value in the database.

Upvotes: 0

Norgerman
Norgerman

Reputation: 79

I think you should change new UnicodeEncoding()to new UTF8Encoding() or just use Encoding.UTF8

Unicode is UTF-16 in C#

Upvotes: 1

Related Questions