Java supports three MAC algorithms:
HmacMD5
HmacSHA1
HmacSHA256
I however need to sign someting using HMAC-SHA256-128, which is HmacSHA256 but truncated to 128 bits.
This example and variants of has circulated on stackoverflow:
String MAC = hmacHelper.calculatePlainMAC("00000000", "HmacSHA256");
String bgSecretKey="1234567890ABCDEF1234567890ABCDEF";
public String calculatePlainMAC(String ascii, String algorithm)
{
Mac mac = null;
final Charset asciiCs = Charset.forName("US-ASCII");
try
{
SecretKeySpec signingKey = new SecretKeySpec(bgcSecretKey.getBytes(), algorithm);
mac = Mac.getInstance(algorithm);
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(asciiCs.encode(ascii).array());
String result = "";
for (final byte element : rawHmac)
{
result += Integer.toString((element & 0xff) + 0x100, 16);//.substring(1);
}
log.debug("Result: " + result);
return result;
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
return null;
}
catch (InvalidKeyException e)
{
e.printStackTrace();
return null;
}
}
Result:
1051cd18118219e1261f41401891fd1911a91cf1bc1751db13e10617c1221131231c31ab15613f14412c1681d7132178
This is all good, except that I need a 128-bit result, which I know is
FF365893D899291C3BF505FB3175E880
I have no idea how they reached this result. What I do know is that the HMAC algorithm used is HmacSHA256-128. From what I understand this algorithm will generate a 256-bit result, question is, how do I truncate this into a 128-bits result, returning the known result above?
The following line always adds 3 characters to the string, starting with '1'. The commented substring(1) removes the 1. It is used so that single character results get a zero pre-pended.
result += Integer.toString((element & 0xff) + 0x100, 16);//.substring(1);
However, even when you fix this, the result does not contain the truncated result you are expecting.
05cd81829e26f44089fd91a9cfbc75db3e067c221323c3ab563f442c68d73278
This of course depends on the value of bgcSecretKey.
You need to use the same key/algorithm/truncation you used to derive the expected result.
The problem is that the "key" should be converted to it's binary representation, not to the binary representation of the string value!
Ie. bgcSecretKey.getBytes() should be javax.xml.bind.DatatypeConverter.parseHexBinary(bgcSecretKey) or whatever function you prefer to convert hex to binary value.
Then it all works. The whole code:
#Test
public void test_key() throws Exception
{
String SECRET = "1234567890ABCDEF1234567890ABCDEF";
Charset CHARSET = Charset.forName("ISO-8859-1");
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new javax.crypto.spec.SecretKeySpec(DatatypeConverter.parseHexBinary(SECRET), "HmacSHA256");
try
{
sha256_HMAC.init(secret_key);
}
catch (InvalidKeyException e1)
{
throw new RuntimeException("Sha key couldn't be initialized", e1);
}
//Create KVV key, it's HMAC of eight zeros
final byte[] kvv_bytes = sha256_HMAC.doFinal(CHARSET.encode("00000000").array());
StringBuilder kvv = new StringBuilder(16);
for (int i = 0; i < Math.min(16, kvv_bytes.length); i++) //KVV, max 16 bytes
{
kvv.append(Integer.toString((kvv_bytes[i] & 0xff) + 0x100, 16).substring(1));
}
System.out.println(kvv.toString().toUpperCase());
}
Related
Android java SHA256 and C# sha256 give different values. I want java to be same as c#.
c# code:
private static string _getHashSha256(string inputString)
{
string hashString = string.Empty;
using (SHA256Managed hashstring = new SHA256Managed())
{
byte[] bytes = UTF32Encoding.UTF32.GetBytes(inputString);
byte[] hash = hashstring.ComputeHash(bytes);
foreach (byte x in hash)
hashString += String.Format("{0:x2}", x);
}
return hashString;
}
input "111", result "f4b5625de0c6abd88521b87d39f5a4fe33935f27c4ac38a63575ad43d36c7fbb"
android java code:
String password="111";
MessageDigest digest=null;
String hash;
try {
digest = MessageDigest.getInstance("SHA-256");
digest.update(password.getBytes("UTF-32"));
hash = bytesToHexString(digest.digest());
Log.i("sha256", hash);
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e1) {
z = "hash code error: " + e1.getMessage();
}
input is "111". result "12215d42454c57aa1039367b66509e53dcd2d6f9a6e80f9d00b2439ea7ebd43f"
please help me guys!
Thanks guys.
I found the problem was when java converts string to bytes by utf-32, there is additional 4 bytes preceded. it changed resulting sha256.
So I removed that initial 4 bytes as follows:
String password="111";
MessageDigest digest=null;
String hash;
byte[] bbb = user_pass.getBytes("UTF-32");
byte[] ccc = new byte[bbb.length - 4];
System.arraycopy(bbb, 4, ccc, 0, bbb.length - 4);
digest = MessageDigest.getInstance("SHA-256");
digest.update(ccc);
hash_password = bytesToHexString(digest.digest());
I'm trying to setup a secure hash key in java (android). It's not getting the same result as that of php side (which I use as a reference and it works).
I've gone through many similar questions, but (only one, I tried it but doesn't work) none doesn't solved it clearly. Here's the codes I've tested.
// php code
$secureHash = 'ABCD';
$secret = '123AE45F';
echo '<br> using pack--';
echo hash_hmac('sha256', $secureHash, pack('H*', $secret));
echo '<br> without using pack--';
echo hash_hmac('sha256', $secureHash, $secret, false);
result with pack : f7a009f2c3e654fa48296917ab6372ecb7aa2a24c43fccb70af743f66b6dba55
result without pack : fc602f0f6faf2072be9c0b995ee3d603f61414c4beb027b678c90946db6903a2
// Java code
private String getHashCode(String message, String secretKey) {
Mac mac;
String result = null;
try {
byte[] byteKey = secretKey.getBytes(StandardCharsets.UTF_8);
final String hmacSHA256 = "HmacSHA256";
mac = Mac.getInstance(hmacSHA256);
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), hmacSHA256);
sha256HMAC.init(keySpec);
byte[] mac_data = sha256HMAC.doFinal(message.getBytes(StandardCharsets.UTF_8));
result = bytesToHex(mac_data);
System.out.println("getHashCode: result " + result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
return result;
}
In the Java code I'm getting the output as
fc602f0f6faf2072be9c0b995ee3d603f61414c4beb027b678c90946db6903a2
same as php code without pack. How can I achieve the same output as PHP, ie using the pack('H*', $secret) in Java code ?
Thanks to this stackoverflow answer by #rolfl, instead of string.getBytes java function on the secret key, I used his function to get the bytes,
public byte[] hexToString(String hex) {
// hexToString that works at a byte level, not at character level
byte[] output = new byte[(hex.length() + 1) / 2];
for (int i = hex.length() - 1; i >= 0; i -= 2) {
int from = i - 1;
if (from < 0) {
from = 0;
}
String str = hex.substring(from, i + 1);
output[i/2] = (byte)Integer.parseInt(str, 16);
}
return output;
}
Now I get the same as php side for hex type secret key.
C++/CLI Function
I am generating Hash key using C++/CLI and Sending Data and Hashkey Over the network to Other Application which is coded in Java.
But Java Application generates a different HashKey.
Is it due to different applications on different servers that the hashkeys are different ?
Any Idea where am i going wrong ?
Thanking in Advance.
char* EncodeData(char* ap_key, char* ap_sourceData)
{
char* lp_data_to_send = NULL;
int key_len = strlen(ap_key);
String^ lv_data = gcnew String(ap_sourceData);//Getting Data in System String
array<Byte>^ lv_main_data = Encoding::UTF8->GetBytes(lv_data);//Encoding to UTF-8
array<Byte>^key = gcnew array< Byte >(key_len + 2);
Marshal::Copy((IntPtr)ap_key, key, 0, key_len); //Copy key in System Array Byte
// Initialize the keyed hash object.
HMACSHA256^ myhmacsha256 = gcnew HMACSHA256(key);
// Compute the hash of the input file.
array<Byte>^hashValue = myhmacsha256->ComputeHash(lv_main_data);
String^ lv_hex_convert = BitConverter::ToString(hashValue)->Replace("-",""); //Converted to Hexadecimal and replacing '-' with ""
Console::WriteLine(lv_hex_convert);//Converted Hexadecimal Hashkey
//Converting to Char*
lp_data_to_send = (char*)(void*)System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(lv_hex_convert);//Converting again to char* to be send to Calling Function
myhmacsha256->Clear(); //myhmacsha256 clear Instance
return lp_data_to_send;//Return Char*
}
int main()
{
//Secret Key shared by C++/CLi application and Java Application
String ^ lv_key_g = " My Secret Key";
char lv_sourceData[] = { "My data" };
char lv_destinationData[512] = { "" };
char* lp_ret = NULL;
array<Byte>^secretkey = gcnew array<Byte>(65); //Declaring Array
//Converting to UTF-8
secretkey = Encoding::UTF8->GetBytes(lv_key_g);
/*Converting to char* */
pin_ptr<System::Byte> p = &secretkey[0];
unsigned char* pby = p;
//Converting to Char* to send
char* lp_key = reinterpret_cast<char*>(pby);//Converting data to char*
/*End converting to Byte Array*/
lp_ret = EncodeData(lp_key, lv_sourceData);//calling Function
}
JAVA-FUNCTION
String key = "My Key"; //Hash Key Shared by Both Application
String hashKey = "My Data"; //Data Shared by both Application
Mac sha256_HMAC = null;
try {
//Creating Instance
sha256_HMAC = Mac.getInstance("HmacSHA256");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
SecretKeySpec secret_key = null;
try {
secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");//UTF-8 Secret Key
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
try {
sha256_HMAC.init(secret_key); //Init Secret Key
} catch (InvalidKeyException e) {
e.printStackTrace();
}
final byte[] mac_data = sha256_HMAC.doFinal(hashKey.getBytes()); //Get Data in Bytes
String result = "";
for (final byte element : mac_data){
//Using Radix 16 to convert to String
result += Integer.toString((element & 0xff) + 0x100, 16).substring(1); //Converting to Hexadecimal
}
System.out.print(result);//Hashkey Print
return lp_data_to_send;//Return Char* will return a dangling pointer, you should probably return a reference-counted string lv_hex_convert instead. Another suspicious thing is that key is 2 bytes longer than required.
in PHP I have the following function:
base64_encode(hash_hmac('sha256', $data, $secret, false));
I'm trying to create a function in Java that will give the same result for the same "data" and "secret" parameters.
I tried to use this function:
public static String base64sha256(String data, String secret) {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] res = sha256_HMAC.doFinal(data.getBytes());
return Base64.encodeToString(res, Base64.NO_WRAP);
}
But I get different results for the same input
Update: This function works. Enjoy.
public static String base64sha256(String data, String secret) {
String hash = null;
try {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] res = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
hash = getHex(res);
hash = Base64.encodeToString(hash.getBytes("UTF-8"), Base64.NO_WRAP);
} catch (Exception e){}
return hash;
}
static final String HEXES = "0123456789abcdef";
public static String getHex( byte [] raw ) {
if ( raw == null ) {
return null;
}
final StringBuilder hex = new StringBuilder( 2 * raw.length );
for ( final byte b : raw ) {
hex.append(HEXES.charAt((b & 0xF0) >> 4))
.append(HEXES.charAt((b & 0x0F)));
}
return hex.toString();
}
The output of the php function are lowercase hex digits when the fourth parameter is false. Your second java version however produces uppercase hex digits. Either correct the case difference or you could change the fourth parameter of hash_hmac to true and it will probably match with your first Java version.
If trying to match output of drupal_hmac_base64 with Java 8, you can use the following code:
final String ALGORITHM = "HmacSHA256";
Mac mac = Mac.getInstance(ALGORITHM);
SecretKeySpec secret = new SecretKeySpec(authorizationKey.getBytes(), ALGORITHM);
mac.init(secret);
byte[] digest = mac.doFinal(body.getBytes());
hash = Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
return signature.equals(hash);
Note that drupal returns a hash using raw binary data (3rd parameter TRUE). Also, base64 encoding in PHP matches the URL and Filename safe base64 encoder in Java https://docs.oracle.com/javase/8/docs/api/java/util/Base64.html#url.
For someone who might be facing a slight change (not working) in Java result compared to PHP, my issue was in returning the hash from HmacSHA256 as String, while you should return it and pass to Hex as byte[].
Here are the working methods to simulate PHP's hash_hmac()
public String hashValue(String message) {
byte[] hash = toHmacSHA256(message);
String hashHexed = toHex(hash);
return hashHexed;
}
private String toHex(byte[] value) {
String hexed = String.format("%040x", new BigInteger(1, value));
return hexed;
}
private byte[] toHmacSHA256(String value) {
byte[] hash = null;
try {
SecretKey secretKey = new SecretKeySpec(PRIVATE_KEY.getBytes("UTF-8"), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(secretKey);
hash = mac.doFinal(value.getBytes("UTF-8"));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return hash;
}
I have two objects based on which SHA 256 Hash needs to be generated.
First value is a JSONObject
Second value is a String variable.
Ideally, what i need is
Hash hash= new Hash(JSONObject, String);
I couldn't find any hash generation methods which takes two values.
Could anyone help me with this?.
SHA 256 works on a byte array as input. You need to convert your JSONObject and your String to byte arrays, then calculate the SHA 256 hash on the concatenation of these byte arrays.
The proper way of generating a sha256 hashcode using key and value
public static String hashMac(String text, String secretKey)
throws SignatureException {
try {
Key sk = new SecretKeySpec(secretKey.getBytes(), HASH_ALGORITHM);
Mac mac = Mac.getInstance(sk.getAlgorithm());
mac.init(sk);
final byte[] hmac = mac.doFinal(text.getBytes());
return toHexString(hmac);
} catch (NoSuchAlgorithmException e1) {
// throw an exception or pick a different encryption method
throw new SignatureException(
"error building signature, no such algorithm in device "
+ HASH_ALGORITHM);
} catch (InvalidKeyException e) {
throw new SignatureException(
"error building signature, invalid key " + HASH_ALGORITHM);
}
}
public static String toHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
Formatter formatter = new Formatter(sb);
for (byte b : bytes) {
formatter.format("%02x", b);
}
return sb.toString();
}