Generating the Signature
Generating the RSA Key Pair
Merchant must securely generate 2048 bit RSA Public Private Key pair on their servers inside a crypto vault. Merchant must share the Public Key (KeyFormat-PEM) with Juspay during onboarding
Private key must be securely kept in a crypto vault on the merchant servers. Private key should never flow to the client.
Note: To simplify integration on sandbox, we have already shared a set of auto-generated keys. Please make sure a new set of keys is generated for production prior to go-live
The below command would generate a private key file private-key.pem
$ openssl genrsa -out private-key.pem 2048
The below command would generate a public key file public-key.pem for the private key file generated via above command
$ openssl rsa -in private-key.pem -pubout -out public-key.pem
Signing the Payload
Algorithm | RSA-SHA256 |
Format | HEX; base 64 encoded |
JSON payload needs to be signed after converting it to String using the Private key stored on the merchant server. The signature shall be in Base 64 encoded format.
Note: Private key must be securely kept in a crypto vault on the merchant servers. Private key should never flow to the client.
package com.newjuspay;
import sun.security.util.DerInputStream;
import sun.security.util.DerValue;
import javax.xml.bind.DatatypeConverter;
import java.io.*;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.net.URLDecoder;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
/**
* Created by azhar on 11/12/19.
*/
public class JuspayUtil {
//The method that signs the data using the private key that is stored in keyFile path
public static String sign(String data, String keyContentOrFile,Boolean urlEncodeSignature) throws Exception {
Signature rsa = Signature.getInstance("SHA256withRSA");
PrivateKey privateKey = null;
data = URLDecoder.decode(data);
if (keyContentOrFile.startsWith("--")) {
privateKey = readPrivateKey(keyContentOrFile);
} else {
privateKey = readPrivateKeyFromFile(keyContentOrFile);
}
rsa.initSign(privateKey);
rsa.update(data.getBytes());
byte[] sign = rsa.sign();
if (urlEncodeSignature) {
return URLEncoder.encode(DatatypeConverter.printBase64Binary(sign), "UTF-8");
} else {
return DatatypeConverter.printBase64Binary(sign);
}
}
//Method to retrieve the Private Key from a file
public static PrivateKey readPrivateKeyFromFile(String filename) throws Exception {
File keyFile = new File(filename);
BufferedReader reader = new BufferedReader(new FileReader(keyFile));
String line;
StringBuffer fileContent = new StringBuffer();
Boolean isPkcs1Content = false;
while ((line = reader.readLine()) != null) {
if (!line.startsWith("--")) {
fileContent.append(line).append("\n");
} else if (!isPkcs1Content && line.startsWith("-----BEGIN RSA PRIVATE KEY-----")){
isPkcs1Content = true;
}
}
byte[] keyBytes = DatatypeConverter.parseBase64Binary(fileContent.toString());
return generatePrivateKey(keyBytes, isPkcs1Content);
}
//Method to retrieve the Private Key from a variable
public static PrivateKey readPrivateKey(String content) throws Exception {
Boolean isPkcs1Content = false;
if (content.startsWith("-----BEGIN RSA PRIVATE KEY-----")){
isPkcs1Content = true;
}
content = content.replaceAll("-----BEGIN RSA PRIVATE KEY-----","");
content = content.replaceAll("-----BEGIN PRIVATE KEY-----","");
content = content.replaceAll("-----END RSA PRIVATE KEY-----","");
content = content.replaceAll("-----END PRIVATE KEY-----","");
byte[] keyBytes = DatatypeConverter.parseBase64Binary(content.toString());
return generatePrivateKey(keyBytes, isPkcs1Content);
}
private static PrivateKey generatePrivateKey(byte[] keyBytes, Boolean isPkcs1Content) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
PrivateKey privateKey = null;
if (isPkcs1Content) {
DerInputStream derReader = new DerInputStream(keyBytes);
DerValue[] seq = derReader.getSequence(0);
// skip version seq[0];
BigInteger modulus = seq[1].getBigInteger();
BigInteger publicExp = seq[2].getBigInteger();
BigInteger privateExp = seq[3].getBigInteger();
BigInteger prime1 = seq[4].getBigInteger();
BigInteger prime2 = seq[5].getBigInteger();
BigInteger exp1 = seq[6].getBigInteger();
BigInteger exp2 = seq[7].getBigInteger();
BigInteger crtCoef = seq[8].getBigInteger();
RSAPrivateCrtKeySpec keySpec =
new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, prime1, prime2, exp1, exp2, crtCoef);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
privateKey = keyFactory.generatePrivate(keySpec);
} else {
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
privateKey = kf.generatePrivate(spec);
}
return privateKey;
}
public static void main(String[] args) throws Exception{
String data = "{\"merchant_id\":\"mid\",%20\"customer_id\":%20\"8600143648\",\"mobile_number\":%20\"8600143648\",\"email_address\":%20\"[email protected]\",\"time_stamp\":\"1576227696\"}";
String fileContent = "-----BEGIN RSA PRIVATE KEY-----\r\n" +
"MIIEpAIBAAKCAQEAzEHPYKNYRcZoWtgTvYPEmTeiSlMxJwNPf8bRJ5U6cXiup2j/\r\n" +
"a/l1oAiuVNQiDRzvSflS80FoRjQjcGRyqTYcYYYUTYOTh6j1WY1SQuPqrsvGNR3L\r\n" +
"NG0owiXTheDd4S6NlK9zBXI1Gtf6Q/mGowfOje/W1i6AwDl1stPmplA7YleiHOUU\r\n" +
"X5XW3jKFChnLFcmD1hpvNrDbCYhy0xVHv2Pc0GhmFlavCTc2yPHYWwTyFYzY7PsX\r\n" +
"UVn3gSuL6w+aW7h7/1OkSYQzr6siLSqfGUfDIXqF2Oke4+PdhmbyqJ6vqNhH7C3e\r\n" +
"PuK8jGyaOsRWDgg1Sxc7fLFyA3CU7B00KIfcKwIDAQABAoIBAQCP8kkdh8Ar3Dmq\r\n" +
"2+B9jKE+gVCkJKDdJ54dJJY92RMI6M3dOUfYZkOY9sU1DxK4Pw38CfOFbzD3WMMo\r\n" +
"8AFGctXpfL5OKk4MKuxNoiS48zpu2TjkMg0E83Dn8hRxoxl7Gn24rTTYP7ZhJpg+\r\n" +
"01kpB9VvffjflIIz9cqWrnM7/gr7sBZjfNFFXmq2D9mvUrJVeyEWdJc5//2OuJKS\r\n" +
"tlDJmW6vrGM9K28Q8btyJcQOWl2IrIhICGMoSVdIJeRssd9mm1x4dVPaM315tQHe\r\n" +
"/CIFbENWI5jmutGfwb0lmiZJXayxEB5+4CPsYd+vxac+DSsbpZ3KJGWdrm+SFBsK\r\n" +
"WoM1Wq6hAoGBAP7MiOV/7Z9zJVVcwW3/R7BgW6T/fszqKfFdOcOCHq+IMH9gU/K/\r\n" +
"qwf5LTCOfeK4yQLg+uY/27D2IhIbRapp60BJ0PB2AT6lp5WnZHjO0llYknJsGVmW\r\n" +
"axMUi3+h+h1+UXfkb8yosY3PQcIdVp/zlJWT4qRQ5DWCPJLj6ah15V7JAoGBAM04\r\n" +
"SVsD+axQMvTRLs46B0V9JCLlr/fNol1Y8PhRNka3qsmUmE0eYwrAMoBsFJczHbDX\r\n" +
"JuViFTg5LKLemPi7lr3SY8LC2oCF0LZv8klyHxMD9ysw1wp5QKBHW+BxkFQMoqPT\r\n" +
"jxgzr78hh2/KN7qk1yR9RWAemTRjHlzU6/q7ZJlTAoGAf8lMju0N234AJ0ORrvzs\r\n" +
"k0SAMcwBZ/u4dVhv8+F6SkZfLEK/V2tQ93q8czHDaMWL6mmy627zW1jV1Ybf9YuR\r\n" +
"dHiQ1UgZb7Xcym1dMklnrW+CYuhb/lY19SfEnoo5yjjj2uEyQM4J3jknnZbhLu8W\r\n" +
"gb3dWNNI4J0ki/bJ5LbcBiECgYBcZAfaewqvwhd+0qJQ5B0b8sAPGftEBAciIYWz\r\n" +
"NdTKt6ujP0vmBFUwpODXolEO1Ut7rxFq2eKVwl/PH4odCU9PPGX/a/w1OomBaRra\r\n" +
"aA+HXxSrFDzsETTANAAwJtCEln+uY/ObQMHRucWg1ZiLZeUaH2/ZW008IZes0YtA\r\n" +
"AZfD0wKBgQCyVj0GyMwnHdF4p8M9TFYWb6BbRIeE7iUXqPvX6bzksylBPngpBF+J\r\n" +
"uEV0+VQiAjXwwzb8peuCNWZiyfpAn3v15GLPqOPURsL+HvIDwEBYerIh9p3Z2G38\r\n" +
"/6CfbI2iD4bPRx2XdPbxr6UoHNvh6y1hAscZy5oxSCT6WW5jCpW1Bg==\r\n" +
"-----END RSA PRIVATE KEY-----\r\n";
// Sign with content in code
String p1 = sign(data, fileContent, true);
// Sign with content in file on given absolute path
String p2 = sign(data, "D:\\eclipse-workspaces\\general-projects\\Juspay\\lib\\private-key.pem",true);
System.out.println(p1.equals(p2));
System.out.println(p1);
System.out.println(p2);
}
}
#!/bin/python
# pip install pycryptodome
from Crypto.PublicKey import RSA # ignore this
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from Crypto import Random # ignore this
from base64 import b64encode
def sign(message, priv_key):
signer = PKCS1_v1_5.new(priv_key)
digest = SHA256.new()
digest.update(message.encode('utf-8'))
return signer.sign(digest)
def newkeys(keysize): # This is a sample snippet. Always store and fetch the private key from crypto vault
random_generator = Random.new().read
key = RSA.generate(keysize, random_generator)
private, public = key, key.publickey()
return public, private
msg1 = "thisIsThePayloadToBeSigned" # Payload for signature
keysize = 2048
(public_key, private_key) = newkeys(keysize)
# Or use this to read the private key from a pem file
# private_key = RSA.importKey(open('pem file location', "rb").read(), passphrase="yourpasswordhere or None")
signature = sign(msg1, private_key)
encoded_signature = b64encode(signature)
print(public_key.exportKey('PEM'))
print(encoded_signature)
const privateKeyString = "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAA70vuNEyFLz2FzpPYzwN5aX1rYhPBmS89Yt6pu6McT7jlnw\n-----END RSA PRIVATE KEY-----"
// This is a sample snippet. Always store and fetch the private key from crypto vault
privateKey = new NodeRSA(privateKeyString);
function createSignature (payload, privateKey) {
const requiredFields = ["order_id", "merchant_id", "amount", "timestamp", "customer_id"];
var objKeys = Object.keys(payload);
if (requiredFields.every(key => objKeys.includes(key))){
signaturePayload = JSON.stringify(payload);
signature = privateKey.sign(signaturePayload, "base64", "utf8");
signaturePayload = "b64_" + Buffer.from(signaturePayload,'utf8').toString('base64');
return {signature, signaturePayload}
}
throw Error ("Not a valid JSON payload");
}
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import javax.xml.bind.DatatypeConverter;
import java.io.FileReader;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.*;
public class SignatureUtil {
public static void main(String ...args) {
String data = "{\"merchant_id\":\"mid\",%20\"customer_id\":%20\"8600143648\",\"mobile_number\":%20\"8600143648\",\"email_address\":%20\"[email protected]\",\"timestamp\":\"1576227696\"}";
String filePath = "/<absolute-path-to-folder-containing-pem-file>/private-key.pem";
String signature = sign(data, filePath, true);
System.out.println(signature);
}
public static String sign(String payload, String filePath, boolean urlEncodeSignature) {
try {
PrivateKey privateKey = readPrivateKeyFromFile(filePath);
Signature privateSignature = Signature.getInstance("SHA256withRSA");
privateSignature.initSign(privateKey);
privateSignature.update(payload.getBytes(StandardCharsets.UTF_8));
byte[] signature = privateSignature.sign();
if (urlEncodeSignature) {
return URLEncoder.encode(DatatypeConverter.printBase64Binary(signature), "UTF-8");
} else {
return DatatypeConverter.printBase64Binary(signature);
}
} catch (IOException | NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
e.printStackTrace();
}
return "sign-failed";
}
private static PrivateKey readPrivateKeyFromFile(String filePath) throws IOException {
Security.addProvider(new BouncyCastleProvider());
PEMParser pemParser = new PEMParser(new FileReader(filePath));
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
PEMKeyPair pemKeyPair = (PEMKeyPair) pemParser.readObject();
KeyPair keyPair = converter.getKeyPair(pemKeyPair);
return keyPair.getPrivate();
}
}
<?php
function generateSign($payload) {
$privateKey = "<private-key>";
$signature = "";
openssl_sign($payload , $signature , $privateKey , "sha256WithRSAEncryption");
return base64_encode($signature);
}
?>
Sample code snippet for reference:
<?php
function generateSign($payload) {
$privateKey = "MIIEpQIBAAKCAQEA2R+5+b1HZSNNMotnr7Z/5By4NSTZW4dDMGDy2huOSLn1EF6v\n75sssdY5kRkFoihLIeNQA+yzsi0kzpz3rCCCmo1DJwgoqA48JVQwwBjZ9SHeC0nE\n66VODmMJJGNWe1quHWQb3otIzS+U+rtd1Alzo9up8u8e+FrecyjO6fBMZfd32iO7\nqPtExtA1XDtKMqoRbHMiAz940xA5+BLmJC+gp1IYsVce2KA5BW1laPxbku42aQR7\neZipSa3BYRY8m964Aj6vLj4kTeTbrc4OH7yatRdWbVbrwVWpg936g8Q3Qf3jQY+H\nMu76l1WeXK4GkPkA+oJXY6ag1XhhqtOrLw3rJwIDAQABAoIBAQCjy2thG4lgouD5\n4HC3/dU9IO1WKhZPFht5w6lxIJiWBLL7RnMzLrzo69NBwr6dNgh36CPU0hw9rhC2\nTXQKRfxA25BtQZpqLVLyVjDwuc6zPnljyqLjojDgaZXb/ZSgOihfw8XCfRDOubaJ\n8A84hmjWlEABJKMYeHSYK5Dsqnr37/Oj4OT2NWLaRx8Kk0HPuv/bxx3MHurIHRtG\n514UJZcOfN8Ti69/DoYbtb/Mpg/djXr5s47TafxPa8jyT2E8nWboPvYPDQcdu2CI\nE0mbrj2C0Ak7g20ZYzm9HCbJHVG+2rPSjVgXKW2ZgXoZflte+G5vRfDrZH+TZuo+\nja0pVlIBAoGBAP4ZEtdpRJp5kvj7bUIkXd5E6lrxd2M0VprfMhHk0i+Z1p1nF8St\nF6p9uIGuRIEthvdRZVy56fWCHXOfmquRJDHazQZVnnXcRaqhuMYdZoJ3i1KkmSL4\nX/SdBsB4rY4FQZWNtwKoSeDugQeJzf4bhyn0iZGbWPq+XO70MxXya8CfAoGBANq/\nzL6ul+G6i/nXrfzwUr83EtXh6Zoj51YBK4g3ZIIuWkWvbo9NguV3p9KmeRKMWwYO\nRHC7aVwpHdjzOyzmSFdmC+5dqVe6rkdl9AzxpKt0p0rOznmZUhDcdElCk0p6pC5R\nQDAt2PA4aR3kT+9z2dPV0IHsUGiouF/LtmTmdCB5AoGAIShUdRefhCjpLORiVYc5\nWI/VpRhtY9yokH0fo4Ygh2Wjw9Z4G4oa1HyjXwjGl7TBL/THLVp1VTwta7EgFdNS\nzc6ngnQZwXeE/8cqvW+IuO2wmJAyC4Ytv1XeU69rtmSpMkLT5tzfByMYY0twPgCJ\nmsf2S7Hh4paEugnTwMFpnjECgYEAoTqc/i5RY97LLOr7ImM/mhBNobdRJnswFwPl\nwhCR1CG2B4a2Rokq4VbAK1LoCfPJYz1A1JZNoc/sX+tmwkE5MLHWOWpvVmoR6i4L\nIz83z+e7JjgnlxialDLowtZ/GXYrbLgWR2yDaQsq7w1InYUWGDyP4jL7USiKPJE5\nbkUtcoECgYEAwhbb1NxzteIr8zlytMj52sgeiJPQRjbU5CoMAJuiHvYHT8jQwso7\nlfbz+fXdQamU29v1Hdhc2JR1xWxrTz4bAt1l9lWK8zQBTK3SOlhyvrvNkKtTwjan\nsR6+uwB9KY5mrF++pRA8IL2f0yhx2uqwDkX/Og6ZnFHJn3BvQM/DWPg=";
$privateKey = openssl_get_privatekey("-----BEGIN RSA PRIVATE KEY-----\n".$privateKey."\n-----END RSA PRIVATE KEY-----"); //private key must be in this format
$signature = "";
openssl_sign( $payload , $signature , $privateKey , "sha256WithRSAEncryption" );
return base64_encode($signature);
}
$data ="{\"merchant_id\":\"abc\",\"customer_id\":\"12345\",\"mobile_number\":\"9876543210\",\"email_address\":\"[email protected]\",\"first_name\":\"abc\",\"last_name\":\"def\",\"timestamp\":\"1616068636\"}";
echo generateSign($data);
?>
require 'openssl'
require 'base64'
private_key_pem = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA2R+5+b1HZSNNMotnr7Z/5By4NSTZW4dDMGDy2huOSLn1EF6v\n75sssdY5kRkFoihLIeNQA+yzsi0kzpz3rCCCmo1DJwgoqA48JVQwwBjZ9SHeC0nE\n66VODmMJJGNWe1quHWQb3otIzS+U+rtd1Alzo9up8u8e+FrecyjO6fBMZfd32iO7\nqPtExtA1XDtKMqoRbHMiAz940xA5+BLmJC+gp1IYsVce2KA5BW1laPxbku42aQR7\neZipSa3BYRY8m964Aj6vLj4kTeTbrc4OH7yatRdWbVbrwVWpg936g8Q3Qf3jQY+H\nMu76l1WeXK4GkPkA+oJXY6ag1XhhqtOrLw3rJwIDAQABAoIBAQCjy2thG4lgouD5\n4HC3/dU9IO1WKhZPFht5w6lxIJiWBLL7RnMzLrzo69NBwr6dNgh36CPU0hw9rhC2\nTXQKRfxA25BtQZpqLVLyVjDwuc6zPnljyqLjojDgaZXb/ZSgOihfw8XCfRDOubaJ\n8A84hmjWlEABJKMYeHSYK5Dsqnr37/Oj4OT2NWLaRx8Kk0HPuv/bxx3MHurIHRtG\n514UJZcOfN8Ti69/DoYbtb/Mpg/djXr5s47TafxPa8jyT2E8nWboPvYPDQcdu2CI\nE0mbrj2C0Ak7g20ZYzm9HCbJHVG+2rPSjVgXKW2ZgXoZflte+G5vRfDrZH+TZuo+\nja0pVlIBAoGBAP4ZEtdpRJp5kvj7bUIkXd5E6lrxd2M0VprfMhHk0i+Z1p1nF8St\nF6p9uIGuRIEthvdRZVy56fWCHXOfmquRJDHazQZVnnXcRaqhuMYdZoJ3i1KkmSL4\nX/SdBsB4rY4FQZWNtwKoSeDugQeJzf4bhyn0iZGbWPq+XO70MxXya8CfAoGBANq/\nzL6ul+G6i/nXrfzwUr83EtXh6Zoj51YBK4g3ZIIuWkWvbo9NguV3p9KmeRKMWwYO\nRHC7aVwpHdjzOyzmSFdmC+5dqVe6rkdl9AzxpKt0p0rOznmZUhDcdElCk0p6pC5R\nQDAt2PA4aR3kT+9z2dPV0IHsUGiouF/LtmTmdCB5AoGAIShUdRefhCjpLORiVYc5\nWI/VpRhtY9yokH0fo4Ygh2Wjw9Z4G4oa1HyjXwjGl7TBL/THLVp1VTwta7EgFdNS\nzc6ngnQZwXeE/8cqvW+IuO2wmJAyC4Ytv1XeU69rtmSpMkLT5tzfByMYY0twPgCJ\nmsf2S7Hh4paEugnTwMFpnjECgYEAoTqc/i5RY97LLOr7ImM/mhBNobdRJnswFwPl\nwhCR1CG2B4a2Rokq4VbAK1LoCfPJYz1A1JZNoc/sX+tmwkE5MLHWOWpvVmoR6i4L\nIz83z+e7JjgnlxialDLowtZ/GXYrbLgWR2yDaQsq7w1InYUWGDyP4jL7USiKPJE5\nbkUtcoECgYEAwhbb1NxzteIr8zlytMj52sgeiJPQRjbU5CoMAJuiHvYHT8jQwso7\nlfbz+fXdQamU29v1Hdhc2JR1xWxrTz4bAt1l9lWK8zQBTK3SOlhyvrvNkKtTwjan\nsR6+uwB9KY5mrF++pRA8IL2f0yhx2uqwDkX/Og6ZnFHJn3BvQM/DWPg=\n-----END RSA PRIVATE KEY-----"
private_key = OpenSSL::PKey::RSA.new(private_key_pem)
payload = '{"timestamp":"1631085704","merchant_id":"random","mobile_number":"9873333333","customer_id":"1324029","email_address":"[email protected]"}'
signature = private_key.sign(OpenSSL::Digest::SHA256.new, payload)
print Base64.encode64(signature).delete!("\n")
print "\n"
Updated over 1 year ago