Generate Signature - V2.0

Authenticate your request

Overview

A signature is an encrypted string of data obtained by processing the request's body data through the combined SHA-256 & RSA Algorithm (Crypto: JCE Sign). The signature at the client's end is encrypted using a unique private key of a CA-signed certificate. To pass a request, the user should process the message body, encrypt it to create a signature, and enclose it in the request.

Every request received by SingleView API is authenticated by decrypting the signature using a Public key/certificate.

Representation of flow to generate Signature

Representation of process flow to generate an encrypted Signature

Generating and Including a Signature in a Request

To ensure data integrity and authentication, every request payload must be digitally signed using the merchant’s private key. The signature is then verified by the receiving system using the corresponding public key from the certificate.

📘

Important Information

  • Algorithm: SHA256withRSA
  • Encoding: Always use UTF-8 for string-to-byte conversion
  • Signature format: Base64 string
  • Signed portion: Only the Message object, not the full JSON
  • Keystore: Must be in .p12 or .pfx format containing a valid RSA private key and certificate
  • Verification: Use the public key from the corresponding certificate

Sample Signature algorithm

Consider a signature has to be generated for the following request:

curl --location 'https://sandboxapi.onesingleview.com/api/{version}/getbeneficary/bankdetails' \
--header 'CompanyId: MYCOMPANY'
--header 'SVReferenceID: SV150619940615'
--header 'DateTimeStamp	:2025-01-02T10:20:39'
--header 'Device: Web'
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiTDNyMmpxV29JS2I4MTNodDBNMlZ4WGpFZmR6WFNWaTFad3B5QnlkOVpHelZXR2ZwUmFQNUV1TXl6S1Aybi94Y0lwR2V5STNNMkdPL1pqakd4ZG0yclJKUkdhRXlaWGNpWnZnOHArMWIyOGtOQTVkZ0VKajVVSEdiTmtNPSIsImlhdCI6MTczNTgwMTQ3NywiZXhwIjoxNzM1ODA1MDc3fQ.Bh7LFkvxNFrLe9dmH5rURWjGpES-u4z2EGeyrNM7z6E'
--header 'Content-Type: application/json'
--data '{
"Message": {
  "OSVBeneficiaryBankDetailsRequest": {
    "OSVBeneficiaryBankDetails": [
      {
        "NationalId": "1231512323",
        "IBANAccount": "SA2012345678972123456789"
      }
    ]
  }
},
"Signature": ""
}'

Only the Message object (not the entire payload) is to be signed.

Below are the sample algorithms with the required portion from the above payload to generate a signature:

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Enumeration;
import java.util.Locale;
import java.util.logging.Logger;


// INPUTS
String jsonPayload = """
    {
            "OSVBeneficiaryBankDetailsRequest": {
                "OSVBeneficiaryBankDetails": [
                    {
                        "NationalId": "1231512323",
                        "IBANAccount": "SA2012345678972123456789"
                    }
                ]
            }
        }
  """; // Pass payload as a string

String keyPath = "path of file"; // .p12 | .pfx | .key | .pem
String password = "password"; // only for p12/pfx

// VALIDATION
if (jsonPayload == null || jsonPayload.isBlank()) {
  throw new IllegalArgumentException("jsonPayload must not be empty");
}
if (keyPath == null || keyPath.isBlank()) {
  throw new IllegalArgumentException("keyPath must not be empty");
}

// JSON PARSE + CANONICALIZE
ObjectMapper mapper = new ObjectMapper();
Object payloadObject = mapper.readValue(jsonPayload, Object.class);
ObjectMapper canonicalMapper = new ObjectMapper();
canonicalMapper.configure(
  SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS,
  true
);
String dataToSign = canonicalMapper.writeValueAsString(payloadObject);

