Signature & Data encryption

Signature

The signature data is transmitted via the request header X-UPA-SIGN.

Signature data rule

METHOD|URI|TIMESTAMP|REQUEST_ID|SIGN_PARAM

When the request parameters are empty, the PAYLOAD is empty, and the signature string ends with |.

Signature Data Rules Explanation

METHOD:API request method(GET/POST)

URI:API path(Example:/api/v1/customer/add)

TIMESTAMP:Request timestamp (milliseconds). 
           Request timeout is 69 seconds. 
           Must match the X-UPAY-TIMESTAMP request header.

REQUEST_ID:Unique request ID. Must match the X-UPAY-REQUESTID request header.

SIGN_PARAM:Signature parameters (refer to Signature Parameter Description)

Signature Parameter Description

1. Sort the request parameters by KEY in ascending order.

2. Concatenate them in key=value format.

3. Join multiple fields with &.

Example:

{
  "groupNo": "101680210001",
  "email": "[email protected]",
  "globalCode": "+1",
  "phone": "127897890",
  "givenName": "Demo given name",
  "lastName": "Demo last name",
  "company": "Demo company",
  "occupation": "Demo occupation",
  "position": "Demo position",
  "annualIncome": 100.5,
  "workYear": "1",
  "address": {
    "name": "Demo given name",
    "countryId": 12,
    "provinceId": 475,
    "postalCode": "94105",
    "address": "535 Mission St, Floor 12"
  },
  "remark": "This is sample data for adding customers"
}

Sorted data:

{
  "address": {
    "address": "535 Mission St, Floor 12",
    "countryId": 12,
    "name": "Demo given name",
    "postalCode": "94105",
    "provinceId": 475
  },
  "annualIncome": 100.5,
  "company": "Demo company",
  "email": "[email protected]",
  "givenName": "Demo given name",
  "globalCode": "+1",
  "groupNo": "101680210001",
  "lastName": "Demo last name",
  "occupation": "Demo occupation",
  "phone": "127897890",
  "position": "Demo position",
  "remark": "This is sample data for adding customers",
  "workYear": "1"
}

Data Participating in Signature:

address={"address":"535 Mission St, Floor 12","cityId":2001,"countryId":840,"name":"Alex Wilson","postalCode":"94105","provinceId":1001}&annualIncome=12.5&company=Open Commerce Inc&[email protected]&givenName=Alex&globalCode=+1&groupNo=GRP202603260001&lastName=Wilson&occupation=Software Engineer&phone=4155550123&position=Senior Engineer&remark=customer created from open api sandbox&workYear=8

Data Signature Method

When signing, you need to use the Secretkey provided by UPay, which can be obtained from the merchant platform

1. Obtain the signature data.

2. Use the HMAC-SHA256 algorithm to calculate the hash value of the signature data.

3. Base64 encode the hash value to obtain the final signature.

Signature Example

ParameterExample
SecretKeyg3)&~8P9S+ZhqO@G8b9Ate%xa5Jh-.E%
METHODPOST
URI/api/v1/customer/add
TIMESTAMP1774573955994
REQUEST_ID366dde77c57a4e7e988c074a21a4f04c
Signature Dataaddress={"address":"535 Mission St, Floor 12","countryId":12,"name":"Demo given name","postalCode":"94105","provinceId":475}&annualIncome=100.5&company=Demo company&email=[email protected] &givenName=Demo given name&globalCode=+1&groupNo=101680210001&lastName=Demo last name&occupation=Demo occupation&phone=127897890&position=Demo position&remark=This is sample data for adding customers&workYear=1

Signature Raw String

POST|/api/v1/customer/add|1774573955994|366dde77c57a4e7e988c074a21a4f04c|address={"address":"535 Mission St, Floor 12","countryId":12,"name":"Demo given name","postalCode":"94105","provinceId":475}&annualIncome=100.5&company=Demo company&[email protected]&givenName=Demo given name&globalCode=+1&groupNo=101680210001&lastName=Demo last name&occupation=Demo occupation&phone=127897890&position=Demo position&remark=This is sample data for adding customers&workYear=1

