I have the following code in C# which generates a hash value from a Base64 encoded string.
var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(key) };
string verb = "post";
string resourceType = "docs";
string resourceId = "dbs/ToDoList/colls/Items";
string date = DateTime.UtcNow.ToString("R");
string payLoad = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}\n{1}\n{2}\n{3}\n{4}\n",
verb.ToLowerInvariant(),
resourceType.ToLowerInvariant(),
resourceId,
date.ToLowerInvariant(),
""
);
byte[] hashPayLoad = hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payLoad));
string signature = Convert.ToBase64String(hashPayLoad);
string authToken = System.Web.HttpUtility.UrlEncode(String.Format(System.Globalization.CultureInfo.InvariantCulture, "type={0}&ver={1}&sig={2}",
keyType,
tokenVersion,
signature));
This works perfectly fine and I wanted to convert it to Java code for my Android app. I checked references from these sources-
C# vs Java HmacSHA1 and then base64
c# and java - difference between hmacsha256 hash
and wrote below code in Java-
String restServiceVersion = "2017-02-22";
String verb = "post";
String resourceType = "docs";
String resourceId = "dbs/ToDoList/colls/Items";
String dateString = org.apache.http.impl.cookie.DateUtils.formatDate(new Date(System.currentTimeMillis()));
String gmtIndex = "GMT";
int index = dateString.indexOf(gmtIndex);
String dateStringFinal = dateString.substring(0, index + 3).toLowerCase();
String payLoad = verb +"\n" + resourceType + "\n" + resourceId + "\n" + dateStringFinal + "\n\n";
System.out.println(payLoad);
String secretAccessKey = MASTER_KEY;
String data = payLoad;
byte[] secretKey = Base64.decode(secretAccessKey, Base64.DEFAULT);
SecretKeySpec signingKey = new SecretKeySpec(secretKey, "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
byte[] bytes = data.getBytes("UTF-8");
byte[] rawHmac = mac.doFinal(bytes);
String authToken = "type=master&ver=1.0&sig=" + Base64.encodeToString(rawHmac, Base64.DEFAULT);
The authToken value generated in Java does not match with C#. Also the byte array generated from Base64 decoding differs.
I am not sure if this is the correct approach. Can someone please take a look? All I need is to convert the above working C# code to Java for my Android app.
Most likely explanation: you have invisible unicode characters (e.g. "zero-width non-joiner") embedded in your BASE64 strings. One platform is stripping these out when base-64 decoding, the other is not, resulting in different keys.
Related
I have created a JWT object with some data. Then I decode that same JWT object just to compare and see if the validation passes. But it does not. Following is the code I have created. What could be the issue?
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
//Make a JWT Token String
String jws = Jwts.builder().setSubject("adam")
.setExpiration(new java.util.GregorianCalendar(2021,
Calendar.NOVEMBER, 8).getTime())
.setIssuer("someUser#mycompany.com")
.claim("groups", new String[] { "user", "admin" })
// HMAC using SHA-512 and 12345678 base64 encoded
.signWith(signatureAlgorithm, "MTIzNDU2Nzg=").compact();
System.out.println("JWTS String: "+ jws.toString());
//=================================================
//Decode the string back
Base64.Decoder decoder = Base64.getDecoder();
String[] chunks = jws.split("\\.");
String header = new String(decoder.decode(chunks[0]));
String payload = new String(decoder.decode(chunks[1]));
String signature = chunks[2];
System.out.println("Header: " + header);
System.out.println("PayLoad: " + payload);
System.out.println("Signature: " + signature);
String tokenWithoutSignature = chunks[0] + "." + chunks[1];
SecretKeySpec secretKeySpec = new
SecretKeySpec("MTIzNDU2Nzg=".getBytes(),signatureAlgorithm.getJcaName());
DefaultJwtSignatureValidator validator = new
DefaultJwtSignatureValidator(signatureAlgorithm,secretKeySpec);
if (validator.isValid(tokenWithoutSignature, signature)){
System.out.println("TOKEN IS VALID");
}else{
System.out.println("TOKEN IS INVALID");
}
You need to base64 decode the secret first.
replace...
SecretKeySpec secretKeySpec = new SecretKeySpec("MTIzNDU2Nzg=".getBytes(),signatureAlgorithm.getJcaName());
with
SecretKeySpec secretKeySpec = new SecretKeySpec(TextCodec.BASE64.decode("MTIzNDU2Nzg="), signatureAlgorithm.getJcaName());
and it should work!
I am working on translating an API from Java into Javascript (nodeJs). The problem is that the signatures generated by the Java code are much shorter than the one in javascript. The results from the getSignature function have different length and as such whenever I generate a signature in javascript the server won't recognize it but it will when it is generated in Java.
I have verified that the values in getSignatureKey are the same in both functions and the getSignature function uses the output from getSignatureKey to encrypt "SOME MESSAGE TO ENCRYPT" which will be the request body in plain text (verified both have the same content and format).
Is there any reason why the output differs in length? Perhaps some encoding problem or something else I'm not seeing.
Using the native crypto library in nodeJs as follows:
var getSignatureKey = function(key, api_key, dateStamp){
let kUser = HMACSHA256("CAS"+api_key, key);
let kDate = HMACSHA256(dateStamp, kUser);
let kService = HMACSHA256(SERVICE_NAME, kDate);
let kSigning = HMACSHA256("cas_request", kService);
return kSigning;
}
var getSignature = function(signature_key){
let signature_bytes = HMACSHA256("SOME MESSAGE TO ENCRYPT", signature_key);
let signature = Buffer.from(signature_bytes).toString('base64');
return signature;
}
var HMACSHA256 = function(message, secret){
let key_bytes = encoder.encode(secret);
let message_bytes = encoder.encode(message);
let hash = crypto.createHmac('sha256', key_bytes).update(message_bytes).digest();
return Uint8Array.from(hash);
}
While in java I have the following code:
public static byte[] getSignatureKey(String key, String apiKey, String dateStamp, String serviceName)
throws Exception {
byte[] kSecret = key.getBytes("UTF8");
byte[] kUser = HmacSHA256("CAS" + apiKey, kSecret);
byte[] kDate = HmacSHA256(dateStamp, kUser);
byte[] kService = HmacSHA256(serviceName, kDate);
byte[] kSigning = HmacSHA256("cas_request", kService);
return kSigning;
}
public static String getSignature(byte[] signature_key) throws Exception {
return Base64.encodeBase64String(HmacSHA256("SOME MESSAGE TO ENCRYPT", signature_key));
}
public static byte[] HmacSHA256(String data, byte[] key) throws Exception {
String algorithm = "HmacSHA256";
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(data.getBytes("UTF8"));
}
So I am not the Crypto wizard by any means but here is some code I have that works in C# but does not return the same b64 string in Java.
c#
string _Cert = "long b64 string here";
string _Pass = "my password";
string lvreturn = "Test";
byte[] lvCertBytes = Convert.FromBase64String(_Cert);
X509Certificate2 lvCertFromBytes = new X509Certificate2(lvCertBytes, _Pass);
SHA1Managed lvSHA1 = new SHA1Managed();
byte[] lvData = Encoding.Unicode.GetBytes(lvReturn);
byte[] lvHash = lvSHA1.ComputeHash(lvData);
RSACryptoServiceProvider lvCryptoProvider = (RSACryptoServiceProvider)lvCertFromBytes.PrivateKey;
byte[] lvSignedBytes = lvCryptoProvider.SignHash(lvHash, CryptoConfig.MapNameToOID("SHA1"));
string lvToken = Convert.ToBase64String(lvSignedBytes);
Java
String certB64 = "long b64 string here";
char[] Pass = "text password".toCharArray();
String alias = "guid looking ID here";
String plaintext = "Test";
byte[] certbytes = Base64.getDecoder().decode(certB64);
InputStream in = new ByteArrayInputStream(certbytes);
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(in,Pass);
KeyStore.PrivateKeyEntry pvk = (KeyStore.PrivateKeyEntry)keystore.getEntry(alias, new KeyStore.PasswordProtection(Pass));
PrivateKey pkey = (PrivateKey)pvk.getPrivateKey();
Signature rsa = Signature.getInstance("SHA1withRSA");
rsa.initSign(pkey);
rsa.update(plaintext.getBytes());
System.out.println("Hash: " + Base64.getEncoder().encodeToString(rsa.sign()));
I have Cert.pfx file that I want to use to use the privatekey to encrypt a https auth segment. I am just ripping the file to a base64 string and stuffing it into the "_Cert" var in C#. I do the same in Java. I want to sign the plaintext message using the private key of the cert and SHA1. The C# code below works and the https server provides a response. Java however is not spitting out the same base64 encoded string. Thanks for some help!
Update: I found a link to another post that is the same as mine but with a couple small diffs, and I didn't want to necro post on it. I followed it exactly removing the messagedigest piece of my original code. I tried reading directly from the pfx file or using the b64 string directly in the code. I am still not getting the same between Java and C#. At this point it has to be something small I am missing with encoding in Java because the C# is basically identical to mine.
Java Digital Signature different to C#
I'm converting a php script to java (for android) but find myself stuck converting the hmac signature process.
PHP which gives correct sign:
$secret = "lT4fhviR7ILvwGeiBJgolfYji1uz/f7B6HQWaWQWVl/sWEz3Kwt4QjzCHWE+MBENOmtgBS6PlN87s+1d7/8bRw==";
$nonce = "1388256620813308";
$postdata = "nonce=1388256620813308";
$path = "/0/private/Balance";
$sign = hash_hmac('sha512', $path . hash('sha256', $nonce . $postdata, true), base64_decode($this->secret), true);
echo $sign;
Hmac = 2IVoBCoadCEivxKVRB/4quJET4DoZV4JdY6bMC2oEYJZuygF5JiAhGrxVMyw2yPhz+KdiwvbzV43cicGamzr2A==
Which is correct and accepted signature
Java (with invalid sign):
String secret = "lT4fhviR7ILvwGeiBJgolfYji1uz/f7B6HQWaWQWVl/sWEz3Kwt4QjzCHWE+MBENOmtgBS6PlN87s+1d7/8bRw==";
String nonce = "1388256620813308";
String postdata = "nonce=1388256620813308";
String path = "/0/private/Balance";
// hash nonce + data
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update((nonce+postdata).getBytes());
byte[] digest = md.digest();
String baseString = path + new String(digest); //this is probably root of evil
// HMAC
Mac mac = Mac.getInstance("HmacSHA512");
SecretKey secretKey = new SecretKeySpec(Base64.decode(secret, Base64.DEFAULT), "HmacSHA512");
mac.init(secretKey);
String sign = new String(Base64.encodeToString(mac.doFinal(baseString.getBytes()), Base64.DEFAULT)).trim();
Log.d(TAG, sign);
Hmac = 7ZQfn+fqMpMEFN5Z/T5UwcqP1uo0JOyAVSn4HEBeE/KotnEf4a5bPOWriiC//gdoEg2kOe60EIr3Lv7irXuejw==
The problem is in the java string conversion of the bytes (even if I add "UTF-8" as characted encoding in getBytes). I know this because if I don add path to the hmac, and just feed it with digest without the string conversion the signature matches.
After posting question I did a quick and dirty test to add bytes from path manually to a new bytes array
byte[] digest = md.digest();
byte[] pBytes = path.getBytes();
int L = digest.length + pBytes.length;
byte[] message = new byte[L];
for (int i=0;i<pBytes.length;i++) {
message[i] = pBytes[i];
}
for (int i=pBytes.length,n=0; n<digest.length; n++) {
message[i+n] = digest[n];
}
String sign = new String(Base64.encodeToString(mac.doFinal(message), Base64.NO_WRAP));
And voilĂ ; the hmac sign matches!
I have solved my problem but keeping question unanswered for some day to say if a better answer is provided that sheds light on this.
Hi I'm having problems creating a Gygia signature. I've tryed everything on this post and I am almost sure my problem resides in wich Base64 I'm using.
Here is what i got right now.
Both methods give me wrong keys
static String sign(String timestamp, String uid, String key) {
String baseString = timestamp + "_" + uid;
String lRet = "";
byte[] baseBytes;
try {
baseBytes = baseString.getBytes("UTF-8");
byte[] secretKeyBytes = org.apache.commons.codec.binary.Base64
.decodeBase64(key.getBytes());
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec(secretKeyBytes, "HmacSHA1"));
byte[] signatureBytes = mac.doFinal(baseBytes);
byte[] encodedSign = org.apache.commons.codec.binary.Base64
.encodeBase64(signatureBytes);
lRet = new String(encodedSign, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return lRet;
}
The other implementation I tried is this one but my signature includes characters like '/' and '+' and Gigya kicks it back.
private String constructSignature(String timestamp, String UID, String pKey) {
// Construct a "base string" for signing
String baseString = timestamp + "_" + UID;
// Convert the base string into a binary array
byte[] binaryBaseString = ConvertUTF8ToBytes(baseString);
// Convert secretKey from BASE64 to a binary array
byte[] binaryKey = ConvertFromBase64ToBytes(pKey);
// Use the HMAC-SHA1 algorithm to calculate the signature
byte[] binarySignature = hmacsha1(baseString, binaryKey);
// Convert the signature to a BASE64
String signature = ConvertToBase64(binarySignature);
return signature;
}
private byte[] ConvertUTF8ToBytes(String pString) {
try {
return pString.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
private byte[] ConvertFromBase64ToBytes(String pBase64String) {
return android.util.Base64.decode(pBase64String,
android.util.Base64.DEFAULT);
}
private String ConvertToBase64(byte[] data) {
String retString = android.util.Base64.encodeToString(data, android.util.Base64.DEFAULT);
return retString;
}
I've gone up and down this code quite a bit, I've used commons.codec Base64 and also the Gigya version with no luck.
Any pointers will be greatly apreciated.
Regards
The error I get back from Gigya with the bad key is:
errorCode:400006
errorMessage:Invalid parameter value
errorDetails:Invalid argument: invalid signature
data:{"statusCode":400,"errorMessage":"Invalid parameter
value","errorCode":400006,"callId":"0106c32c05e14afba1fc93ae0659bb69",
"errorDetails":"Invalid argument: invalid signature","statusReason":"Bad Request"}
Well after reading the post I mentioned I found on the accepted answer the SigUtils class wich basically does all the work for you... took me a while and I hope i didn't waste anyones time. Heres how to generate the key:
String lSig = SigUtils.getOAuth1Signature(query+"_"+expTime, lHashedKey);
And to validate:
boolean valid = SigUtils.validateUserSignature(expTime, query, lHashedKey, lSig);