// KEY LOADER (AUTO DETECT)
PrivateKey privateKey;
String lowerPath = keyPath.toLowerCase(Locale.ROOT);
if (lowerPath.endsWith(".p12") || lowerPath.endsWith(".pfx")) {
  if (password == null || password.isBlank()) {
    throw new IllegalArgumentException("password is required for p12/pfx");
  }

  KeyStore keyStore = KeyStore.getInstance("PKCS12");
  char[] passwordChars = password.toCharArray();
  try (FileInputStream fis = new FileInputStream(keyPath)) {
    keyStore.load(fis, passwordChars);
  }

  privateKey = null;
  Enumeration<String> aliases = keyStore.aliases();
  while (aliases.hasMoreElements()) {
    String alias = aliases.nextElement();
    if (keyStore.isKeyEntry(alias)) {
      privateKey = (PrivateKey) keyStore.getKey(alias, passwordChars);
      break;
    }
  }

  if (privateKey == null) {
    throw new IllegalArgumentException("No private key found in keystore");
  }
} else if (lowerPath.endsWith(".key") || lowerPath.endsWith(".pem")) {
  String pem = Files.readString(Path.of(keyPath), StandardCharsets.UTF_8);

  // Optional: PEM may include CERTIFICATE block(s)
  boolean hasCert = pem.contains("-----BEGIN CERTIFICATE-----");
  boolean hasPkcs8Key = pem.contains("-----BEGIN PRIVATE KEY-----");
  boolean hasPkcs1Key = pem.contains("-----BEGIN RSA PRIVATE KEY-----");

  if (hasCert && !hasPkcs8Key && !hasPkcs1Key) {
    throw new IllegalArgumentException(
      "PEM contains CERTIFICATE only. Signing requires a PRIVATE KEY."
    );
  }

  if (hasPkcs1Key) {
    throw new IllegalArgumentException(
      "BEGIN RSA PRIVATE KEY (PKCS#1) is not supported by this code. Convert to PKCS#8: BEGIN PRIVATE KEY."
    );
  }

  if (!hasPkcs8Key) {
    throw new IllegalArgumentException("No supported private key block found in PEM");
  }

  String key = pem
    .replace("-----BEGIN PRIVATE KEY-----", "")
    .replace("-----END PRIVATE KEY-----", "")
    // remove certificate blocks if present
    .replaceAll("-----BEGIN CERTIFICATE-----[\\s\\S]*?-----END CERTIFICATE-----", "")
    .replaceAll("\\s", "");

  byte[] decoded = Base64.getDecoder().decode(key);
  PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);
  privateKey = KeyFactory.getInstance("RSA").generatePrivate(spec);
} else {
  throw new IllegalArgumentException("Unsupported key format");
}

// SIGN
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(privateKey);
signer.update(dataToSign.getBytes(StandardCharsets.UTF_8));
String signature = Base64.getEncoder().encodeToString(signer.sign());
import base64
import json
from pathlib import Path
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import pkcs12


# INPUTS
json_payload = """
    {
            "OSVBeneficiaryBankDetailsRequest": {
                "OSVBeneficiaryBankDetails": [
                    {
                        "NationalId": "1231512323",
                        "IBANAccount": "SA2012345678972123456789"
                    }
                ]
            }
        }
    """  # Pass payload as a string

key_path = Path(__file__).resolve().parent / "path of file"  # .p12 | .pfx | .key | .pem
password = "password"  # only for p12/pfx

# VALIDATION
if not json_payload or not json_payload.strip():
  raise ValueError("jsonPayload must not be empty")
  if not str(key_path).strip():
    raise ValueError("keyPath must not be empty")

    # JSON PARSE + CANONICALIZE
    payload_node = json.loads(json_payload)

    def canonicalize(value):
      if isinstance(value, list):
        return [canonicalize(item) for item in value]
      if isinstance(value, dict):
        return {k: canonicalize(value[k]) for k in sorted(value.keys())}
      return value

    canonical_node = canonicalize(payload_node)
    data_to_sign = json.dumps(canonical_node, separators=(",", ":"), ensure_ascii=False)

    # KEY LOADER (AUTO DETECT)
    lower_path = str(key_path).lower()

    if lower_path.endswith(".p12") or lower_path.endswith(".pfx"):
      if not password or not password.strip():
        raise ValueError("password is required for p12/pfx")

          p12_bytes = key_path.read_bytes()
            private_key, _certificate, _additional_certs = pkcs12.load_key_and_certificates(
              p12_bytes,
              password.encode("utf-8"),
            )

            if private_key is None:
              raise ValueError("No private key found in keystore")

            elif lower_path.endswith(".key") or lower_path.endswith(".pem"):
              pem_bytes = key_path.read_bytes()
                pem_text = pem_bytes.decode("utf-8", errors="ignore")

            # Optional: PEM may include CERTIFICATE block(s)
              has_cert = "-----BEGIN CERTIFICATE-----" in pem_text
          has_pkcs8_key = "-----BEGIN PRIVATE KEY-----" in pem_text
          has_pkcs1_key = "-----BEGIN RSA PRIVATE KEY-----" in pem_text

          if has_cert and not has_pkcs8_key and not has_pkcs1_key:
              raise ValueError("PEM contains CERTIFICATE only. Signing requires a PRIVATE KEY.")

                if has_pkcs1_key:
                raise ValueError(
                    "BEGIN RSA PRIVATE KEY (PKCS#1) is not supported by this code. Convert to PKCS#8: BEGIN PRIVATE KEY."
                  )

                  if not has_pkcs8_key:
                  raise ValueError("No supported private key block found in PEM")

                private_key = serialization.load_pem_private_key(pem_bytes, password=None)

              else:
                raise ValueError("Unsupported key format")

                # SIGN
                signature_bytes = private_key.sign(
                    data_to_sign.encode("utf-8"),
                    padding.PKCS1v15(),
                    hashes.SHA256(),
                  )
                  signature = base64.b64encode(signature_bytes).decode("utf-8")
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;

