HMC SHA1 hash - Java producing different hash output than C# - java

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.

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

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.

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?

Related

JAVA DES Encryption Output Encoding

I have DES Encryption Algorithm implementation in JAVA (javax.crypto.Cipher), it is successfully encoding and decoding (most) strings... the problem is that, sometimes, it message specific blocks (since DES uses 8-character blocks in block mode).
In my case, almost always the 3rd block is messed up and rest shows fine.
for example:
key: thisiskey
message to encrypt: Google is an American multinational technology company specializing in Internet-related services
encrypted message (in UTF-8):
mñqè•ÀPŒ�øf"
ߦ\±õ¤ù'È9¢ëyT ÍQEÁ|;ëâÉ÷JWú
Now, when i go and decrypt this, i get this:
Decrypted message:
Google i,í\O¯‹Ýbº-¸�¬ltinational technology company specializHôJ—=ÊÍnternet-related services
As far as i understand the issue, it is due to the fact that UTF-8 CANNOT show all characters and thus, while showing as well as copying for decryption, this problem occurs.
Can anyone suggest me a solution?
Preferably, either a character-set that can handle this, or, a way to convert Binary directly to HEX (that can be output to user) and then Vice Versa (decrypted, after copying/pasting) in JAVA.
EDIT
This is 'approximate' code, not exact (for example encrypted message is not properly paste-able and these are parts of the function, but it should give the idea). Even in base64 encoding , i am unable to get this decrypted properly.
Encrypt Function code:
boolean base64 = true;
key = "thisiskey";
plainText = "Google is an American multinational technology company specializing in Internet-related services";
SecretKeyFactory MyKeyFactory = SecretKeyFactory.getInstance("DES");
byte[] keyBytes = key.getBytes();
DESKeySpec generatedKeySpec = new DESKeySpec(keyBytes);
SecretKey generatedSecretKey = MyKeyFactory.generateSecret(generatedKeySpec);
Cipher generatedCipher = Cipher.getInstance("DES");
generatedCipher.init(Cipher.ENCRYPT_MODE, generatedSecretKey);
byte[] messsageStringBytes = plainText.getBytes();
byte[] encryptedMessage = generatedCipher.doFinal(messsageStringBytes);
String encryptedMessageString = new String(encryptedMessage);
if (base64) {
encryptedMessageString = Base64.getEncoder().encodeToString(encryptedMessageString.getBytes("utf-8"));
}
return encryptedMessageString;
Decrypt Function code:
boolean dbase64 = true;
dkey = "thisiskey";
messageToDecrypt = "mñqè•ÀPŒ�øf\"ߦ\±õ¤ù'È9¢ëyT ÍQEÁ|;ëâÉ÷JWú"; // Message from above code
SecretKeyFactory MyKeyFactory = SecretKeyFactory.getInstance("DES");
byte[] dkeyBytes = dkey.getBytes();
DESKeySpec generatedKeySpec = new DESKeySpec(dkeyBytes);
SecretKey generatedSecretKey = MyKeyFactory.generateSecret(generatedKeySpec);
Cipher generatedCipher = Cipher.getInstance("DES");
generatedCipher.init(Cipher.DECRYPT_MODE, generatedSecretKey);
if (dbase64) {
byte[] decodedBytes = Base64.getDecoder().decode(dencryptedText);
dencryptedText = new String(decodedBytes, "utf-8");
}
byte[] messsageStringBytes = dencryptedText.getBytes();
byte[] encryptedMessage = generatedCipher.doFinal(messsageStringBytes);
String decryptedMessageString = new String(encryptedMessage);
return decryptedMessageString;
"Encrypted message in UTF-8" makes no sense. The ciphertext is binary and not UTF-8. You need to put it into a byte[], not a String.
If you need a String, use Base64 or Hex encoding.
Even in base64 encoding , i am unable to get this decrypted properly.
String encryptedMessageString = new String(encryptedMessage);
if (base64) {
encryptedMessageString = Base64.getEncoder().encodeToString(encryptedMessageString.getBytes("utf-8"));
}
That does not work. You are encoding to Base64 after the data is already broken (by calling new String). Do not put it in a String at all. Go directly from encryptedMessage (the byte[]) to Base64.

