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 which need to be configured. 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.

Sample Code Snippet
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.json.JSONObject;

import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.HashMap;

import javax.xml.bind.DatatypeConverter;

public class SignatureUtil {
    public static void main(String ...args) {
        JSONObject data = new JSONObject("{'order_id': 'venkatesh12','first_name': 'Test','last_name': 'Customer','customer_phone': '9876543210','customer_email': '[email protected]','merchant_id': 'udit_juspay','amount': '1.00','customer_id': '9876543210','return_url': 'https://sandbox.juspay.in/end','currency': 'INR','mandate.start_date': '1638535683287','mandate.end_date': '2134731745451','timestamp': '1576227696'}");
        String filePath = "/<absolute-path-to-folder-containing-pem-file>/private-key.pem";
        HashMap<String,String> response = createSignature(data, filePath);
    }

    public static HashMap<String,String> createSignature(JSONObject payload, String filePath) {
        try {
            PrivateKey privateKey = readPrivateKeyFromFile(filePath);
            Signature privateSignature = Signature.getInstance("SHA256withRSA");
            String[] requiredFields = {"order_id", "merchant_id", "amount", "timestamp", "customer_id"};
            for (String key : requiredFields)
                if(!payload.has(key))
                    throw new Exception(key + " not found in payload");
            String signaturePayload = payload.toString();
            privateSignature.initSign(privateKey);
            privateSignature.update(signaturePayload.getBytes(StandardCharsets.UTF_8));
            byte[] signature = privateSignature.sign();
            String encodedSignature = DatatypeConverter.printBase64Binary(signature);
            HashMap<String,String> response = new HashMap<String,String>();
            response.put("signature",encodedSignature);
            response.put("signaturePayload", signaturePayload);
            return response;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new HashMap<String, String>();
    }

    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();
    }
}
import sun.security.util.DerInputStream;
import sun.security.util.DerValue;
import javax.xml.bind.DatatypeConverter;
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.HashMap;

import org.json.JSONObject;

public class SignatureUtil {

    //The method that signs the data using the private key that is stored in keyFile path
    public static HashMap<String, String> createSignature (JSONObject payload, String keyContentOrFile) throws Exception {
        Signature rsa = Signature.getInstance("SHA256withRSA");
        PrivateKey privateKey = null;

        if (keyContentOrFile.startsWith("--")) {
            privateKey = readPrivateKey(keyContentOrFile);
        } else {
            privateKey = readPrivateKeyFromFile(keyContentOrFile);
        }
        String[] requiredFields = {"order_id", "merchant_id", "amount", "timestamp", "customer_id"};
        for (String key : requiredFields)
            if(!payload.has(key))
                throw new Exception(key + " not found in payload");
        String signaturePayload = payload.toString();
        rsa.initSign(privateKey);
        rsa.update(signaturePayload.getBytes());
        byte[] sign =  rsa.sign();
        String encodedSignature = DatatypeConverter.printBase64Binary(sign);
        HashMap<String,String> response = new HashMap<String,String>();
        response.put("signature",encodedSignature);
        response.put("signaturePayload", signaturePayload);
        return response;
    }

    //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);
        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{
        JSONObject data = new JSONObject("{'order_id': 'venkatesh12','first_name': 'Test','last_name': 'Customer','customer_phone': '9876543210','customer_email': '[email protected]','merchant_id': 'udit_juspay','amount': '1.00','customer_id': '9876543210','return_url': 'https://sandbox.juspay.in/end','currency': 'INR','mandate.start_date': '1638535683287','mandate.end_date': '2134731745451','timestamp': '312342'}");
        
        //This is a sample snippet. Always store and fetch the private key from crypto vault
        String fileContent = "-----BEGIN RSA PRIVATE KEY-----\r\n" +
                "MIIEpAIBAAKCAQEAzEHPYKNYRcZoWtgTvYPEmTeiSlMxJwNPf8bRJ5U6cXiup2j/\r\n" +
                "a/l1oAiuVNQiDRzvSflS80FoRjQjcGRyqTYcYYYUTYOTh6j1WY1SQuPqrsvGNR3L\r\n" +
                "/6CfbI2iD4bPRx2XdPbxr6UoHNvh6y1hAscZy5oxSCT6WW5jCpW1Bg==\r\n" +
                "-----END RSA PRIVATE KEY-----\r\n";

        // Sign with content in code
        HashMap<String,String> p1 = createSignature(data, fileContent);

        // Sign with content in file on given absolute path
        HashMap<String,String> p2 = createSignature(data, "/<absolute-path-to-folder-containing-pem-file>/private-key.pem");
    
    }
}
#!/bin/python
# pip install pycryptodome 

