Generate SHA 256 Hash using multiple values in Java/Android - java

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

Related

Android java SHA256 and C# sha256 give different values

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

How can i use the same sha512 of c# in java

This is the code used in c#
public static string Encode_SHA512(string input) {
try {
using (SHA512 sha = SHA512.Create()) {
byte[] hash = sha.ComputeHash(Encoding.Unicode.GetBytes(input));
return Convert.ToBase64String(hash);
}
} catch (Exception ex) {
throw new Exception("Error al generar hash SHA512", ex);
}
}
And this is the code used in java (One of many attempts)
public static String Encode_SHA512(String input) {
MessageDigest md = MessageDigest.getInstance("SHA-512");
byte[] digest = md.digest(input.getBytes("UTF-16LE"));
return String.format("%0128x", new BigInteger(1, digest));
}
But always the result is different. How can I use the same encryption of the C # code in java?
The problem is in your Java version you are not converting to a base64 encoded string correctly. If you update your Java code to the following then they both produce the same hash:
public static String Encode_SHA512(String input) throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest md = MessageDigest.getInstance("SHA-512");
byte [] inputBytes = input.getBytes("UTF-16LE");
byte[] digest = md.digest(inputBytes);
return Base64.getEncoder().encodeToString(digest);
}
The line return String.format("%0128x", new BigInteger(1, digest)); is replaced with return Base64.getEncoder().encodeToString(digest); which currently base 64 encodes the resultant hash. Perhaps why the code was confusing is that the byte[] produced by the digest method looked different as in Java the byte type is twos complement (which is signed) so -128 to + 127 whereas in C# it is unsigned so 0 to 255 hence in the debugger any byte with starting with a 1 would look different as it would appear negative in Java but positive in C#.

How to generate secret key using SecureRandom.getInstanceStrong()?

How to generate secret key using SecureRandom.getInstanceStrong()?
With this code I can receive byte array with random values. Is there any easy way to generate key of a given length (256 bits, for example), type (int, String) and format (hex, bin, dec)?
package com.company;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class KeyGen {
public void generate() throws NoSuchAlgorithmException {
SecureRandom random = SecureRandom.getInstanceStrong();
byte[] values = new byte[32]; // 256 bit
random.nextBytes(values);
StringBuilder sb = new StringBuilder();
for (byte b : values) {
sb.append(String.format("%02x", b));
}
System.out.print("Key: ");
System.out.println(sb.toString());
}
}
Output:
Key: 8fcea84897f48f575c22441ece4e7ddb43ac08cd2c1a83fca46c080768468059
Keys should be of a specific type, e.g. AES. They should preferably be kept inside a SecretKey instance or a similar Key derived class.
Keys for modern symmetric ciphers consist of bits. Usually you would not need a human/String representation of them (and this could actually harm security). Store them in a KeyStore or derive them from a password instead. If you do encode them then the representation format is inconsequential, as long as you don't loose your data during the conversions.
This is probably the best way to generate a strong AES key:
public class GenerateStrongAESKey {
public static SecretKey generateStrongAESKey(final int keysize) {
final KeyGenerator kgen;
try {
kgen = KeyGenerator.getInstance("AES");
} catch (final NoSuchAlgorithmException e) {
throw new RuntimeException("AES key generator should always be available in a Java runtime", e);
}
final SecureRandom rng;
try {
rng = SecureRandom.getInstanceStrong();
} catch (final NoSuchAlgorithmException e) {
throw new RuntimeException("No strong secure random available to generate strong AES key", e);
}
// already throws IllegalParameterException for wrong key sizes
kgen.init(keysize, rng);
return kgen.generateKey();
}
public static void main(String[] args) {
SecretKey strongAESKey = generateStrongAESKey(256);
// well, if you must have a human readable string, here it is
// but you've been warned
System.out.println(toHex(strongAESKey.getEncoded()));
}
private static String toHex(final byte[] data) {
final StringBuilder sb = new StringBuilder(data.length * 2);
for (byte b : data) {
sb.append(String.format("%02X", b));
}
return sb.toString();
}
}
Note: this requires the unlimited strength jurisdictional files for the Oracle runtime environment for keys > 128 bits.

How do I truncate HmacSHA256 to 128 bits?

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

why PHP's hash_hmac('sha256') gives different result than java sha256_HMAC

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;
}

Categories