Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HMC SHA1 hash - C# producing different hash output than Ruby

I'm trying to quickly get a buggy .Net client library for a third party service I'm using to work. The original library (which works) is written in Ruby, but their equivalent library for DotNet produces differing hash output to the Ruby library.

The Ruby encryption code is as follows:

def self.encrypt_string(input_string)
  raise Recurly::ConfigurationError.new("Recurly gem not configured") unless Recurly.private_key.present?
  digest_key = ::Digest::SHA1.digest(Recurly.private_key)
  sha1_hash = ::OpenSSL::Digest::Digest.new("sha1")
  ::OpenSSL::HMAC.hexdigest(sha1_hash, digest_key, input_string.to_s)
end

The (supposedly) equivalent C# code is:

private static string ComputePrivateHash(string dataToProtect)
{
    if(String.IsNullOrEmpty(Configuration.RecurlySection.Current.PrivateKey))
        throw new RecurlyException("A Private Key must be configured to use the Recurly Transparent Post API.");

    byte[] salt_binary = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(dataToProtect));
    string salt_hex = BitConverter.ToString(salt_binary).Replace("-", "").ToLower();
    string salt = salt_hex.Substring(0, 20);

    HMACSHA1 hmac_sha1 = new HMACSHA1(Encoding.ASCII.GetBytes(Configuration.RecurlySection.Current.PrivateKey));
    hmac_sha1.Initialize();

    byte[] private_key_binary = Encoding.ASCII.GetBytes(salt);
    byte[] passkey_binary = hmac_sha1.ComputeHash(private_key_binary, 0, private_key_binary.Length);

    return BitConverter.ToString(passkey_binary).Replace("-", "").ToLower();
}

The actual hash output differs though, given the same input and private key. What is wrong with the C# method that causes it to produce the wrong hash output?

EDIT
This is the way I would have written the code, though it still produces the wrong output:

private static string ComputePrivateHash(string dataToProtect)
{
    if(String.IsNullOrEmpty(Configuration.RecurlySection.Current.PrivateKey))
        throw new RecurlyException("A Private Key must be configured to use the Recurly Transparent Post API.");

    var privateKey = Configuration.RecurlySection.Current.PrivateKey;
    var hashedData = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(dataToProtect));
    var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(privateKey));
    var hash = hmac.ComputeHash(hashedData);
    return BitConverter.ToString(hash).Replace("-", "").ToLower();
}

CORRECT ANSWER

Thanks to Henning's answer below, I was able to determine that the correct code is:

var privateKey = Configuration.RecurlySection.Current.PrivateKey;
var hashedKey = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(privateKey));
var hmac = new HMACSHA1(hashedKey);
var hash = hmac.ComputeHash(Encoding.ASCII.GetBytes(dataToProtect));
return BitConverter.ToString(hash).Replace("-", "").ToLower();
like image 758
Nathan Ridley Avatar asked Oct 10 '22 04:10

Nathan Ridley


1 Answers

If I understand the code, it seems the Ruby code is hashing the key separately before feeding it to HMAC (which shoudln't be cryptographically necessary, as HMAC will hash a long key itself if necessary), and feeds the hashed key to HMAC together with the original message.

On the other hand, your C# code computes a HMAC with the original key and a hash of the message. (Inexplicably, the variables where you store the hashed message are called salt and private_key_binary, though the content is neither a salt nor a key...)

I cannot imagine that the Ruby and C# libraries would treat HMAC so differently that this is the right thing to do.

like image 138
hmakholm left over Monica Avatar answered Oct 18 '22 20:10

hmakholm left over Monica