Signed Data

The signed value is transmitted via the request header X-UPA-SIGN.

pRbX0ikM/4j/E3tBxNOO/m3Hra+4d2/r+xtRTJ6N5pk=

Signature Sample Code

/**
  * Signature
  * @param data Signature data
  * @param secretKey Signature key
  * @throws AppServerException
*/
 public static String generateHmacSha256(String data, String secretKey) throws Exception {
        
        Mac mac = Mac.getInstance("HmacSHA256");
   
        byte[] keyBytes = secretKey.getBytes();
        SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA256");
        
        mac.init(signingKey);
        
        byte[] rawHmac = mac.doFinal(data.getBytes());
        
        return Base64.getEncoder().encodeToString(rawHmac);
    }
import hmac
import hashlib
import base64

def generate_hmac_sha256(data: str, secret_key: str) -> str:
    """
    Generate an HMAC-SHA256 signature and return a Base64-encoded string
    
    Args:
        data: Data to be signed
        secretKey: Secret Key
    
    Returns:
        Base64-encoded HMAC-SHA256 signature
    """
    key_bytes = secretKey.encode('utf-8')
    data_bytes = data.encode('utf-8')
    
   
    raw_hmac = hmac.new(key_bytes, data_bytes, hashlib.sha256).digest()
     
    return base64.b64encode(raw_hmac).decode('utf-8')

Testing Website

https://www.devglan.com/online-tools/hmac-sha256-online

Data encryption/decryption

Encryption Process

Encryption uses two pairs of keys (public/private): one pair is the platform key, and the other pair is the merchant key.

Platform Key

  • Platform Public Key: Used by merchants to encrypt request parameters when calling APIs.

  • Platform Private Key: Used by UPay to decrypt the parameters after receiving the API request, obtaining the original data.

Merchant Key

  • Generate Key Pair (Example using OpenSSL)
