Приклади верифікації підпису 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:
X-Token
required
string
Токен з особистого кабінету https://web.monobank.ua/ або тестовий токен з https://api.monobank.ua/
get
/personal/checkout/signature/public/key
200
Response samples
200