// INPUTS
string jsonPayload = """
{
  "OSVBeneficiaryBankDetailsRequest": {
    "OSVBeneficiaryBankDetails": [
      {
        "NationalId": "1231512323",
        "IBANAccount": "SA2012345678972123456789"
        }
    ]
  }
}
"""; // Pass payload as a string

  string keyPath = "path of file"; // .p12 | .pfx | .key | .pem
string password = "password"; // only for p12/pfx

// VALIDATION
if (string.IsNullOrWhiteSpace(jsonPayload))
  throw new ArgumentException("jsonPayload must not be empty");

if (string.IsNullOrWhiteSpace(keyPath))
  throw new ArgumentException("keyPath must not be empty");

// JSON PARSE + CANONICALIZE
JsonNode payloadNode = JsonNode.Parse(jsonPayload)
  ?? throw new InvalidOperationException("Invalid JSON payload");

JsonNode Canonicalize(JsonNode node)
{
  if (node is JsonObject obj)
  {
    var sorted = new JsonObject();
    foreach (var kv in obj.OrderBy(k => k.Key, StringComparer.Ordinal))
    {
      sorted[kv.Key] = kv.Value is null ? null : Canonicalize(kv.Value);
    }
    return sorted;
  }

  if (node is JsonArray arr)
  {
    var canonicalArray = new JsonArray();
    foreach (var item in arr)
    {
      canonicalArray.Add(item is null ? null : Canonicalize(item));
    }
    return canonicalArray;
  }

  return node.DeepClone();
}

JsonNode canonicalNode = Canonicalize(payloadNode);
string dataToSign = canonicalNode.ToJsonString(new JsonSerializerOptions { WriteIndented = false });

// KEY LOADER (AUTO DETECT)
RSA privateKey;
string lowerPath = keyPath.ToLowerInvariant();

if (lowerPath.EndsWith(".p12") || lowerPath.EndsWith(".pfx"))
{
  if (string.IsNullOrWhiteSpace(password))
    throw new ArgumentException("password is required for p12/pfx");

  var cert = new X509Certificate2(keyPath, password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.EphemeralKeySet);
  privateKey = cert.GetRSAPrivateKey() ?? throw new InvalidOperationException("No private key found in keystore");
}
else if (lowerPath.EndsWith(".key") || lowerPath.EndsWith(".pem"))
{
  string pem = File.ReadAllText(keyPath, Encoding.UTF8);

  // Optional: PEM may include CERTIFICATE block(s)
  bool hasCert = pem.Contains("-----BEGIN CERTIFICATE-----", StringComparison.Ordinal);
  bool hasPkcs8Key = pem.Contains("-----BEGIN PRIVATE KEY-----", StringComparison.Ordinal);
  bool hasPkcs1Key = pem.Contains("-----BEGIN RSA PRIVATE KEY-----", StringComparison.Ordinal);

  if (hasCert && !hasPkcs8Key && !hasPkcs1Key)
  {
    throw new InvalidOperationException("PEM contains CERTIFICATE only. Signing requires a PRIVATE KEY.");
  }

  if (hasPkcs1Key)
  {
    throw new InvalidOperationException(
      "BEGIN RSA PRIVATE KEY (PKCS#1) is not supported by this code. Convert to PKCS#8: BEGIN PRIVATE KEY.");
  }

  if (!hasPkcs8Key)
  {
    throw new InvalidOperationException("No supported private key block found in PEM");
  }

  var rsa = RSA.Create();
  rsa.ImportFromPem(pem);
  privateKey = rsa;
}
else
{
  throw new InvalidOperationException("Unsupported key format");
}