openssl genrsa -out private.pem 2048 && openssl rsa -in private.pem -pubout -out public.pem
  • Public Key Example
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2r69mrDofiNPIAKNRV5v
BeVBjEnrYPgKf8LKDK+gLhLrepuTc+gxlJurH/gYwgRwD8DL5rmdcXRZMpX+L8gx
yszzD0qFWnBoumN1RHx2kkok1vgLr/dBlJRwVPZbZT69m/B8kGWR8JboBVOWJwxn
DX6WaRDyWVa2qu1KogQq4m028gJWZlVzs0bSExQqSW5ZiUFGyJygjTa2XAKqceSe
xHov8jR66mpnv0vJy6+4YJK34t2VzyNdEqseTE3spZkSJCzzxNV7xIs63u/WTenK
J1BWar+/rA63A+/0Lt2BB/Uf/iq0OwGm4t2d0Pf1O7hylXXP65BGunWvu0QWCsNo
xQIDAQAB
-----END PUBLIC KEY-----
  • Private Key Example
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDavr2asOh+I08g
Ao1FXm8F5UGMSetg+Ap/wsoMr6AuEut6m5Nz6DGUm6sf+BjCBHAPwMvmuZ1xdFky
lf4vyDHKzPMPSoVacGi6Y3VEfHaSSiTW+Auv90GUlHBU9ltlPr2b8HyQZZHwlugF
U5YnDGcNfpZpEPJZVraq7UqiBCribTbyAlZmVXOzRtITFCpJblmJQUbInKCNNrZc
Aqpx5J7Eei/yNHrqame/S8nLr7hgkrfi3ZXPI10Sqx5MTeylmRIkLPPE1XvEizre
79ZN6conUFZqv7+sDrcD7/Qu3YEH9R/+KrQ7Aabi3Z3Q9/U7uHKVdc/rkEa6da+7
RBYKw2jFAgMBAAECggEANwQQJe7mmosA5JkftNm6bK4rXUBeLeZUpat1K9mkHNJv
XUfxvw4gIjNAx+qbN3jsQloILoByo81SfdGRu6zLMSl43Fiuz39EJ9TJ8q6nF8YE
G/kI33n9iYQH+KZ5eC5ee/DxM1QIb41Uz7olIq8Q4Cj9ZXF4spWHndfOlI8dxhKg
3HLx90yW6/2D8teI2KoyDVd3FH/3dMsibAFwYbAlvfZzFuH0stOUyDOxt1CdWl/N
l4zTouGz82A+x8OPKf52E9W0iw+PnX/XOjDVpU39d21zhSmN3IjgfrW0vbbixD8F
1Xeusy9DYwNY60q2bo0xckdz8cisr59IT9iT+xfiBQKBgQDuM59iG56EljyEtwZR
YMkD3Tf2apzSl39+i6s1Sna96e+WCH1INqJofY24ZVRfcwID2jEzurrMlGtxTjM6
UwfWIhbyS+ZoMbpri13Hz3lN8lvKPiXoiJqceXSzfW0vJS7RJFUDwE1yLaNXrwko
jr2fRKnNS0wwUWptfGiPMtFVlwKBgQDrFvK8OHjuJDlwr/oYtSjPbYvoqo0zab1h
a2wfozRMNAtifh12MEJTmCaALvO6ip0k7KIVRGT6taE5eMOZVv/gkle6S/g2vC7+
uh8s2urjxxLZgoSj1nRCLtA/aby5xYxcK0Wmnnz/0e7u8j4LMzSxzr2YCDTUImCy
WO+gcajYAwKBgBsmiy41k7XtIezGp9OywnbMSkquED34wrF73gHvAOXYulRRl2YZ
xB1A4lx4QEu44ivqPN12lUAoUq7RiQlG2YfQzujDOfn6YRNNCV1zCpKV41yEBPIi
T+0x8tlanI1ZIaL5Dy+kRa+UACBIdTTIQFjdDLW3tXF0djsQiSJ5Wl71AoGBANNy
Zo1ItexsBIYIoggLGVPIkiiJDkuJ9d5jwnKKVoWb4gmKqXEeYunRVf/BO1MzTbhi
Zj5+r9yX9RU+O5/2ElupBOL5ZZ3FkPdn7JZpqQ+KhLfCnw7F2veUJ5aBwk/NETvt
Z84/iuqFpkSg+ZEVU3YCH6FY8DtFWHfRamaDFHyFAoGAIaACJa4qbSKPtK+jj8Sh
EpBAAQS9DIrOitS9+Fq7QgrMK9OvEuuO35+0E92EOb11exBgFk/3mkojseUhOHoH
qSKzafmQlW9BTV6uUtanRIXXkzV2yTipWOUMcQ9pHXJTOJ5wEEbKQN7t8NCO7KLE
2LnP0jnUNuNxPQbjkr4Vw0s=
-----END PRIVATE KEY-----
  • Merchant public key

    The merchant configures the generated public key to the merchant platform

    If an API request is encrypted, the response data will definitely be encrypted. UPay will encrypt the response data using the merchant's public key.

  • Merchant private key: After obtaining the API response data, the merchant decrypts it using the merchant private key to retrieve the original response data

Encryption Algorithm

{
  "alg": "RSA-OAEP-256",
  "enc": "A128GCM",
  "kid": kid,
  "typ": "JOSE",
  "zip": "DEF"
}

Parameter function and description

ParameterAlgorithmRemarks
algRSA-OAEP-256Key management algorithm
encA128GCMEncryption algorithm
typJOSEEncryption type
zipDEFIncreases transmission speed, reduces transmission content size
kidsha256(public_pem).hexdigest()Perform hash fingerprint signature on the public key
For generating a kid, refer to

KID generation example code

  /**
     * Generate KID
     * @param publicKey RSA Public Key
     */
    public String generateKid(RSAPublicKey publicKey) throws NoSuchAlgorithmException {
        byte[] publicKeyBytes = publicKey.getEncoded();
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] hash = digest.digest(publicKeyBytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(hash).toUpperCase();
    }
import hashlib
import base64
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa

