I need to send a POST request to api.example.com. I need to generate singin KEY to send a POST request. The KEY must be generated by the following formula
HEX (HMAC-SHA384({apiPath} + {nonce} + JSON({body}),{secretKey}))
I've found a sample of Javascript code how to genereate signing key
javascript
const crypto = require('crypto');
const publicKey = '';
const secretKey = '';
const apiPath = '/v3/auth/kuna_codes/issued-by-me';
const nonce = new Date().getTime();
const body = {};
const signatureString = `${apiPath}${nonce}${JSON.stringify(body)}`;
const signature = crypto
.createHmac('sha384', secretKey)
.update(signatureString)
.digest('hex');
console.log(signature);
I need a Java code that generates a signing key by the formula provided above
Something like this
package com.vorontsov.tbe.main;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
public class KunaSignatureGenerator {
private static final String ALGORITHM = "HmacSHA384";
private static final String API_PATH = "/v3/auth/kuna_codes/issued-by-me";
private static final String SECRET_KEY = "secret";
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException, JsonProcessingException {
var signature = new KunaSignatureGenerator().generateSignature(new RequestParams("stringVal"));
System.out.println(signature);
}
private String generateSignature(RequestParams requestParams) throws NoSuchAlgorithmException, InvalidKeyException, JsonProcessingException {
var nonce = "" + System.currentTimeMillis();
var body = new ObjectMapper().writeValueAsString(requestParams);
final String signatureString = API_PATH + nonce + body;
SecretKeySpec signingKey = new SecretKeySpec(SECRET_KEY.getBytes(), ALGORITHM);
final Mac mac = Mac.getInstance(ALGORITHM);
mac.init(signingKey);
return HexFormat.of().formatHex(mac.doFinal(signatureString.getBytes()));
}
private static record RequestParams(#JsonProperty("stringParam") String stringParams) {
}
}
Related
I am trying to create a base64 hmac for sha256. I have two codes for the same, one in JS and other in Java,though I am doing it in android and a kotlin one will help me as well. I have mostly used codes from other SO answers only. The one in node js seems to give correct results and matches with the backend but in java is does not. Here are the codes
const crypto = require('crypto')
const base64urlm = require('base64url')
console.log('hello')
var yourMessage = 'Pritish8-s9';
var sharedSecret = 'Nilesh/ev12/';
//generate hmac sha256 hash
var hmacSignature = crypto.createHmac('SHA256', new Buffer(sharedSecret, 'base64')).update(yourMessage).digest('base64');
hmacSignature = base64urlm.fromBase64(hmacSignature)
console.log(hmacSignature)
It gives the output as
_eiq1peyHuPx8yQwzORwoT7wcNdzv2Y0LUp_E70aIvM
The above is the correct value. Now following is the java code.
package com.drivertest.hmactest;
import android.util.Log;
import org.apache.commons.codec.binary.Hex;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class HMAC {
public static String cal() {
try {
String secret = "Nilesh/ev12/";
String message = "Pritish8-s9";
byte[] secretByteArray = new byte[0];
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
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 = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
hash = Base64.getEncoder().encodeToString(sha256_HMAC.doFinal(message.getBytes()));
}
System.out.println("hash "+hash);
Log.d("++++",hash);
return hash;
}
catch (Exception e){
System.out.println("Error");
}
return "";
}
public static String encode(String key, String data) {
try {
String secret = "Nilesh/ev12/";
String message = "Pritish8-s9";
key=secret;
data=message;
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
sha256_HMAC.init(secret_key);
SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
sha256_HMAC.init(secretKey);
String hash = android.util.Base64.encodeToString(sha256_HMAC.doFinal(message.getBytes()), android.util.Base64.DEFAULT);
Log.d("++",hash);
return Hex.encodeHexString(sha256_HMAC.doFinal(data.getBytes(StandardCharsets.UTF_8)));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
return null;
}
}
It is a class where I have attempted to do it in different ways with other SO answers. Unfortunately I get a value like
8i/ce0u0GZ+JhL3yblsGhaMnFC0UKkUwJSQSXZ3536s=
or
f22fdc7b4bb4199f8984bdf26e5b0685a327142d142a45302524125d9df9dfab
So can anyone help me in writing java/kotlin code for the same and get the same value like the above in nodejs ?
PS : I have verified the java results on random sites and they seem to match, but my api is failing with this value , and will only work if it can match with that os nodejs, so it is incorrect in that sense.
Thank you :)
There are two differences between your nodejs and Java implementations.
First and the most important: in nodejs you decode your secret using base64, while in Java you encode it:
Base64.getEncoder().encode(secret.getBytes())
Replace it with:
Base64.getDecoder().decode(secret.getBytes())
Second, in nodejs you use URL variant of base64 (base64urlm) when encoding the final result. In Java you use a regular base64. Replace it with:
Base64.getUrlEncoder().encodeToString(...)
I have been reading several similar questions and based on that for my initial code. Unfortunately, I still can't get it to work.
key="fb52042ada308dd1d4dfd8a3870d5ab5"
iv = "bb8e0b158f57f63dfeea86e24af1abfc"
data = {"MerchantId":"0000000000000001"}
Get SHA256 from "data" (dataSha256)
Get encryption from Encrypt dataSha256 + iv + key
Result of Hexa encryption, similar to:
fd72fcc16b66d04cf0f4dd2265a59eb675103482bae806b405bb85595056f5770b3202b42d42a87b767892591a333eb6b5c9ad3ef34f4d415f8d3bbc3d0f389e2e5b6f7cd915520c7b2c19225680728b
When migrating the code from Java to Node.js, I don't get similar result. The first problem is that the "iv" should be 16 bytes, however in the code it is 32.
JAVA EXTRACT (original)
public class AesEncryption implements SymmetricEncryptionComponent {
Map<String, String> initParams;
String key, iv;
String mode, encoding;
String keyFile;
String ENCODING = "ISO-8859-1";
public AesEncryption() {
Security.addProvider(new BouncyCastleProvider());
}
// PARAMETERS INITIALITATION
public void setInitParams()
{
initParams=params;
key=” 1ea9a91b0ba908b44f598d2822499441”;
iv= "f20946931dd6e8594dc6f469b5e583ab";
mode= "AES/CBC/PKCS7Padding";
encoding= "HEX";
if(encoding.equalsIgnoreCase("BASE64")&&encoding.equalsIgnoreCase("HEX"))
throw new IllegalArgumentException("AES.ENCODING can only be 'HEX' of 'BASE64'")
}
// INFORMATION CIPHERING return encodeBase24
public String encrypt(String data) {
byte[] output = null;
try {
byte[] keyBytes = decode(key);
byte[] input = data.getBytes(ENCODING);
AlgorithmParameterSpec ivSpec = new
IvParameterSpec(Hex.decodeHex(iv.toCharArray()));
SecretKeySpec keySpec = null;
keySpec = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance(mode);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
output = cipher.doFinal(input);
} catch (Exception e) {
throw new EncryptionException("Error", e);
}
return encode(output);
}
// INFORMATION ENCODE
private String encode(byte[] output) {
if (mode.equalsIgnoreCase("BASE64"))
return Base64.encodeBase64String(output);
else
return new String(Hex.encodeHex(output));
}
// INFORMATION DECODE
private byte[] decode(String data) throws DecoderException {
if (data.indexOf("=") > 0 || data.indexOf("+") > 0)
return Base64.decodeBase64(data);
else
return Hex.decodeHex(data.toCharArray());
}
}
NODE EXTRACT (using crypto-js)
const cryptojs = require("crypto-js");
const crypto = require("crypto");
let jsonData = {"MerchantId":"0000000000000001"};
let key = 'fb52042ada308dd1d4dfd8a3870d5ab5';
let iv = 'bb8e0b158f57f63dfeea86e24af1abfc';
const jsonDataSha256 =
crypto.createHash('sha256').update(JSON.stringify(jsonData)).digest('hex');
key = cryptojs.enc.Latin1.parse(key); //Convierte hex string -> word array
iv = cryptojs.enc.Latin1.parse(iv);
jsonDataSha256Bin = cryptojs.enc.Latin1.parse(jsonDataSha256); //Convert hex
string -> word array
console.log(key);
console.log(iv);
console.log(jsonDataSha256);
let encrypted = cryptojs.AES.encrypt(jsonDataSha256Bin, key, {
iv: iv,
mode: cryptojs.mode.CBC,
padding: cryptojs.pad.Pkcs7,
});
encrypted = encrypted.toString();
const salida = crypto.createHash('sha256').update(encrypted).digest('hex');
console.log(`${salida}`);
//Equals to ce994c4d2b1f398ff0bed22c4b48e1f2170dbf26baf2eaacec96ea6b31667cd6
//No match to Java output.
What will I be doing wrong? Any help is appreciated!
I generated a private and public key in javascript like this.
import crypto from "crypto";
/*export const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
});*/
const pair = crypto.generateKeyPairSync("rsa", { modulusLength: 2048 });
export const privateKey = pair.privateKey.export({
type: "pkcs1",
format: "pem",
});
export const publicKey = pair.publicKey.export({
type: "pkcs1",
format: "pem",
});
Then i use the private key to create a signature for a jsonfile like this, and the public key to verify it before i return the signature.
//Lav signatur
const signate = crypto.createSign("SHA384");
signate.update(Buffer.from(licenseRelationship, "utf-8"));
const signature = signate.sign(privateKey, "hex");
const verifier = crypto.createVerify("SHA384");
// verificer signature, besked
verifier.update(Buffer.from(licenseRelationship, "utf-8"));
const verificationResult = verifier.verify(publicKey, signature, "hex");
This works perfectly, and then i return the json and the signature as a http response.
I recieve it in c# code and store the two components so im can use them later on request.
Upon request i fetch the two components and want to use the signature to check if the json has been tampered with.
I also has the public key in this code.
I do it like this.
string licenseRelationshipJson = licenseRelationshipDAO.getLicenseRelationshipWithoutSignatureAsJson(licenseRelationship);
byte[] signature = Encoding.UTF8.GetBytes(licenseRelationship.signature);
byte[] licenseRelationshipJsonAsArray = Encoding.UTF8.GetBytes(licenseRelationshipJson);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048);
result = rsa.VerifyData(licenseRelationshipJsonAsArray, signature,
HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1);
if (result)
{
log.write("Message verified ", null);
} else
{
log.write("Message not Verified ", null);
}
All debug code and exception handling removed.
I'm a crypto virgin, and am trying to understand this. But i must have misunderstood something serious.
I have the public key as a string (not base64 encoded)
Ive checked the json, and it is the exact same bytes when signed in Javascript as when being verified in c#
The public key is not used in this process. That has to be wrong i think ?
How do i get the public key into the RWACryptoServiceProvider ?
Im sure im using RWACryptoServiceProvider wrong.
EDIT....:
Ive tried this instead, but still to no avail.
string licenseRelationshipJson = licenseRelationshipDAO.getLicenseRelationshipWithoutSignatureAsJson(licenseRelationship);
byte[] signature = Encoding.UTF8.GetBytes(licenseRelationship.signature);
byte[] licenseRelationshipJsonAsArray = Encoding.UTF8.GetBytes(licenseRelationshipJson);
byte[] asBytes = Encoding.ASCII.GetBytes(DataStorage.Instance.PUBLIC_KEY);
char[] publicKeyAsArray = Encoding.ASCII.GetChars(asBytes);
ReadOnlySpan<char> publicKeyChars = publicKeyAsArray;
RSA rsa = RSA.Create();
try
{
rsa.ImportFromPem(publicKeyChars);
result = rsa.VerifyData(licenseRelationshipJsonAsArray, signature, HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1);
} catch (CryptographicException cex)
{
log.write("Something went wrong with the crypto verification process", cex);
}
.
.
.
Thankyou for your time.
I need to calculate the HMAC-sha256 signature in JavaScript. I am using the following code.
crypto.createHmac('sha256','abc123').update('{"video-id":"212zpS6bjN77eixPUMUEjR", "exp-time": 1458396066}').digest('hex');
console.log( '1458396066' + '~'+ res);
The resulting hash I get is: 1458396066~d87d121117b46dc28ffec1117cd44cb114b32c1d7bfe5db30ebee7cb89221d3e
This is not the hash that I am expecting. I have implemented code in PHP and Java which seems to work fine.
PHP Code
<?php
$videoId = "212zpS6bjN77eixPUMUEjR";
$sharedSecret = "abc123";
function generateToken($videoId, $sharedSecret, $lifeTime)
{
$expiryTime = "1458396066";
$data = sprintf("{\"video-id\":\"%s\", \"exp-time\": %s}" , $videoId, "1458396066");
$hash = hash_hmac ( "sha256", $data , hex2bin($sharedSecret) );
$token = sprintf ("%s~%s","1458396066" , $hash);
return $token;
}
$token = generateToken($videoId, $sharedSecret, 5);
echo $token;
?>
JAVA Code
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.math.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
public class VMProToken {
public static void main(String[] args) {
final String videoID = "212zpS6bjN77eixPUMUEjR";
final String sharedSecret = "abc123";
try {
final String token = generateToken(videoID, sharedSecret);
System.out.println(token);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace();
}
}
private static String generateToken(String videoId, String sharedSecret)
throws NoSuchAlgorithmException, InvalidKeyException {
final String HASH_PATTERN = "{\"video-id\":\"%s\", \"exp-time\": %s}";
final String HASH_ALGORITHM = "HmacSHA256";
final String tokenCalcBase = String.format(HASH_PATTERN, videoId, 1458396066);
System.out.println(tokenCalcBase);
final Mac hmac = Mac.getInstance(HASH_ALGORITHM);
final byte[] keyBytes = DatatypeConverter.parseHexBinary(sharedSecret);
final SecretKeySpec secretKey = new SecretKeySpec(keyBytes, HASH_ALGORITHM);
hmac.init(secretKey);
final byte[] hmacBytes = hmac.doFinal(tokenCalcBase.getBytes());
System.out.println(String.format("%064x", new BigInteger(1, hmacBytes)));
final String hash = String.format("%064x", new BigInteger(1, hmacBytes));
return 1458396066 + "~" + hash;
}
}
The above two codes result in the correct answer which is
1458396066~62dcbe0e20827245454280c51129a9f30d1122eaeafc5ce88f0fec527631f1b5
Can somebody please let me know what I'm doing wrong here?
The key is processed as a hexadecimal encoded string in the PHP and Java code, but not in the NodeJS code. To do the same in the NodeJS code, replace 'abc123' with Buffer.from('abc123', 'hex') in the createHmac call.
I want to encrypt and decrypt "ABCD1234" in AES but the output is not the same.
What I have to do to get the same results?
encrypt in crypto-js
function encrypt(string) {
const key = CryptoJS.enc.Utf8.parse("g#eNR#2H'9n/ZF8s");
const encrypted = CryptoJS.AES.encrypt(string, key, {
iv: CryptoJS.enc.Utf8.parse("Fa6Fy$F8.qRvHKU+"),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.NoPadding
});
return encrypted.toString()
}
document.getElementById("encrypted").innerHTML = encrypt("ABCD1234");
then the output is sRdk5O4U+WA=
when i try to decrypt in java
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
class Main {
public static void main(String[] args) {
String data = "sRdk5O4U+WA=";
String key = "g#eNR#2H'9n/ZF8s";
String iv = "Fa6Fy$F8.qRvHKU+";
try
{
byte[] encrypted1 = Base64.getDecoder().decode(data);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes("UTF-8"));
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
byte[] original = cipher.doFinal(encrypted1);
String decrypt = new String(original).trim();
System.out.println("decrypt "+decrypt);
} catch (Exception e)
{
System.out.println("e "+e.toString());
}
}
}
throws an exception javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes
java code: https://repl.it/repls/NoxiousWavyMp3
javascript code: https://codepen.io/anon/pen/ydEpZB