Приклади верифікації підпису webhook

Параметр містить підпис тіла запиту вебхуку по стандарту ECDSA.

Go
                        
package main

  import (
    "crypto/ecdsa"
    "crypto/sha256"
    "crypto/x509"
    "encoding/base64"
    "encoding/pem"
    "log"
  )

  var (
    // example pubkey, you should receive one at https://api.monobank.ua/personal/checkout/signature/public/key 

    pubKeyBase64 = `LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFQUc1LzZ3NnZubGJZb0ZmRHlYWE4vS29CbVVjTgo3NWJSUWg4MFBhaEdldnJoanFCQnI3OXNSS0JSbnpHODFUZVQ5OEFOakU1c0R3RmZ5Znhub0ZJcmZBPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==`

    // value from X-Sign header in webhook request
    xSignBase64 = `MEUCIQC/mVKhi8FKoayul2Mim3E2oaIOCNJk5dEXxTqbkeJSOQIgOM0hsW0qcP2H8iXy1aQYpmY0SJWEaWur7nQXlKDCFxA=`

 // webhook request body bytes


func main() {
    pubKeyBytes, err := base64.StdEncoding.DecodeString(pubKeyBase64)
    if err != nil {
        panic(err)

    }

    signatureBytes, err := base64.StdEncoding.DecodeString(xSignBase64)
    if err != nil {
        panic(err)
    }

    block, _ := pem.Decode(pubKeyBytes)
    if block == nil {
        panic("invalid pem")
    }

    genericPubKey, err := x509.ParsePKIXPublicKey(block.Bytes)
    if err != nil {
        panic(err)
    }

    pubKey, ok := genericPubKey.(*ecdsa.PublicKey)
    if !ok {
        panic("invalid key")
    }

    hash := sha256.Sum256(bodyBytes)

    ok = ecdsa.VerifyASN1(pubKey, hash[:], signatureBytes)
    if !ok {
        panic("invalid X-Sign")
    }

    log.Println("OK")
  }

                    
Python
                        
import base64
  import hashlib

  import ecdsa

  # example pubkey, you should receive one at  https://api.monobank.ua/personal/checkout/signature/public/key 
  pub_key_base64 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFQUc1LzZ3NnZubGJZb0ZmRHlYWE4vS29CbVVjTgo3NWJSUWg4MFBhaEdldnJoanFCQnI3OXNSS0JSbnpHODFUZVQ5OEFOakU1c0R3RmZ5Znhub0ZJcmZBPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=="

  # value from X-Sign header in webhook request
  x_sign_base64 = "MEUCIQC/mVKhi8FKoayul2Mim3E2oaIOCNJk5dEXxTqbkeJSOQIgOM0hsW0qcP2H8iXy1aQYpmY0SJWEaWur7nQXlKDCFxA="

 // webhook request body bytes

if __name__ == '__main__':
      pub_key_bytes = base64.b64decode(pub_key_base64)
      signature_bytes = base64.b64decode(x_sign_base64)
      pub_key = ecdsa.VerifyingKey.from_pem(pub_key_bytes.decode())

      ok = pub_key.verify(signature_bytes, body_bytes, sigdecode=ecdsa.util.sigdecode_der, hashfunc=hashlib.sha256)
      if ok:
          print("OK")
      else:
          print("NOT OK")

                    
Php
                        
<?php

  // example pubkey, you should receive one at  https://api.monobank.ua/personal/checkout/signature/public/key 
  $pubKeyBase64 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFQUc1LzZ3NnZubGJZb0ZmRHlYWE4vS29CbVVjTgo3NWJSUWg4MFBhaEdldnJoanFCQnI3OXNSS0JSbnpHODFUZVQ5OEFOakU1c0R3RmZ5Znhub0ZJcmZBPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==";

  // value from X-Sign header in webhook request
  $xSignBase64 = "MEUCIQC/mVKhi8FKoayul2Mim3E2oaIOCNJk5dEXxTqbkeJSOQIgOM0hsW0qcP2H8iXy1aQYpmY0SJWEaWur7nQXlKDCFxA=";

  $message = (webhook request body)
 $signature = base64_decode($xSignBase64);
  $publicKey = openssl_get_publickey(base64_decode($pubKeyBase64));

  $result = openssl_verify($message, $signature, $publicKey, OPENSSL_ALGO_SHA256);

  echo $result === 1 ? "OK" : "NOT OK";

                    
NodeJs
                        
const crypto = require('crypto');

  // example pubkey, you should receive one at  https://api.monobank.ua/personal/checkout/signature/public/key 
  let pubKeyBase64 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFQUc1LzZ3NnZubGJZb0ZmRHlYWE4vS29CbVVjTgo3NWJSUWg4MFBhaEdldnJoanFCQnI3OXNSS0JSbnpHODFUZVQ5OEFOakU1c0R3RmZ5Znhub0ZJcmZBPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==";

  // value from X-Sign header in webhook request
  let xSignBase64 = "MEUCIQC/mVKhi8FKoayul2Mim3E2oaIOCNJk5dEXxTqbkeJSOQIgOM0hsW0qcP2H8iXy1aQYpmY0SJWEaWur7nQXlKDCFxA=";

  let message = (webhook request body)

let signatureBuf = Buffer.from(xSignBase64, 'base64');
  let publicKeyBuf = Buffer.from(pubKeyBase64, 'base64');

  let verify = crypto.createVerify('SHA256');

  verify.write(message);
  verify.end();

  let result = verify.verify(publicKeyBuf, signatureBuf);

  console.log(result === true ? "OK" : "NOT OK");

                    
Java
                        
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.Signature;
import java.security.interfaces.ECPublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;


import static org.junit.jupiter.api.Assertions.assertTrue;

class X_SignTest {

    private final String BODY = "{
" +
    " "payment": {
" +
    " "referenceid": "230925PP111695626670692",
" +
    " "rrnc": "021110701778",
" +
    " "invoiceNumber": "2458-3747-5436-0454",
" +
    " "owner_pan_a": false,
" +
    " "ext_ref": "1a1c925e-f597-4798-9d41-a8fb267be00d_2222",
" +
    " "stage": "v",
" +
    " "status": "p"
" +
    " }
" +
    " }";


    private final String PUBLIC_KEY_BASE64 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFZEJ2eG42d1pRNG1mZHN3bVZLYTgwbXNIODFBaQoyMG9lZG9UNkhYS2hYUHE0TXhVTUMxK1lUTDRrdWx4R1d5alhIWDdtc2dQcEFRNmVYQXdkNDluYm53PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==";
    private final String X_SIGH_FROM_HEADER_BASE64 = "MEQCIHnSF8YiWGhlUD3VhvFLbUefnyl/dOIcCqDE2l8B2sTXAiBGGThskb6kR3hF6VKac9dPFayI5lNITnjXvv0znPYfIg==";

    @Test
    void testBase64SignAndVerify() throws Exception {
        byte[] signature = Base64.getDecoder().decode(X_SIGH_FROM_HEADER_BASE64);
        boolean isValid = this.verify(BODY, signature);
        assertTrue(isValid, "The signature should be valid");
    }

    boolean verify(String data, byte[] signature) throws Exception {
        ECPublicKey publicKey = loadPublicKeyFromPEM(PUBLIC_KEY_BASE64);
        Signature ecdsaVerify = Signature.getInstance("SHA256withECDSA");
        ecdsaVerify.initVerify(publicKey);
        ecdsaVerify.update(data.getBytes(StandardCharsets.UTF_8));
        return ecdsaVerify.verify(signature);
    }

    private ECPublicKey loadPublicKeyFromPEM(String publicKeyBase64) throws Exception {
        String decodedPublicKey = new String(Base64.getDecoder().decode(publicKeyBase64), StandardCharsets.UTF_8);
        byte[] keyBytes = decodePEM(decodedPublicKey);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("EC");
        return (ECPublicKey) kf.generatePublic(spec);
    }

    private byte[] decodePEM(String pem) throws IOException {
        PemReader pemReader = new PemReader(new StringReader(pem));
        try {
            PemObject pemObject = pemReader.readPemObject();
            if (pemObject == null) {
                throw new IllegalArgumentException("Invalid PEM format or content.");
            }
            return pemObject.getContent();
        } finally {
            pemReader.close();
        }
    }
}

                    
HEADER PARAMETERS:
get

/personal/checkout/signature/public/key

200

Response samples
200