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();
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With