Difference in HMAC signature between python and java

I am trying to take some working python code and convert it to java for my usage. The python code below produces the correct signature. The java code using the same key, salt, produces something different and I am at a loss for why. In the Java code I am using the key generated in python (_key) to create the signature.
What I don't understand is, if I print the value of _key in python I get "34ee7983-5ee6-4147-aa86-443ea062abf774493d6a-2a15-43fe-aace-e78566927585". Now if I take that and place it directly into the hmac(new) call I get a different result than if I just leave the _key variable. I assume this has something to do with encoding of some type but I am at a loss.
_s1 = base64.b64decode('VzeC4H4h+T2f0VI180nVX8x+Mb5HiTtGnKgH52Otj8ZCGDz9jRW'
'yHb6QXK0JskSiOgzQfwTY5xgLLSdUSreaLVMsVVWfxfa8Rw==')
_s2 = base64.b64decode('ZAPnhUkYwQ6y5DdQxWThbvhJHN8msQ1rqJw0ggKdufQjelrKuiG'
'GJI30aswkgCWTDyHkTGK9ynlqTkJ5L4CiGGUabGeo8M6JTQ==')
# bitwise and of _s1 and _s2 ascii, converted to string
_key = ''.join([chr(ord(c1) ^ ord(c2)) for (c1, c2) in zip(_s1, _s2)])
#classmethod
def get_signature(cls, song_id, salt=None):
"""Return a (sig, salt) pair for url signing."""
if salt is None:
salt = str(int(time.time() * 1000))
mac = hmac.new(cls._key, song_id, sha1)
mac.update(salt)
sig = base64.urlsafe_b64encode(mac.digest())[:-1]
return sig, salt
This is my Java code. I think ultimately my issue is how I am handling or encoding the AA_KEY but I cannot figure it out.
private static final String AA_KEY = "34ee7983-5ee6-4147-aa86-443ea062abf774493d6a-2a15-43fe-aace-e78566927585";
public void someFunc(String songId) {
salt = "1431875768596"
String sig = hmacSha1(songId + salt, AA_KEY);
sig = StringUtils.replaceChars(sig, "+/=", "-_.");
}
static String hmacSha1(String value, String key) {
try {
// Get an hmac_sha1 key from the raw key bytes
byte[] keyBytes = key.getBytes();
SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1");
// Get an hmac_sha1 Mac instance and initialize with the signing key
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
// Compute the hmac on input data bytes
byte[] rawHmac = mac.doFinal(value.getBytes());
return Base64.encodeBytes(rawHmac);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
I found a couple of similar questions but they didn't help me figure it out sadly. Thanks!
Python HMAC-SHA256 signature differs from PHP signature
Python HMAC-SHA1 vs Java HMAC-SHA1 different results

HMAC value calculated from Java is not matching with Ruby code

I have to write client provided Ruby code in Java. The code uses secret key and Base64 encoding to form hmac value. I tried to write similar code in Java but the resulted hmac value is not matching with the Ruby script result. Please find the below block of code for Java & Ruby along with resulted output.
Java Code:
public static void main(String[] args)
throws NoSuchAlgorithmException, InvalidKeyException
{
// get an hmac_sha1 key from the raw key bytes
String secretKey =
"Ye2oSnu1NjzJar1z2aaL68Zj+64FsRM1kj7I0mK3WJc2HsRRcGviXZ6B4W+/V2wFcu78r8ZkT8=";
byte[] secretkeyByte = Base64.decodeBase64(secretKey.getBytes());
SecretKeySpec signingKey = new SecretKeySpec(secretkeyByte, "HmacSHA1");
// get an hmac_sha1 Mac instance and initialize with the signing key.
String movingFact = "0";
byte[] text = movingFact.getBytes();
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
// compute the hmac on input data bytes
byte[] rawHmac = mac.doFinal(text);
byte[] hash = Base64.encodeBase64(rawHmac);
System.out.println("hash :" + hash);
}
Java Output: hash :[B#72a32604
Ruby Code:
def get_signature()
key = Base64.decode64("Ye2oSnu1NjzJar1z2aaL68Zj+64FsRM1kj7I0mK3WJc2HsRRcGviXZ6B4W+/V2wFcu78r8ZkT8=")
digest = OpenSSL::Digest::Digest.new('sha1')
string_to_sign = "0"
hash = Base64.encode64(OpenSSL::HMAC.digest(digest, key, string_to_sign))
puts "hash: " + hash
end
Ruby Output: hash: Nxe7tOBsbxLpsrqUJjncrPFI50E=
As mentionned in the comments, you're printing the description of your byte array, not the contents:
Replace:
System.out.println("hash :" + hash);
With:
System.out.println("hash: " + new String(hash, StandardCharsets.UTF_8));

How to decrypt Triple Des CryptoJS values in Java class

I was asked to encrypt some text from client side ( web ) before sending it to server side ( java )
So i try to use CryptoJS library for client side.
I encrypt it like this :
var key = "aaaaaaaaaaaaaaaaaaaaaaaa";
var value = "KF169841";
var encryptedString = CryptoJS.TripleDES.encrypt(value, key);
console.log(encryptedString.toString());
And i get something like this : U2FsdGVkX19eYFFHgYGCr3v9/skTOKVp0pLWRNK9JTg=
I use this encryptedString and key in other Decrypt tool online ( Which also use CryptoJS ) and got back exact value KF169841.
After sending this value and key to server ( well key isn't sending directly to server though but for test, it is ), i need to decrypt it using Java.
But i quite don't know how to decrypt it. I'm tried some code from google search but it end up wrong padding if use DESese or get wrong value if i use ECB/NoPadding.
I did try to something like setting sfg for CryptoJS side like:
mode: CryptoJS.mode.EBC,
padding: CryptoJS.pad.NoPadding
But they got javascript exception ( a is not define )
So any have any experience with CryptoJS can help me decrypt this one using java ?
=============================================================
UPDATE : Sorry here my server side code i'm using
/**
* Method To Decrypt An Ecrypted String
*/
public String decrypt(String encryptedString, String myEncryptionKey) {
String decryptedText = null;
try {
byte[] keyAsBytes = myEncryptionKey.getBytes("UTF8");
KeySpec myKeySpec = new DESedeKeySpec(keyAsBytes);
SecretKeyFactory mySecretKeyFactory =
SecretKeyFactory.getInstance("DESede");
Cipher cipher = Cipher.getInstance("DESede/ECB/NoPadding");
SecretKey key = mySecretKeyFactory.generateSecret(myKeySpec);
cipher.init(Cipher.DECRYPT_MODE, key);
// BASE64Decoder base64decoder = new BASE64Decoder();
// byte[] encryptedText = base64decoder.decodeBuffer(encryptedString);
byte[] encryptedText = org.apache.commons.codec.binary.Base64.decodeBase64(encryptedString);
byte[] plainText = cipher.doFinal(encryptedText);
decryptedText= bytes2String(plainText);
} catch (Exception e) {
e.printStackTrace();
}
return decryptedText;
}
According to the documentation, your encryptedString variable contains structured data that must be split apart to be sent to Java code. You will need to send encryptedString.iv and encryptedString.ciphertext to your Java code. If you continue to use passwords (see below), you will need to send encryptedString.salt as well.
If you pass your key as a string it will be interpreted as a password and a key will be derived from it. If you actually want to pass an explicit key, follow the documentation and specify the IV and key as suggested by the code snippet below. If you stick with supplying a password, then you must figure out the derivation scheme and use the same process in your Java code.
// Code snippet from http://code.google.com/p/crypto-js/#Custom_Key_and_IV
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"></script>
<script>
var key = CryptoJS.enc.Hex.parse('000102030405060708090a0b0c0d0e0f');
var iv = CryptoJS.enc.Hex.parse('101112131415161718191a1b1c1d1e1f');
var encrypted = CryptoJS.AES.encrypt("Message", key, { iv: iv });
</script>
Regarding your Java code, it looks mostly OK (although there is plenty of room for error with string conversions). However, you probably want to convert your key from hex to binary rather than grabbing the bytes:
byte[] keyAsBytes = DatatypeConverter.parseHexBinary(myEncryptionKey);
This assumes you alter your JavaScript code to pass the literal key value.
You will also need to switch to DESede/CBC/PKCS5Padding and pass an IVParameterSpec object to your Cipher.init call, specifying the IV value sent from your Java Script code.

Java equivalent of Fantom HMAC using SHA1

I'm having trouble doing the following in Java. Below is the Fantom code from the documentation for the the tool I am using.
// compute salted hmac
hmac := Buf().print("$username:$userSalt").hmac("SHA-1", password.toBuf).toBase64
// now compute login digest using nonce
digest := "${hmac}:${nonce}".toBuf.toDigest("SHA-1").toBase64
// our example variables
username: "jack"
password: "pass"
userSalt: "6s6Q5Rn0xZP0LPf89bNdv+65EmMUrTsey2fIhim/wKU="
nonce: "3da210bdb1163d0d41d3c516314cbd6e"
hmac: "IjJOApgvDoVDk9J6NiyWdktItl0="
digest: "t/nzXF3n0zzH4JhXtihT8FC1N3s="
I've been searching various examples through Google but none of them produce the results the documentation claims should be returned.
Can someone with Fantom knowledge verify if the example in the documentation is correct?
As for the Java side, here is my most recent attempt
public static String hmacSha1(String value, String key) {
try {
// Get an hmac_sha1 key from the raw key bytes
byte[] keyBytes = key.getBytes("UTF-8");
SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1");
// Get an hmac_sha1 Mac instance and initialize with the signing key
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
// Compute the hmac on input data bytes
byte[] rawHmac = mac.doFinal(value.getBytes("UTF-8"));
// Convert raw bytes to Hex
byte[] hexBytes = new Hex().encode(rawHmac);
// Covert array of Hex bytes to a String
return new String(hexBytes, "UTF-8");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
However, when I call the method with the following parameters
jack:6s6Q5Rn0xZP0LPf89bNdv+65EmMUrTsey2fIhim/wKU=
pass
I get
22324e02982f0e854393d27a362c96764b48b65d
Not sure where the docs came from - but they could be out-of-date - or wrong. I would actually run the Fantom code to use as your reference to make sure you're testing the right stuff ;)
You can take a look at the Java source for sys::Buf.hmac: MemBuf.java
I would also recommend separating out the 3 transformations. Make sure your raw byte array matches in both Fantom and Java, then verify the digest matches, and finally the Base64 encoding. Be alot easier to verify each stage in your code.
Turns out it was just my own lack of knowledge and with enough trial and error I was able to figure it out by doing the following:
//username: "jack"
//password: "pass"
//userSalt: "6s6Q5Rn0xZP0LPf89bNdv+65EmMUrTsey2fIhim/wKU="
//nonce: "3da210bdb1163d0d41d3c516314cbd6e"
//hmac: "IjJOApgvDoVDk9J6NiyWdktItl0="
//digest: "t/nzXF3n0zzH4JhXtihT8FC1N3s="
...
// initialize a Mac instance using a signing key from the password
SecretKeySpec signingKey = new SecretKeySpec(password.getBytes(), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
// compute salted hmac
byte[] hmacByteArray = mac.doFinal((username + ':' + userSalt).getBytes());
String hmacString = new String(Base64.encodeBase64(hmacByteArray));
// hmacString == hmac
// now compute login digest using nonce
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update((hmacString + ':' + nonce).getBytes());
byte[] digestByteArray = md.digest();
String digestString = new String(Base64.encodeBase64(digestByteArray));
// digestString == digest
Used org.apache.commons.codec.binary.Base64 to encode the byte arrays.

Categories

Resources