import json

from Crypto.PublicKey import RSA # ignore this
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from base64 import b64encode

privateKeyString = "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAA70vuNEyFLz2FzpPYzwN5...aX1rYhPBmS89Yt6pu6McT7jlnw\n-----END RSA PRIVATE KEY-----"
# This is a sample snippet. Always store and fetch the private key from crypto vault

privateKey = RSA.import_key(privateKeyString)

def createSignature (payload, privateKey):
    requiredFields = ["order_id", "merchant_id", "amount", "timestamp", "customer_id"]
    signaturePayload = json.dumps(payload)
    for key in requiredFields:
        if key not in payload:
            raise ValueError (key , " not found in payload")
    signer = PKCS1_v1_5.new(privateKey)
    digest = SHA256.new()
    digest.update(signaturePayload.encode('utf-8'))
    signature = signer.sign(digest)
    encodedSignature = b64encode(signature)
    return encodedSignature, signaturePayload
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");
        return {signature, signaturePayload}
    }
    throw Error ("Not a valid JSON payload");
}
<?php
$privateKey = "-----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

function createSignature($payload, $privateKey) {
    $response = array();
    $requiredFields = ["order_id", "merchant_id", "amount", "timestamp", "customer_id"];
    foreach ($requiredFields as $key){
        if(!array_key_exists($key, $payload)){
            echo $key . " is not present ";
            return;
        }
    }
    $signaturePayload = json_encode($payload);
    $signature = "";
    openssl_sign($signaturePayload, $signature, $privateKey, "sha256WithRSAEncryption");
    array_push($response, base64_encode($signature));
    array_push($response,$signaturePayload);
    return $response;
}
?>
require 'openssl'
require 'base64'
require 'json'

privateKeyString = "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAA70vuN....EyFLz2FzpPYzwN5aX1rYhPBmS89Yt6pu6McT7jlnw\n-----END RSA PRIVATE KEY-----"
privateKey = OpenSSL::PKey::RSA.new(privateKeyString)
# This is a sample snippet. Always store and fetch the private key from crypto vault

def createSignature (payload, privateKey)
    requiredFields = ["order_id", "merchant_id", "amount", "timestamp", "customer_id"]
    signaturePayload = JSON.generate(payload)
    jsonPayload = JSON.parse(signaturePayload)
    for key in requiredFields do
        if !jsonPayload.has_key?(key)
            raise (key + " not found in payload \n")
        end
    end
    signature = privateKey.sign(OpenSSL::Digest::SHA256.new, signaturePayload)
    encodedSignature = Base64.encode64(signature).delete!("\n")
    return encodedSignature, signaturePayload
end
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.OpenSsl;
using Newtonsoft.Json.Linq;

class SignatureUtil {
    // string filePath = "/<absolute-path-to-folder-containing-pem-file>/private-key.pem";
    public static Dictionary<string,string> createSignature (JObject payload, string filePath){
        string[] requiredFields = {"order_id", "merchant_id", "amount", "timestamp", "customer_id"};
        foreach (string key in requiredFields)
            if(!payload.ContainsKey(key))
                throw new Exception (key + " not found in payload");
        string signaturePayload = payload.ToString();
        byte[] byteArrayPayload = ASCIIEncoding.ASCII.GetBytes(signaturePayload);
        StreamReader sr = new StreamReader(filePath);
        PemReader pr = new PemReader(sr);
        AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pr.ReadObject();
        RsaKeyParameters privateKey = (RsaKeyParameters) keyPair.Private;
        ISigner sign = SignerUtilities.GetSigner(PkcsObjectIdentifiers.Sha256WithRsaEncryption.Id);
        sign.Init(true, privateKey);
        sign.BlockUpdate(byteArrayPayload, 0, byteArrayPayload.Length);
        byte[] signature = sign.GenerateSignature();
        string encodeSignature = Convert.ToBase64String(signature);
        Dictionary<string, string> response = new Dictionary<string, string>();
        response.Add("signature", encodeSignature);
        response.Add("signaturePayload", signaturePayload);
        Console.Write(encodeSignature);
        return response;
    }
}