've been trying to port some NodeJS code to Java in order to make HMAC signed requests to an API. I've been able to simplify the code to a point were encoding is clearly affecting the output.
Here's my code in JavaScript:
var APIKey = "secret";
var secretByteArray = CryptoJS.enc.Base64.parse(APIKey);
var hash = CryptoJS.HmacSHA256("Message", secretByteArray);
var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);
//alert(hashInBase64);
document.write(hashInBase64);
Here's the Java Code:
try {
String secret = "secret";
String message = "Message";
byte[] secretByteArray = Base64.getEncoder().encode(secret.getBytes());
//byte[] secretByteArray = Base64.encodeBase64(secret.getBytes("utf-8"), true);
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(secretByteArray, "HmacSHA256");
sha256_HMAC.init(secret_key);
String hash = Base64.getEncoder().encodeToString(sha256_HMAC.doFinal(message.getBytes()));
System.out.println(hash);
}
catch (Exception e){
System.out.println("Error");
}
**What to use instead of Base64.getEncoder().encode? **
Related
How do we translate the following java into C# for .Net Framework 4.8?
private static String getBearerToken(String publicKeyBase64, String apiKey)
{
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
Cipher cipher = Cipher.getInstance("RSA");
byte[] encodedPublicKey = Base64.decodeBase64(publicKeyBase64);
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encodedPublicKey);
PublicKey pk = keyFactory.generatePublic(publicKeySpec);
cipher.init(Cipher.ENCRYPT_MODE, pk);
byte[] encryptedApiKey = Base64.encodeBase64(cipher.doFinal(apiKey.getBytes("UTF-8")));
return new String(encryptedApiKey, "UTF-8");
}
I have tried fiddling with the following (with BouncyCastle package):
public static string GetBearerToken(string publicKeyBase64, string apiKey)
{
var key = Convert.FromBase64String(publicKeyBase64);
var info = SubjectPublicKeyInfo.GetInstance(key);
var pk = PublicKeyFactory.CreateKey(info);
var x = ???(pk);
using (var rsa = new RSACryptoServiceProvider(???))
{
var parameters = new RSAParameters()
{
Modulus = x.???,
Exponent = x.???
};
rsa.ImportParameters(parameters);
var data = Encoding.UTF8.GetBytes(apiKey);
var encryptedBytes = rsa.Encrypt(data, true);
return Convert.ToBase64String(encryptedBytes);
}
}
I also tried this, which runs but does not produce the expected result in the test case:
public static string GetBearerToken(string publicKeyBase64, string apiKey)
{
var keyBytes = Convert.FromBase64String(publicKeyBase64);
var info = SubjectPublicKeyInfo.GetInstance(keyBytes);
var keyParameter = PublicKeyFactory.CreateKey(info);
var encryptEngine = new RsaEngine();
encryptEngine.Init(true, keyParameter);
var dataToEncrypt = Encoding.UTF8.GetBytes(apiKey);
var encryptedBytes = encryptEngine.ProcessBlock(dataToEncrypt, 0, dataToEncrypt.Length);
return Convert.ToBase64String(encryptedBytes);
}
And here is the unit test I'm trying to get passing:
[TestMethod]
public void Utils_GetBearerToken()
{
var publicKey = "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAszE+xAKVB9HRarr6/uHYYAX/RdD6KGVIGlHv98QKDIH26ldYJQ7zOuo9qEscO0M1psSPe/67AWYLEXh13fbtcSKGP6WFjT9OY6uV5ykw9508x1sW8UQ4ZhTRNrlNsKizE/glkBfcF2lwDXJGQennwgickWz7VN+AP/1c4DnMDfcl8iVIDlsbudFoXQh5aLCYl+XOMt/vls5a479PLMkPcZPOgMTCYTCE6ReX3KD2aGQ62uiu2T4mK+7Z6yvKvhPRF2fTKI+zOFWly//IYlyB+sde42cIU/588msUmgr3G9FYyN2vKPVy/MhIZpiFyVc3vuAAJ/mzue5p/G329wzgcz0ztyluMNAGUL9A4ZiFcKOebT6y6IgIMBeEkTwyhsxRHMFXlQRgTAufaO5hiR/usBMkoazJ6XrGJB8UadjH2m2+kdJIieI4FbjzCiDWKmuM58rllNWdBZK0XVHNsxmBy7yhYw3aAIhFS0fNEuSmKTfFpJFMBzIQYbdTgI28rZPAxVEDdRaypUqBMCq4OstCxgGvR3Dy1eJDjlkuiWK9Y9RGKF8HOI5a4ruHyLheddZxsUihziPF9jKTknsTZtF99eKTIjhV7qfTzxXq+8GGoCEABIyu26LZuL8X12bFqtwLAcjfjoB7HlRHtPszv6PJ0482ofWmeH0BE8om7VrSGxsCAwEAAQ==";
var apiKey = "aaaab09uz9f3asdcjyk7els777ihmwv8";
var expectedToken = "rfNjFso4uJbzhwl8E9vizqmHEuD7XDmPqfsRx1L62UoTmURGGLAGgJSl9lCPbgy03Q7NwozFYD4r9BFQY5QpvErHximBDU8HE25urVahm0HnB8VyCIobs684XGSN4GjdequePDrG6xUAxxpvmhqZRlGt1tUjUBeBg6kYqp4EnKHsiaBtvd0THGLZbefpT6UaShASQWYNiEPwEon5wtUMaDwnyQEazDu1H2ieN3r8cCVM3hsak59J/1MP07FQjdFbxdCLfA0DuxgpeKpvLs7WrA767WJSB1QZy7hcP1igSGRfd7Zrp6E7gIukdpC0DApqPKa4XsNTo2AMpG4AwiET2WeKvHn539gbwREXf79kZlYdFDCgTc0Zs7OfDx5ZXMCBKHOS/H3tVFJqXTfEfIF5LOzrFU5pPE0HeNBV0Q2vm8qRwQX0RijnvMOGpdcmXb0qoph4oy8Mj+vjRfFRboMAafttDozBhRmWEmeBB3EjYASm1fToQp5ey6ltCiEt8rjL5PlexxB0u3u2LVJQcDzMVNiiq10t1xyw8qtc6BMOyrKVlIANWglRYOKr9saVBVvDFUcCfsghMjUTDeAwHom4A3cSDWmVlNF9Vs/WqCoUzjQCV0BFPDzeAUbQqt7h7OgFno/+D9n5j1eMro0aXbbHNx71u8YmgPJhdixzFhxM1Pw=";
var token = Utils.GetBearerToken(publicKey, apiKey);
Assert.AreEqual(expectedToken, token, "GetBearerToken");
}
I have figured out a solution from a very similar question: .NET equivalent of Java KeyFactory.getInstance "RSA"/"RSA/ECB/PKCS1Padding"
I also pasted the original java code into jdoodle (https://www.jdoodle.com/online-java-compiler-ide/) to see what it actually does - encrypts with randomization therefore the result is different every time and cannot be unit tested as originally presumed (contrary to the API documentation page, therefore always confirm the assumptions first!)
The following produces randomized results but so far seems to be accepted by the API (uses BouncyCastle package):
public static string GetBearerToken(string publicKeyBase64, string apiKey)
{
var keyBytes = Convert.FromBase64String(publicKeyBase64);
var keyParameter = PublicKeyFactory.CreateKey(keyBytes);
var rsaKeyParameters = (RsaKeyParameters)keyParameter;
var rsaParameters = new RSAParameters
{
Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned(),
Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned()
};
using (var rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(rsaParameters);
var dataToEncrypt = Encoding.UTF8.GetBytes(apiKey);
var encryptedBytes = rsa.Encrypt(dataToEncrypt, false);
return Convert.ToBase64String(encryptedBytes);
}
}
this project in github is a example of this usage https://github.com/RomuloSantanaFadami/fadamipay-autorizador-exemplo-rsa
if you have a problem with a public key formating, try use StringBuilder to make her.
I use like this
internal partial class Criptografia{
private string ChavePublica = String.Empty;
public Criptografia()
{
//if (String.IsNullOrEmpty(PertoBus.Program.PublicKeyQrCode))
//{
StringBuilder chavePublica = new StringBuilder();
chavePublica.AppendLine("-----BEGIN RSA PUBLIC KEY-----");
chavePublica.AppendLine("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
chavePublica.AppendLine("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
chavePublica.AppendLine("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=");
chavePublica.AppendLine("-----END RSA PUBLIC KEY-----");
ChavePublica = chavePublica.ToString();
//}
//else
// ChavePublica = PertoBus.Program.PublicKeyQrCode;
}
public string RsaCriptografarComChavePublica(string pTexto)
{
var bytesToEncrypt = Encoding.UTF8.GetBytes(pTexto);
var encryptEngine = new Pkcs1Encoding(new RsaEngine());
using (var txtreader = new StringReader(ChavePublica.ToString()))
{
var keyParameter = (AsymmetricKeyParameter)new PemReader(txtreader).ReadObject();
encryptEngine.Init(true, keyParameter);
}
var encrypted = Convert.ToBase64String(encryptEngine.ProcessBlock(bytesToEncrypt, 0, bytesToEncrypt.Length));
return encrypted;
}
}
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"));
}
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.
I'm making an app in java and a server with node and as an authentication method I would like to compare two strings.
In java i'm doing this:
try {
String secret = "secret";
String message = "Message";
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
sha256_HMAC.init(secret_key);
String hash = Base64.encodeBase64String(sha256_HMAC.doFinal(message.getBytes()));
System.out.println(hash);
} catch (Exception e){
System.out.println("Error");
}
But I'm still pretty new to node.js and I'm trying to figure out how to do the same there. This is what I've got:
var crypto = require('crypto');
var sha256 = crypto.createHash('HMAC-SHA256').update('Message').digest("base64");
How can I make them do the same? I'm still missing the salt in node.js.
Suggestions?
EDIT:
The answer below helped me find the solution. If other android users has this problem then this code worked for me:
try {
String secret = "secret";
String message = "Message";
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] s53 = sha256_HMAC.doFinal(message.getBytes());
String hash = Base64.encodeToString(s53, Base64.DEFAULT);
Log.e("beadict", hash);
} catch (Exception e){
System.out.println("Error");
}
And this in node:
var crypto = require('crypto');
var hash = crypto.createHmac('SHA256', "secret").update("Message").digest('base64');
You can use this line:
let test = crypto.createHmac('sha256', "key").update("message").digest("base64");
Convert to base64 last.
If you want to use a HMAC then you need to use the method crypto.createHmac(algorithm, key).
I'm still missing the salt in node.js
It seems that you do not use the salt in your Java code...
I'm expanding an iOS project over to Android. My existing application communicates with a server via PHP using an AES encryption system.
Here are the functions that I am using on the PHP side:
Encrypt
function cryptAESEncrypt($string,$key) {
$key = md5($key);
$iv = "1234567890123436"; //IV isn't needed if MCRYPT_MODE is ECB (What we are using)
$data = $data = base64_encode($string);
$algorythm = MCRYPT_RIJNDAEL_128;
$mode = MCRYPT_MODE_ECB;
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128,$key,$data,MCRYPT_MODE_ECB,$iv);
return base64_encode($encrypted);
}
Decrypt
function cryptAESDecrypt($string,$key) {
$key = md5($key);
$iv = "1234567890123436"; //IV isn't needed if MCRYPT_MODE is ECB (What we are using)
$data = base64_decode($string);
$algorythm = MCRYPT_RIJNDAEL_128;
$mode = MCRYPT_MODE_ECB;
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128,$key,$data,MCRYPT_MODE_ECB,$iv);
return base64_decode($decrypted);
}
The general flow of the process is:
md5 hash the $key (brings it down to 16 characters regardless)
Base64 Encode the $string
Encrypt the Base64'ed using 128Bit AES/RIJNDAEL in ECB mode (no IV)
Base64 the encrypted data and returns it as a string.
The decryption works the same but in reverse.
Now I'm just playing with samples but don't seem to be having much luck. I've encrypted the string "test" in PHP using that function ("test" was the key too - MD5'ed to 098f6bcd4621d373cade4e832627b4f6) and I am given the output of "ijzLe/2WgbaP+n3YScQSgQ==".
Now what I tried in Java didn't work as I get an incorrect key length error but I had more luck with a previous snippet earlier. Here's what I had anyway:
String key = "test";
String in = "ijzLe/2WgbaP+n3YScQSgQ==";
SecretKeySpec skeySpec = new SecretKeySpec(md5(key).getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] encryptedByteArray = Base64.decode(in.getBytes(),0);
byte[] decryptedByteArray = cipher.doFinal(encryptedByteArray);
String decryptedData = new String(Base64.decode(decryptedByteArray, 0));
Log.v("NOTE","Data: "+decryptedData);
As I said though, that doesn't work. Now my question is, is there anybody that can help me make my Java code work with the supplied PHP code as I can't change that (had other code working using different PHP snippets).
Thanks to Duncan in the comments I found out the issue was with my MD5 hash function..
Found a working version for reference:
public String md5(String s) {
if (s != null)
{
try { // Create MD5 Hash
MessageDigest digest = java.security.MessageDigest .getInstance("MD5");
digest.update(s.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < messageDigest.length; i++) {
String h = Integer.toHexString(0xFF & messageDigest[i]);
while (h.length() < 2)
h = "0" + h;
hexString.append(h);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
return "";
}