def generate_kid(public_key):
    """
    Generate KID (Key ID) from RSA public key
    
    Args:
        public_key: It can be an RSAPublicKey object, a PEM string, or bytes
        
    Returns:
        str: Base64URL encoded SHA-256 hash value (uppercase)
    """
    if isinstance(public_key, str):
        public_key_bytes = public_key.encode('utf-8')
    elif isinstance(public_key, bytes):
        public_key_bytes = public_key
    elif hasattr(public_key, 'public_bytes'):
        public_key_bytes = public_key.public_bytes(
            encoding=serialization.Encoding.DER,
            format=serialization.PublicFormat.SubjectPublicKeyInfo
        )
    else:
        raise ValueError("Unsupported public key type")
    
    digest = hashlib.sha256(public_key_bytes)
    hash_bytes = digest.digest()
    
    kid = base64.urlsafe_b64encode(hash_bytes).decode('utf-8').rstrip('=').upper()
    
    return kid

Encryption/Decryption Example Data

Encryption Example Data

  • Platform Public Key
    -----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn7okKBd9BHTV48GGFbfdJYC53nkQcvBeEGG1HFL3pA43D2n0nbpFd3lppZfMz9ZFmSYu02mtbF0IA8qbxe15ia0ffTRFK6K1jwnluiDADBtUdG+n/aPWnmBhpG6fdHehQ7LKMwq9gYMyqCV5QOZ3f/kIlQT3/NUGpKfxRzGz7vVV86ibEwnI+62F1qAX5UN1e5AX9WhsqjaI+lB9OMmG7va1xnDhGTbTou/sEGRKKYSsZrYxC2H1pzeJG4puV8C+HVWYIBnIM1UtpROw8NTfdMotShkYZqKwBrX84Z4GZqGbh+h0BnzRC7g7Yon0iYv3F9Ee9SRLcNKOrNYdqK3aHQIDAQAB
    -----END PUBLIC KEY-----
    
  • KID(Generated using the merchant's public key) is
    Y7UBL8TJQJY2YR9CDAM6UNXPKPLSM6QD4GMSA3MEPCQ
    
  • Original Request Payload
    {
      "groupNo": "101680210001",
      "email": "[email protected]",
      "globalCode": "+1",
      "phone": "127897890",
      "givenName": "Demo given name",
      "lastName": "Demo last name",
      "company": "Demo company",
      "occupation": "Demo occupation",
      "position": "Demo position",
      "annualIncome": 100.5,
      "workYear": "1",
      "address": {
        "name": "Demo given name",
        "countryId": 12,
        "provinceId": 475,
        "postalCode": "94105",
        "address": "535 Mission St, Floor 12"
      },
      "remark": "This is sample data for adding customers"
    }
    
  • Request Payload
    The data in payload is the original request payload encrypted with the platform public key.
    {
        "payload": "eyJ6aXAiOiJERUYiLCJraWQiOiJZN1VCTDhUSlFKWTJZUjlDREFNNlVOWFBLUExTTTZRRDRHTVNBM01FUENRIiwidHlwIjoiSk9TRSIsImVuYyI6IkExMjhHQ00iLCJhbGciOiJSU0EtT0FFUC0yNTYifQ.iThR8f9aBxnvq8PR-ta-AkD6w-5USqrCrTkISbz8ovTurgCmHasM0NmmlV5N8_Kn5qxJWCqIVd3EkmU9nkjsiO5uLSHPxL_CLmQD1gV79m1AxC41GMpHxGtiXkg-LUCwxE2TM-yLa5xKCjNJRUFPRShkObIq1rWiz_HIppYbw853AIa9PS9vt2p0LWpfthQ5UBaiCbHI5i2pIzaTAff3I8spgz2kJaZamnGEPH3v93rqpv2e2ICL0aYt6rp0wsAIe4dHR1tPwdqQKQA1VFo1VMSNhfxQmJqLZ7WsEJTh-zFO82Og2SYNp_Kop6lsSsPkoIo62p-qC9dgNU75zWe_5w.pVaqC8cS3mgKZ1z7.25Dz8A5LmYMqP_ujf20ZcVSdIf4etmd7Ws2BW6Dd1LvYigkXtEat4VrOhHyJEJEj4ksBuyQRS-uNrYy75MdZAkPSYC9xw4bpgQAgjYoZAurvX8Yy6mTlRV5IS4VcLTP3b7rsMVZOohYdWwVwl35yqGRv-5u6NRiFOKx0jyIvje0sLhae9dicJXrPe7xRaMLcKUCklpSAYQs8X6dk_ZJ7WgEkdhO_JOwyGLu7AtjCnvXRRAQtH9jM0ioXOrCad1d99bsZyuJ5ndF_54z3Dbiv4XPHKX4Z8Rm2Uhs_T84nOEXDwUP0ZUD6XLTgYj685o3qHIfLSmdgmgC-MinFvWYWFCasEIGLS8R7hv4gNPH_X3ulvRZg8kJsfpdaHRr_.lQsBYAPQAWf3AdOraThn5w"
    }
    

Decryption Example Data

  • Original Response Data
    {
        "code": 0,
        "data": {
            "payload": "eyJ6aXAiOiJERUYiLCJraWQiOiI3MzlTTFlCVUZNTVZWTFlIRktKSkIyN0EyTEJWWFhPVUpEU19FT0tXSjVVIiwidHlwIjoiSk9TRSIsImVuYyI6IkExMjhHQ00iLCJhbGciOiJSU0EtT0FFUC0yNTYifQ.ZIpTCVNg8G8IZKdvDOolnRr49oZZjO1WtFAtM3iW8yLy16KZQI8Ete_iksoHF4m8BCXcV5VIe6i2rv-8xHCFKXCk_rl3MwaM1MXPZL7Fm0hN_sZRYEbdWC5kGzIVl8t5mOnwsIIctPgvIa7ZQoLj4fNcMcXXdSpXIm2xtvxDSAeQUXnaP6pgP0-3Y0PG_ULYFDAxiQHYAp-EN5GpPIqDLdIAdyjYmdIoyEDcBrMfu0sbc1O7XZ6TMr0kK5r--CiHdsl36czPoQhDXsP1L4Bmbo0CsRNRLGcwSmZH8xD43BX4OOTubF4TTBDjx08hnE7wxjtElCxhvT2ibg7To9jVyA.zVwjezyC0K3FbJ_J.sm8AzVy7OjAhB0k3HMc.9MQzaH0aX_I4JEYzFrN1aw"
        },
        "msg": "Success",
        "success": true,
        "ts": 1774589644
    }
    
  • Merchant Private Key
    -----BEGIN PRIVATE KEY-----
    MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDavr2asOh+I08g
    Ao1FXm8F5UGMSetg+Ap/wsoMr6AuEut6m5Nz6DGUm6sf+BjCBHAPwMvmuZ1xdFky
    lf4vyDHKzPMPSoVacGi6Y3VEfHaSSiTW+Auv90GUlHBU9ltlPr2b8HyQZZHwlugF
    U5YnDGcNfpZpEPJZVraq7UqiBCribTbyAlZmVXOzRtITFCpJblmJQUbInKCNNrZc
    Aqpx5J7Eei/yNHrqame/S8nLr7hgkrfi3ZXPI10Sqx5MTeylmRIkLPPE1XvEizre
    79ZN6conUFZqv7+sDrcD7/Qu3YEH9R/+KrQ7Aabi3Z3Q9/U7uHKVdc/rkEa6da+7
    RBYKw2jFAgMBAAECggEANwQQJe7mmosA5JkftNm6bK4rXUBeLeZUpat1K9mkHNJv
    XUfxvw4gIjNAx+qbN3jsQloILoByo81SfdGRu6zLMSl43Fiuz39EJ9TJ8q6nF8YE
    G/kI33n9iYQH+KZ5eC5ee/DxM1QIb41Uz7olIq8Q4Cj9ZXF4spWHndfOlI8dxhKg
    3HLx90yW6/2D8teI2KoyDVd3FH/3dMsibAFwYbAlvfZzFuH0stOUyDOxt1CdWl/N
    l4zTouGz82A+x8OPKf52E9W0iw+PnX/XOjDVpU39d21zhSmN3IjgfrW0vbbixD8F
    1Xeusy9DYwNY60q2bo0xckdz8cisr59IT9iT+xfiBQKBgQDuM59iG56EljyEtwZR
    YMkD3Tf2apzSl39+i6s1Sna96e+WCH1INqJofY24ZVRfcwID2jEzurrMlGtxTjM6
    UwfWIhbyS+ZoMbpri13Hz3lN8lvKPiXoiJqceXSzfW0vJS7RJFUDwE1yLaNXrwko
    jr2fRKnNS0wwUWptfGiPMtFVlwKBgQDrFvK8OHjuJDlwr/oYtSjPbYvoqo0zab1h
    a2wfozRMNAtifh12MEJTmCaALvO6ip0k7KIVRGT6taE5eMOZVv/gkle6S/g2vC7+
    uh8s2urjxxLZgoSj1nRCLtA/aby5xYxcK0Wmnnz/0e7u8j4LMzSxzr2YCDTUImCy
    WO+gcajYAwKBgBsmiy41k7XtIezGp9OywnbMSkquED34wrF73gHvAOXYulRRl2YZ
    xB1A4lx4QEu44ivqPN12lUAoUq7RiQlG2YfQzujDOfn6YRNNCV1zCpKV41yEBPIi
    T+0x8tlanI1ZIaL5Dy+kRa+UACBIdTTIQFjdDLW3tXF0djsQiSJ5Wl71AoGBANNy
    Zo1ItexsBIYIoggLGVPIkiiJDkuJ9d5jwnKKVoWb4gmKqXEeYunRVf/BO1MzTbhi
    Zj5+r9yX9RU+O5/2ElupBOL5ZZ3FkPdn7JZpqQ+KhLfCnw7F2veUJ5aBwk/NETvt
    Z84/iuqFpkSg+ZEVU3YCH6FY8DtFWHfRamaDFHyFAoGAIaACJa4qbSKPtK+jj8Sh
    EpBAAQS9DIrOitS9+Fq7QgrMK9OvEuuO35+0E92EOb11exBgFk/3mkojseUhOHoH
    qSKzafmQlW9BTV6uUtanRIXXkzV2yTipWOUMcQ9pHXJTOJ5wEEbKQN7t8NCO7KLE
    2LnP0jnUNuNxPQbjkr4Vw0s=
    -----END PRIVATE KEY-----
    
  • Result of decrypting the payload in the original response data using the merchant's private key
    101680110004
    

Encryption/Decryption Sample Code

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.RSADecrypter;
import com.nimbusds.jose.crypto.RSAEncrypter;

import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class JWECrypto {

    private static final JWEAlgorithm JWE_ALG = JWEAlgorithm.RSA_OAEP_256;
    private static final EncryptionMethod ENC_METHOD = EncryptionMethod.A128GCM;

    private RSAPublicKey publicKey;
    private RSAPrivateKey privateKey;

    public JWECrypto(String publicKeyPEM , String privateKeyPEM) throws Exception {
        if (publicKeyPEM != null) {
            this.publicKey = loadPublicKey(publicKeyPEM);
        }
        if (privateKeyPEM != null) {
            this.privateKey = loadPrivateKey(privateKeyPEM);
        }
    }

    /**
     * Encryption
     */
    public String encrypt(String payload) throws Exception {
        String kid = generateKid(publicKey);
        JWEHeader header = new JWEHeader.Builder(JWE_ALG , ENC_METHOD)
                .type(JOSEObjectType.JOSE)
                .keyID(kid)
                .compressionAlgorithm(CompressionAlgorithm.DEF)
                .build();
        JWEObject jweObject = new JWEObject(header , new Payload(payload));
        RSAEncrypter rsaEncrypter = new RSAEncrypter(publicKey);
        jweObject.encrypt(rsaEncrypter);
        return jweObject.serialize();
    }

    /**
     * Decryption
     */
    public String decrypt(String jweString) throws Exception {
        JWEObject jweObject = JWEObject.parse(jweString);
        RSADecrypter rsaDecrypter = new RSADecrypter(privateKey);
        jweObject.decrypt(rsaDecrypter);
        return jweObject.getPayload().toString();
    }

    private RSAPublicKey loadPublicKey(String publicKeyPEM) throws Exception {
        String publicKeyPEMFormatted = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----" , "")
                .replace("-----END PUBLIC KEY-----" , "")
                .replaceAll("\\s" , "");

        byte[] encoded = Base64.getDecoder().decode(publicKeyPEMFormatted);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
        return (RSAPublicKey) keyFactory.generatePublic(keySpec);
    }

    private RSAPrivateKey loadPrivateKey(String privateKeyPEM) throws Exception {
        String privateKeyPEMFormatted = privateKeyPEM.replace("-----BEGIN PRIVATE KEY-----" , "")
                .replace("-----END PRIVATE KEY-----" , "")
                .replaceAll("\\s" , "");

        byte[] encoded = Base64.getDecoder().decode(privateKeyPEMFormatted);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
        return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
    }

    /**
     * Generated KID 
     * @param publicKey 
     */
    public String generateKid(RSAPublicKey publicKey) throws NoSuchAlgorithmException {
        byte[] publicKeyBytes = publicKey.getEncoded();
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] hash = digest.digest(publicKeyBytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(hash).toUpperCase();
    }
     
}

import base64
import hashlib
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from jwcrypto import jwe, jwk
from jwcrypto.common import json_encode
import json


class JWECrypto:
    JWE_ALG = "RSA-OAEP-256"
    ENC_METHOD = "A128GCM"

    def __init__(self, public_key_pem=None, private_key_pem=None):
        """
        Initialize JWE encryption/decryption handler
        
        Args:
            public_key_pem: Public key string in PEM format
            private_key_pem: Private key string in PEM format
        """
        self.public_key = None
        self.private_key = None
        
        if public_key_pem:
            self.public_key = self._load_public_key(public_key_pem)
        if private_key_pem:
            self.private_key = self._load_private_key(private_key_pem)

    def _load_public_key(self, public_key_pem):
        """Load public key"""
        try:
            # Clean PEM format
            public_key_pem = public_key_pem.strip()
            if not public_key_pem.startswith('-----BEGIN PUBLIC KEY-----'):
                # If it's already in plain base64 format, add PEM header and footer
                public_key_pem = self._format_pem(public_key_pem, "PUBLIC KEY")
            
            # Load public key
            public_key = serialization.load_pem_public_key(
                public_key_pem.encode('utf-8'),
                backend=default_backend()
            )
            return public_key
        except Exception as e:
            raise Exception(f"Failed to load public key: {e}")

    def _load_private_key(self, private_key_pem):
        """Load private key"""
        try:
            # Clean PEM format
            private_key_pem = private_key_pem.strip()
            if not private_key_pem.startswith('-----BEGIN PRIVATE KEY-----'):
                # If it's already in plain base64 format, add PEM header and footer
                private_key_pem = self._format_pem(private_key_pem, "PRIVATE KEY")
            
            # Load private key
            private_key = serialization.load_pem_private_key(
                private_key_pem.encode('utf-8'),
                password=None,
                backend=default_backend()
            )
            return private_key
        except Exception as e:
            raise Exception(f"Failed to load private key: {e}")

    def _format_pem(self, key_str, key_type):
        """Format PEM string"""
        # Remove all whitespace characters
        key_str = ''.join(key_str.split())
        # Add PEM header and footer
        pem = f"-----BEGIN {key_type}-----\n"
        # Wrap every 64 characters
        for i in range(0, len(key_str), 64):
            pem += key_str[i:i+64] + "\n"
        pem += f"-----END {key_type}-----"
        return pem

    def _create_jwk_from_public_key(self):
        """Create JWK object from public key"""
        # Export public key to PEM format
        public_pem = self.public_key.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo
        )
        
        # Create JWK
        jwk_obj = jwk.JWK.from_pem(public_pem)
        return jwk_obj

    def _create_jwk_from_private_key(self):
        """Create JWK object from private key"""
        # Export private key to PEM format
        private_pem = self.private_key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=serialization.NoEncryption()
        )
        
        # Create JWK
        jwk_obj = jwk.JWK.from_pem(private_pem)
        return jwk_obj

    def encrypt(self, payload):
        """
        Encrypt data
        
        Args:
            payload: String to encrypt
            
        Returns:
            JWE string
        """
        try:
            # Generate KID
            kid = self.generate_kid(self.public_key)
            
            # Create JWK
            public_jwk = self._create_jwk_from_public_key()
            
            # Create JWE object
            jwe_token = jwe.JWE(
                json.dumps(payload) if isinstance(payload, dict) else payload,
                json_encode({
                    "alg": self.JWE_ALG,
                    "enc": self.ENC_METHOD,
                    "kid": kid,
                    "typ": "JOSE",
                    "zip": "DEF"  # Compression algorithm
                })
            )
            
            # Encrypt
            jwe_token.add_recipient(public_jwk)
            encrypted = jwe_token.serialize(compact=True)
            
            return encrypted
            
        except Exception as e:
            raise Exception(f"Encryption failed: {e}")

    def decrypt(self, jwe_string):
        """
        Decrypt data
        
        Args:
            jwe_string: JWE string
            
        Returns:
            Decrypted string
        """
        try:
            # Create JWK
            private_jwk = self._create_jwk_from_private_key()
            
            # Parse and decrypt JWE
            jwe_token = jwe.JWE()
            jwe_token.deserialize(jwe_string, key=private_jwk)
            
            # Get payload
            payload = jwe_token.payload
            return payload.decode('utf-8') if isinstance(payload, bytes) else str(payload)
            
        except Exception as e:
            raise Exception(f"Decryption failed: {e}")

    def generate_kid(self, public_key):
        """
        Generate KID (Key ID)
        
        Args:
            public_key: RSA public key object
            
        Returns:
            KID string
        """
        # Get DER encoding of public key
        public_der = public_key.public_bytes(
            encoding=serialization.Encoding.DER,
            format=serialization.PublicFormat.SubjectPublicKeyInfo
        )
        
        # Calculate SHA-256 hash
        sha256_hash = hashlib.sha256(public_der).digest()
        
        # Base64 URL encoding (without padding)
        kid = base64.urlsafe_b64encode(sha256_hash).decode('utf-8').rstrip('=')
        
        return kid.upper()


