Приклади формування підпису даних замовлення (signature)
signature — це цифровий підпис даних замовлення, згенерований з використанням приватного ключа ECDSA P-256.
Що підписується:
Підпис формується шляхом підписання SHA-256 хешу від строки: JSON.stringify(orderData) + requestId
Приклади реалізації:
Go
package main
import (
"crypto/ecdsa"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"os"
)
var (
privateKeyPath = "./private.pem"
publicKeyPath = "./pubkey.pem"
)
func Sign(body []byte) (string, error) {
const privateKeyPath = "./private.pem"
bytes, err := os.ReadFile(privateKeyPath)
if err != nil {
return "", err
}
block, _ := pem.Decode(bytes)
var privateKey *ecdsa.PrivateKey
if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
if ecdsaKey, ok := key.(*ecdsa.PrivateKey); ok {
privateKey = ecdsaKey
}
}
if privateKey == nil {
if privateKey, err = x509.ParseECPrivateKey(block.Bytes); err != nil {
return "", err
}
}
hash := sha256.Sum256(body)
signBytes, err := privateKey.Sign(rand.Reader, hash[:], nil)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(signBytes), nil
}
Python
import base64
import hashlib
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed
def sign(body: bytes, private_key_path: str = "./private.pem") -> str:
with open(private_key_path, "rb") as f:
pem_data = f.read()
private_key = serialization.load_pem_private_key(
pem_data,
password=None,
)
digest = hashlib.sha256(body).digest()
# Підписуємо вже готовий хеш
signature_der = private_key.sign(
digest,
ec.ECDSA(Prehashed(hashes.SHA256()))
)
return base64.b64encode(signature_der).decode("ascii")
Java
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
public class MonoPaySign {
static byte[] readPem(String path) throws Exception {
String pem = new String(Files.readAllBytes(Paths.get(path)));
pem = pem.replaceAll("-----BEGIN ([A-Z ]+)-----", "")
.replaceAll("-----END ([A-Z ]+)-----", "")
.replaceAll("\s", "");
return Base64.getDecoder().decode(pem);
}
public static String sign(byte[] body, String privateKeyPemPath) throws Exception {
byte[] keyBytes = readPem(privateKeyPemPath); // PKCS#8
KeyFactory kf = KeyFactory.getInstance("EC");
PrivateKey privateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
Signature sig = Signature.getInstance("SHA256withECDSA");
sig.initSign(privateKey);
sig.update(body);
byte[] signatureDer = sig.sign();
return Base64.getEncoder().encodeToString(signatureDer);
}
public static void main(String[] args) throws Exception {
byte[] body = "{"test":1}".getBytes();
System.out.println(sign(body, "./private_pkcs8.pem"));
}
}
Ruby
require "openssl"
require "base64"
def sign(body, private_key_path = "./private.pem")
pem = File.read(private_key_path)
key = OpenSSL::PKey.read(pem) # EC key
digest = OpenSSL::Digest::SHA256.digest(body)
# DER/ASN.1 ECDSA підпис над digest
signature_der = key.dsa_sign_asn1(digest)
Base64.strict_encode64(signature_der)
end
puts sign('{"test":1}')
Php
<?php
function sign($body, $privateKeyPath = "./private.pem") {
$privateKeyPem = file_get_contents($privateKeyPath);
$privateKey = openssl_pkey_get_private($privateKeyPem);
if ($privateKey === false) {
throw new Exception("Cannot load private key");
}
$signature = "";
$ok = openssl_sign($body, $signature, $privateKey, OPENSSL_ALGO_SHA256);
openssl_free_key($privateKey);
if (!$ok) {
throw new Exception("Signing failed");
}
// ECDSA signature у DER, кодуємо в base64
return base64_encode($signature);
}
echo sign('{"test":1}');
NodeJs
// CommonJS
const fs = require("fs");
const crypto = require("crypto");
// ES Modules
// import fs from "fs";
// import crypto from "crypto";
function generateSignature (dataToSign, requestId, privateKeyPath = "./private.pem") {
try {
const privateKeyPem = fs.readFileSync(privateKeyPath, "utf8");
const privateKeyMatch = privateKeyPem.match(/-----BEGIN PRIVATE KEY-----([\s\S]*?)-----END PRIVATE KEY-----/);
if (!privateKeyMatch) {
throw new Error("Invalid private key format");
}
const keyData = privateKeyMatch[1].replace(/\s/g, "");
const privateKey = crypto.createPrivateKey({
key: Buffer.from(`-----BEGIN PRIVATE KEY-----\n${keyData}\n-----END PRIVATE KEY-----`),
format: 'pem',
type: 'pkcs8'
});
const dataStr = JSON.stringify(dataToSign) + requestId;
const dataBytes = Buffer.from(dataStr, 'utf8');
const signature = crypto.sign('sha256', dataBytes, {
key: privateKey,
dsaEncoding: 'ieee-p1363'
});
const derSignature = ecdsaRawToDer(signature);
return derSignature.toString('base64');
}
catch (error) {
throw new Error(`Signature generation failed: ${error.message}`);
}
}
function ecdsaRawToDer (signature) {
const sig = new Uint8Array(signature);
const r = sig.slice(0, sig.length / 2);
const s = sig.slice(sig.length / 2);
function trim (bytes) {
let i = 0;
while (i < bytes.length - 1 && bytes[i] === 0) {
i++;
}
return bytes.slice(i);
}
function encodeInt (bytes) {
bytes = trim(bytes);
if (bytes[0] & 0x80) {
const tmp = new Uint8Array(bytes.length + 1);
tmp[0] = 0;
tmp.set(bytes, 1);
bytes = tmp;
}
return Uint8Array.from([0x02, bytes.length, ...bytes]);
}
const rDer = encodeInt(r);
const sDer = encodeInt(s);
const seqLen = rDer.length + sDer.length;
return Buffer.from([0x30, seqLen, ...rDer, ...sDer]);
}