// SIGN
byte[] signatureBytes = privateKey.SignData(
  Encoding.UTF8.GetBytes(dataToSign),
  HashAlgorithmName.SHA256,
  RSASignaturePadding.Pkcs1
);
string signature = Convert.ToBase64String(signatureBytes);
privateKey.Dispose();
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const forge = require('node-forge');


// INPUTS
const jsonPayload = `{
            "OSVBeneficiaryBankDetailsRequest": {
                "OSVBeneficiaryBankDetails": [
                    {
                        "NationalId": "1231512323",
                        "IBANAccount": "SA2012345678972123456789"
                    }
                ]
            }
        }`; // Pass payload as a string

const keyPath = path.join(__dirname, 'path of file'); // .p12 | .pfx | .key | .pem
const password = 'password'; // only for p12/pfx

// VALIDATION
if (!jsonPayload || !jsonPayload.trim()) {
  throw new Error('jsonPayload must not be empty');
}
if (!keyPath || !keyPath.trim()) {
  throw new Error('keyPath must not be empty');
}

// JSON PARSE + CANONICALIZE
const payloadNode = JSON.parse(jsonPayload);

const canonicalize = (value) => {
  if (Array.isArray(value)) {
    return value.map(canonicalize);
  }

  if (value && typeof value === 'object') {
    return Object.keys(value)
      .sort()
      .reduce((acc, key) => {
      acc[key] = canonicalize(value[key]);
      return acc;
    }, {});
  }

  return value;
};

const canonicalNode = canonicalize(payloadNode);
const dataToSign = JSON.stringify(canonicalNode);

// KEY LOADER (AUTO DETECT)
let privateKey;
const lowerPath = keyPath.toLowerCase();

if (lowerPath.endsWith('.p12') || lowerPath.endsWith('.pfx')) {
  if (!password || !password.trim()) {
    throw new Error('password is required for p12/pfx');
  }

  const p12Der = fs.readFileSync(keyPath, { encoding: 'binary' });
  const p12Asn1 = forge.asn1.fromDer(p12Der);
  const p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, false, password);

  const keyBags = p12.getBags({ bagType: forge.pki.oids.pkcs8ShroudedKeyBag })[
    forge.pki.oids.pkcs8ShroudedKeyBag
  ];

  if (!keyBags || keyBags.length === 0) {
    throw new Error('No private key found in keystore');
  }

  const pem = forge.pki.privateKeyToPem(keyBags[0].key);
  privateKey = crypto.createPrivateKey({ key: pem, format: 'pem' });
} else if (lowerPath.endsWith('.key') || lowerPath.endsWith('.pem')) {
  const pem = fs.readFileSync(path.resolve(keyPath), 'utf8');

  // Optional: PEM may include CERTIFICATE block(s)
  const hasCert = pem.includes('-----BEGIN CERTIFICATE-----');
  const hasPkcs8Key = pem.includes('-----BEGIN PRIVATE KEY-----');
  const hasPkcs1Key = pem.includes('-----BEGIN RSA PRIVATE KEY-----');

  if (hasCert && !hasPkcs8Key && !hasPkcs1Key) {
    throw new Error('PEM contains CERTIFICATE only. Signing requires a PRIVATE KEY.');
  }

  if (hasPkcs1Key) {
    throw new Error(
      'BEGIN RSA PRIVATE KEY (PKCS#1) is not supported by this code. Convert to PKCS#8: BEGIN PRIVATE KEY.'
    );
  }

  if (!hasPkcs8Key) {
    throw new Error('No supported private key block found in PEM');
  }

  privateKey = crypto.createPrivateKey({ key: pem, format: 'pem' });
} else {
  throw new Error('Unsupported key format');
}

// SIGN
const signer = crypto.createSign('RSA-SHA256');
signer.update(dataToSign, 'utf8');
signer.end();
const signature = signer.sign(privateKey).toString('base64');