# Usage example
if __name__ == "__main__":
    # Example keys (please use your own keys)
    # Note: The keys here are for demonstration only. Use real key pairs in production.
    
    # Generate test key pair
    from cryptography.hazmat.primitives.asymmetric import rsa
    from cryptography.hazmat.primitives import serialization
    
    # Generate RSA key pair
    private_key_obj = rsa.generate_private_key(
        public_exponent=65537,
        key_size=2048,
        backend=default_backend()
    )
    public_key_obj = private_key_obj.public_key()
    
    # Export to PEM format
    private_pem = private_key_obj.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption()
    ).decode('utf-8')
    
    public_pem = public_key_obj.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo
    ).decode('utf-8')
    
    print("Generated Public Key PEM:")
    print(public_pem)
    print("\nGenerated Private Key PEM:")
    print(private_pem)
    
    # Create encryption/decryption handler
    jwe_crypto = JWECrypto(public_pem, private_pem)
    
    # Test encryption and decryption
    original_payload = "Hello, this is a test message!"
    print(f"\nOriginal data: {original_payload}")
    
    # Encrypt
    encrypted = jwe_crypto.encrypt(original_payload)
    print(f"Encrypted result: {encrypted}")
    
    # Decrypt
    decrypted = jwe_crypto.decrypt(encrypted)
    print(f"Decrypted result: {decrypted}")
    
    # Verify
    assert original_payload == decrypted, "Encryption/decryption verification failed"
    print("\nEncryption/decryption verification successful!")

Testing Website

https://dinochiesa.github.io/jwt/