6

This is a follow up to this question, but I'm trying to port C# code to Java instead of Ruby code to C#, as was the case in the related question. I am trying to verify the encrypted signature returned from the Recurly.js api is valid. Unfortunately, Recurly does not have a Java library to assist with the validation, so I must implement the signature validation myself.

Per the related question above (this), the following C# code can produce the hash needed to validate the signature returned from Recurly:

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();

Recurly provides the following example data on their signature documentation page:

unencrypted verification message: [1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]

private key: 0123456789ABCDEF0123456789ABCDEF

resulting signature: 0f5630424b32402ec03800e977cd7a8b13dbd153-1312701386

Here is my Java implementation:

String unencryptedMessage = "[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]";
String privateKey = "0123456789ABCDEF0123456789ABCDEF";
String encryptedMessage = getHMACSHA1(unencryptedMessage, getSHA1(privateKey));

private static byte[] getSHA1(String source) throws NoSuchAlgorithmException, UnsupportedEncodingException{
    MessageDigest md = MessageDigest.getInstance("SHA-1");
    byte[] bytes = md.digest(source.getBytes("UTF-8"));
    return bytes;
}

private static String getHMACSHA1(String baseString, byte[] keyBytes) throws GeneralSecurityException, UnsupportedEncodingException {
    SecretKey secretKey = new SecretKeySpec(keyBytes, "HmacSHA1");
    Mac mac = Mac.getInstance("HmacSHA1");
    mac.init(secretKey);
    byte[] bytes = baseString.getBytes("ASCII");
    return Hex.encodeHexString(mac.doFinal(bytes));
}

However, when I print out the encryptedMessage variable, it does not match the message portion of the example signature. Specifically, I get a value of "c8a9188dcf85d1378976729e50f1de5093fabb78" instead of "0f5630424b32402ec03800e977cd7a8b13dbd153".

Update

Per @M.Babcock, I reran the C# code with the example data, and it returned the same output as the Java code. So it appears my hashing approach is correct, but I am passing in the wrong data (unencryptedMessage). Sigh. I will update this post if/when I can determine what the correct data to encrypt is- as the "unencrypted verification message" provided in the Recurly documentation appears to be missing something.

Update 2

The error turned out to be the "unencrypted verification message" data/format. The message in the example data does not actually encrypt to the example signature provided- so perhaps outdated documentation? At any rate, I have confirmed the Java implementation will work for real-world data. Thanks to all.

2
  • 1
    Have you verified that you are feeding the same values to the hash functions in both languages? If the inputs differ then so will the outputs. Commented Jan 8, 2012 at 19:26
  • Further to @M.Babcock's point, I'd suggest you change the string and key to something simple, like "Hello" and "World". It looks like you've got identical values in both cases, but it's much easier to verify that with simple strings. Commented Jan 8, 2012 at 19:56

3 Answers 3

5

I think the problem is in your .NET code. Does Configuration.RecurlySection.Current.PrivateKey return a string? Is that value the key you expect?

Using the following code, .NET and Java return identical results.

.NET Code

string message = "[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]";
string privateKey = "0123456789ABCDEF0123456789ABCDEF";

var hashedKey = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(privateKey));
var hmac = new HMACSHA1(hashedKey);
var hash = hmac.ComputeHash(Encoding.ASCII.GetBytes(message));

Console.WriteLine("  Message: {0}", message);
Console.WriteLine("      Key: {0}\n", privateKey);
Console.WriteLine("Key bytes: {0}", BitConverter.ToString(hashedKey).Replace("-", "").ToLower());
Console.WriteLine("   Result: {0}", BitConverter.ToString(hash).Replace("-", "").ToLower());

Result:

  Message: [1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]
      Key: 0123456789ABCDEF0123456789ABCDEF

Key bytes: 4d857d2408b00c3dd17f0c4ffcf15b97f1049867
   Result: c8a9188dcf85d1378976729e50f1de5093fabb78

Java

String message = "[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]";
String privateKey = "0123456789ABCDEF0123456789ABCDEF";

MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] keyBytes = md.digest(privateKey.getBytes("UTF-8"));

SecretKey sk = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(sk);
byte[] result = mac.doFinal(message.getBytes("ASCII"));

System.out.println("  Message: " + message);
System.out.println("      Key: " + privateKey + "\n");
System.out.println("Key Bytes: " + toHex(keyBytes));
System.out.println("  Results: " + toHex(result));

Result:

  Message: [1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]
      Key: 0123456789ABCDEF0123456789ABCDEF

Key Bytes: 4d857d2408b00c3dd17f0c4ffcf15b97f1049867
  Results: c8a9188dcf85d1378976729e50f1de5093fabb78
Sign up to request clarification or add additional context in comments.

2 Comments

This is true. I will have to investigate what is returned from Configuration.RecurlySection.Current.PrivateKey, as I assumed it was a string of the private key.
It turns out the key is in fact in string format. The real culprit turned out to be the unencryptedMessage format. See Update 2 in my question.
1

I suspect the default encoding of the values you're working on may be different. As they do not have it specified, they will use the default encoding value of the string based on the platform you're working on.

I did a quick search to verify if this was true and it was still inconclusive, but it made me think that strings in .NET default to UTF-16 encoding, while Java defaults to UTF-8. (Can someone confirm this?)

If such's the case, then your GetBytes method with UTF-8 encoding is already producing a different output for each case.

Comments

0

Based on this sample code, it looks like Java expects you to have not already SHA1'd your key before creating a SecretKeySpec. Have you tried that?

1 Comment

I have tried just passing in the unencrypted key (not SHA1'd), and then getting the bytes from that to pass into the SecretKeySpec, but to no avail. It returns a different hash, but still not the right one.

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.