How to Generate and Verify a Signature
PrerequisiteYou must generate a 256-bit elliptic curve keypair (EC-256), also known as NIST P-256, secp256r1, or prime256v1, and share the public key with Blinc.
The private key must be generated and securely stored in PKCS#8 format within your environment. Never share the private key with Blinc.
We recommend using separate keypairs for test and live/production environments.
For a Blinc request that requires a signature, sign its plaintext JSON payload before encryption. Send the signature in the x-signature header.
Signing requirements
| Item | Requirement |
|---|---|
| Algorithm | ECDSA with SHA-256 |
| Curve | NIST P-256 (secp256r1) |
| Signature | Base64-encoded, 64-byte IEEE P1363 (32-byte r followed by 32-byte s) |
| Public key | Base64-encoded X.509 SubjectPublicKeyInfo DER bytes registered with Blinc |
| Private key | Matching ECDSA P-256 private key, stored securely in your system and never shared with Blinc |
| Message | UTF-8 bytes of compact JSON converted to uppercase using locale-independent casing |
Keep the private key on your backend. Share only the public key with Blinc, without PEM header or footer lines. Use a separate key pairs for sandbox and production.
//Sample public key
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3G+eSLh2rQ6zxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxdQpCOOJw==Build the message to sign
Start with the plaintext object. This generic example uses a single key1/value1 pair. Serialize it as a compact JSON, preserving the property order:
{"key1":"value1"}Convert the entire JSON string to uppercase, then encode it as UTF-8:
{"KEY1":"VALUE1"}Sign those bytes with your private key using ECDSA P-256 and SHA-256. Base64-encode the P1363 signature.
Send the request
Send Content-Type: application/json and x-signature: <Base64 P1363 signature> in the request header, on a request that requires signing.
Sample Code Snippet for generating a signature
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
var request = new { key1 = "value1" };
var message = JsonSerializer.Serialize(request).ToUpperInvariant();
using var signer = ECDsa.Create(ECCurve.NamedCurves.nistP256);
signer.ImportPkcs8PrivateKey(Convert.FromBase64String(privateKeyBase64), out _);
var signature = signer.SignData(Encoding.UTF8.GetBytes(message), HashAlgorithmName.SHA256);
var signatureBase64 = Convert.ToBase64String(signature);import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Locale;
String json = "{\"key1\":\"value1\"}";
byte[] message = json.toUpperCase(Locale.ROOT).getBytes(StandardCharsets.UTF_8);
byte[] keyBytes = Base64.getDecoder().decode(privateKeyBase64);
PrivateKey key = KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
Signature signer = Signature.getInstance("SHA256withECDSAinP1363Format");
signer.initSign(key);
signer.update(message);
String signatureBase64 = Base64.getEncoder().encodeToString(signer.sign());const crypto = require("node:crypto");
const body = { key1: "value1" };
const message = JSON.stringify(body).toUpperCase();
const signer = crypto.createSign("SHA256");
signer.update(Buffer.from(message, "utf8"));
signer.end();
const signatureBase64 = signer.sign({ key: privateKeyPem, dsaEncoding: "ieee-p1363" }).toString("base64");import base64
import json
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec, utils
body = {"key1": "value1"}
message = json.dumps(body, separators=(",", ":")).upper().encode("utf-8")
private_key = serialization.load_pem_private_key(private_key_pem, password=None)
der = private_key.sign(message, ec.ECDSA(hashes.SHA256()))
r, s = utils.decode_dss_signature(der)
signature_base64 = base64.b64encode(r.to_bytes(32, "big") + s.to_bytes(32, "big")).decode("ascii")<?php
$body = array("key1" => "value1");
$message = strtoupper(json_encode($body, JSON_UNESCAPED_SLASHES));
openssl_sign($message, $derSignature, $privateKeyPem, OPENSSL_ALGO_SHA256);
$signatureBase64 = base64_encode(derToP1363($derSignature));Verifing a signature
To verify a signed payload, repeat the compact-JSON, uppercase, and UTF-8 steps on the plaintext. Decode the Base64 signature and verify its P1363 bytes with blinc's public key using ECDSA P-256 with SHA-256. Reject the request if verification fails.
Sample Code snippet for verifing a signature
using System.Security.Cryptography;
using System.Text;
static bool Verify(string compactJson, string signatureBase64, string publicKeyBase64)
{
try
{
byte[] signature = Convert.FromBase64String(signatureBase64);
if (signature.Length != 64) return false;
using var verifier = ECDsa.Create(ECCurve.NamedCurves.nistP256);
verifier.ImportSubjectPublicKeyInfo(Convert.FromBase64String(publicKeyBase64), out _);
if (verifier.KeySize != 256) return false;
return verifier.VerifyData(
Encoding.UTF8.GetBytes(compactJson.ToUpperInvariant()),
signature,
HashAlgorithmName.SHA256);
}
catch (Exception error) when (error is FormatException or CryptographicException or ArgumentException)
{
return false;
}
}import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Locale;
static boolean verify(String compactJson, String signatureBase64, String publicKeyBase64) {
try {
byte[] signature = Base64.getDecoder().decode(signatureBase64);
if (signature.length != 64) return false;
PublicKey key = KeyFactory.getInstance("EC").generatePublic(
new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyBase64)));
Signature verifier = Signature.getInstance("SHA256withECDSAinP1363Format");
verifier.initVerify(key);
verifier.update(compactJson.toUpperCase(Locale.ROOT).getBytes(StandardCharsets.UTF_8));
return verifier.verify(signature);
} catch (Exception error) {
return false;
}
}const crypto = require("node:crypto");
function verify(compactJson, signatureBase64, publicKeyBase64) {
try {
const signature = Buffer.from(signatureBase64, "base64");
if (signature.length !== 64) return false;
const key = crypto.createPublicKey({
key: Buffer.from(publicKeyBase64, "base64"), format: "der", type: "spki"
});
if (key.asymmetricKeyType !== "ec" || key.asymmetricKeyDetails?.namedCurve !== "prime256v1") return false;
return crypto.verify(
"sha256", Buffer.from(compactJson.toUpperCase(), "utf8"),
{ key, dsaEncoding: "ieee-p1363" }, signature
);
} catch {
return false;
}
}import base64
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec, utils
def verify(compact_json: str, signature_base64: str, public_key_base64: str) -> bool:
try:
signature = base64.b64decode(signature_base64, validate=True)
if len(signature) != 64:
return False
public_key = serialization.load_der_public_key(
base64.b64decode(public_key_base64, validate=True)
)
if not isinstance(public_key, ec.EllipticCurvePublicKey) or not isinstance(
public_key.curve, ec.SECP256R1
):
return False
r = int.from_bytes(signature[:32], "big")
s = int.from_bytes(signature[32:], "big")
public_key.verify(
utils.encode_dss_signature(r, s),
compact_json.upper().encode("utf-8"),
ec.ECDSA(hashes.SHA256()),
)
return True
except (InvalidSignature, ValueError, TypeError):
return False<?php
function p1363ToDer(string $signature): string {
if (strlen($signature) !== 64) throw new InvalidArgumentException("Expected 64 bytes");
$integer = static function (string $bytes): string {
$bytes = ltrim($bytes, "\x00");
if ($bytes === "") $bytes = "\x00";
if ((ord($bytes[0]) & 0x80) !== 0) $bytes = "\x00" . $bytes;
return "\x02" . chr(strlen($bytes)) . $bytes;
};
$body = $integer(substr($signature, 0, 32)) . $integer(substr($signature, 32, 32));
return "\x30" . chr(strlen($body)) . $body;
}
function verify(string $compactJson, string $signatureBase64, string $publicKeyBase64): bool {
try {
$signature = base64_decode($signatureBase64, true);
$der = base64_decode($publicKeyBase64, true);
if ($signature === false || strlen($signature) !== 64 || $der === false) return false;
$pem = "-----BEGIN PUBLIC KEY-----\n"
. chunk_split(base64_encode($der), 64, "\n")
. "-----END PUBLIC KEY-----\n";
$key = openssl_pkey_get_public($pem);
if ($key === false) return false;
$details = openssl_pkey_get_details($key);
if (($details["ec"]["curve_name"] ?? null) !== "prime256v1") return false;
return openssl_verify(strtoupper($compactJson), p1363ToDer($signature), $key, OPENSSL_ALGO_SHA256) === 1;
} catch (Throwable $error) {
return false;
}
}Updated 4 days ago