const crypto = require("crypto");
const fs = require('fs');
// Load the public key from a local file
const publicKeyPath = 'keys/public-key.pem';
const publicKey = fs.readFileSync(publicKeyPath, 'utf-8');
function verify(publicKey, signature, payload) {
const verifier = crypto.createVerify("RSA-SHA256");
verifier.update(payload);
return verifier.verify(
{ key: publicKey, padding: crypto.constants.RSA_PKCS1_PSS_PADDING },
Buffer.from(signature, 'base64')
);
}
// Notification JSON
const data = { /* your data here */ };
const { sequence_number, signature, ...payload } = data;
const verified = verify(publicKey, signature, JSON.stringify(payload).replace(/\s/g, ''));
console.log('Signature Verified: ', verified);
package com.example;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class SignatureVerification {
static {
Security.addProvider(new BouncyCastleProvider());
}
public static void main(String[] args) {
try {
// Load the public key from a local file
String publicKeyPath = "public-key.pem";
PublicKey publicKey = getPublicKey(publicKeyPath);
// Notification JSON String
String jsonData = new String(Files.readAllBytes(Paths.get("notification_payload.json")));
// Parse the JSON data
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> data = objectMapper.readValue(jsonData, Map.class);
// Remove 'sequence_number' and 'signature' from the data
data.remove("sequence_number");
String signature = (String) data.remove("signature");
// Convert the modified JSON data back to a JSON string and remove spaces
String jsonString = objectMapper.writeValueAsString(data).replace(" ", "");
// Decode the Base64-encoded signature
byte[] signatureDecoded = Base64.getDecoder().decode(signature);
// Determine the signature length in bytes
int signatureLengthBytes = signatureDecoded.length;
// Set the digest output length (SHA-256 produces a 32-byte hash)
int digestOutputLength = 32;
// Calculate the salt length
int saltLength = signatureLengthBytes - digestOutputLength - 2;
// Verify the signature using the public key
Signature sig = Signature.getInstance("SHA256withRSA/PSS");
PSSParameterSpec pssSpec = new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, saltLength, 1);
sig.setParameter(pssSpec);
sig.initVerify(publicKey);
sig.update(jsonString.getBytes());
boolean isVerified = sig.verify(signatureDecoded);
System.out.println("Signature Verified: " + isVerified);
} catch (Exception e) {
e.printStackTrace();
}
}
private static PublicKey getPublicKey(String filename) throws Exception {
byte[] keyBytes = Files.readAllBytes(Paths.get(filename));
String publicKeyPEM = new String(keyBytes);
publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----", "");
publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
publicKeyPEM = publicKeyPEM.replaceAll("\\s", "");
byte[] decoded = Base64.getDecoder().decode(publicKeyPEM);
X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(spec);
}
}
<?php
// Include the Composer autoload file
require __DIR__ . '/vendor/autoload.php';
// Use the Phpseclib3 library
use phpseclib3\Crypt\RSA;
// Load the public key from a local file
$publicKeyPath = 'keys/public-key.pem';
$publicKey = RSA::loadPublicKey(file_get_contents($publicKeyPath));
// Notification JSON String
$jsonData = '{ ... }';
// Decode the JSON data
$data = json_decode($jsonData, true);
// Remove 'sequence_number' and 'signature' from the data
unset($data['sequence_number']);
$signature = $data['signature'];
unset($data['signature']);
// Convert the modified JSON data back to a JSON string and remove spaces
$jsonString = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$jsonString = str_replace(' ', '', $jsonString);
// Decode the signature
$signatureDecoded = base64_decode($signature);
// Determine the signature length in bytes
$signatureLengthBytes = strlen($signatureDecoded);
// Set the digest output length
$digestOutputLength = 32;
// Calculate the salt length
$saltLength = $signatureLengthBytes - $digestOutputLength - 2;
// Verify the signature using the public key
$verified = $publicKey
->withPadding(RSA::SIGNATURE_PSS)
->withHash('sha256')
->withSaltLength($saltLength)
->verify($jsonString, $signatureDecoded);
if ($verified) {
echo 'Signature Verified: true';
} else {
echo 'Signature Verified: false';
}
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_pem_public_key
import base64
import json
# Load the public key from a local file
public_key_path = 'keys/public-key.pem'
with open(public_key_path, 'rb') as key_file:
public_key = load_pem_public_key(key_file.read())
def verify(public_key, signature, payload):
try:
public_key.verify(
base64.b64decode(signature),
payload.encode('utf-8'),
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
return True
except Exception as e:
return False
# Notification JSON
data = { ... }
sequence_number = data.pop('sequence_number')
signature = data.pop('signature')
payload = json.dumps(data, separators=(',', ':'))
verified = verify(public_key, signature, payload)
print('Signature Verified: ', verified)
using System;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Signers;
public class RsaPssVerifier
{
public static bool VerifySignature(string publicKeyPem, string webhookResponse)
{
try
{
JsonNode jsonNode = JsonNode.Parse(webhookResponse);
string signature = jsonNode["signature"].GetValue<string>();
jsonNode.AsObject().Remove("signature");
jsonNode.AsObject().Remove("sequence_number");
string messageToVerify = jsonNode.ToJsonString(new JsonSerializerOptions { WriteIndented = false });
AsymmetricKeyParameter publicKeyParam = PublicKeyFactory.CreateKey(Convert.FromBase64String(GetPublicKeyBytes(publicKeyPem)));
RsaKeyParameters rsaKeyParameters = (RsaKeyParameters)publicKeyParam;
// Convert message to bytes
byte[] messageBytes = Encoding.UTF8.GetBytes(messageToVerify);
// Create SHA-256 digest
var digest = new Org.BouncyCastle.Crypto.Digests.Sha256Digest();
// Calculate salt length
int emBits = rsaKeyParameters.Modulus.BitLength - 1;
int emLen = (emBits + 7) / 8;
int saltLength = emLen - digest.GetDigestSize() - 2;
PssSigner signer = new PssSigner(new RsaEngine(), digest, saltLength);
signer.Init(false, rsaKeyParameters);
signer.BlockUpdate(messageBytes, 0, messageBytes.Length);
// Verify the signature
return signer.VerifySignature(Convert.FromBase64String(signature));
}
catch (Exception ex)
{
// Exception logging
Console.WriteLine($"An error occurred: {ex.Message}");
return false;
}
}
private static string GetPublicKeyBytes(string publicKeyPem)
{
const string PemHeader = "-----BEGIN PUBLIC KEY-----";
const string PemFooter = "-----END PUBLIC KEY-----";
return publicKeyPem
.Replace(PemHeader, "")
.Replace(PemFooter, "")
.Replace("\n", "")
.Trim();
}
}
// Usage example:
class Program
{
static void Main()
{
// Public key for Age verification service
string publicKey = @"-----BEGIN PUBLIC KEY-----
-----END PUBLIC KEY-----";
// Full webhook response object
string webhookResponse = @"{ }";
bool isVerified = RsaPssVerifier.VerifySignature(publicKey, webhookResponse);
Console.WriteLine($"Signature is {(isVerified ? "valid" : "invalid")}");
}
}
package main
import (
"crypto"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
)
// Age verification service public key
const publicKey = ``
// Full Webhook response
const message = ``
type Message struct {
Method string `json:"method"`
Result bool `json:"result"`
Age int `json:"age"`
SessionKey string `json:"session_key"`
ReferenceID string `json:"reference_id"`
ID string `json:"id"`
Timestamp int64 `json:"timestamp"`
NotificationURL string `json:"notification_url"`
EvidenceID string `json:"evidence_id"`
State string `json:"state"`
CheckType string `json:"check_type"`
ErrorCode string `json:"error_code,omitempty"`
}
type Signature struct {
Signature string `json:"signature"`
}
func main() {
block, _ := pem.Decode([]byte(publicKey))
if block == nil {
panic("failed to parse PEM block containing the key")
}
pkixPub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
panic(err.Error())
}
publicKey, ok := pkixPub.(*rsa.PublicKey)
if !ok {
panic("Pub key is not rsa")
}
var msg Message
var sig Signature
err = json.Unmarshal([]byte(message), &msg)
if err != nil {
panic(err.Error())
}
err = json.Unmarshal([]byte(message), &sig)
if err != nil {
panic(err.Error())
}
p, err := json.Marshal(msg)
if err != nil {
panic(err.Error())
}
vb, err := base64.StdEncoding.DecodeString(sig.Signature)
if err != nil {
panic(err)
}
msgHashSum, err := hash(p)
if err != nil {
panic(err.Error())
}
err = rsa.VerifyPSS(publicKey, crypto.SHA256, msgHashSum, vb, nil)
if err != nil {
panic(err.Error())
}
fmt.Println("message is verified")
}
func hash(msg []byte) ([]byte, error) {
msgHash := sha256.New()
_, err := msgHash.Write(msg)
if err != nil {
return nil, err
}
msgHashSum := msgHash.Sum(nil)
return msgHashSum, nil
}