5

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();
9
  • Maybe stackoverflow.com/questions/3393790/… can help? Commented Aug 24, 2011 at 12:09
  • 1
    You'd think, but nope :( Commented Aug 24, 2011 at 12:10
  • What have you done to debug the problem yourself? The very least one could expect would be that you investigate whether the binary inputs to the HMAC primitive are the same or not. Your C# version is doing some strange stuff with hex-encoding and substring extraction that does not seem to be present in your Ruby code. So are you sure the Ruby version is actually doing all this behind your back? Commented Aug 24, 2011 at 12:19
  • Heh, yes I have done plenty to try and debug the problem. I've rewritten the method in several different ways, running unit tests against each to check the output however none of my attempts have worked. I agree with the substring code looking out of place, though I'm a bit sketchy on encryption and I didn't write this code; it's what was present in the library. Commented Aug 24, 2011 at 12:23
  • Could you show us the output for both on a sample input and key? Commented Aug 24, 2011 at 12:23

1 Answer 1

4

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Excellent, you're right, I wasn't interpreting what I was reading correctly. Thanks for talking me through it; I've updated my question to include the correct answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.