Приклади формування підпису даних замовлення (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]);
}