Sign & Data encryption

Authentication Signature&Data Encryption

Authentication Signature

  1. Place the agent ID in the Header.
  2. Sort the request parameters in alphabetical order (a to z) and concatenate them using the &x=y logic.
  3. Split "request method + URL + time + requestid + request parameters" with the "|" symbol and combine it into a string.
  4. Sign the concatenated string using hmac.sha256.
  5. Encode the signed data in base64.

📘

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

ParameterRequiredExample value备注
URLStringTrue/api/v1/test
METHODStringTruePOST
timestampStringTrue1701024967796Milliseconds level is sufficient. Verify a request timeout of 60 seconds.
requestidStringTruea0a47bf4-0507-4f04-894b-047d15bfeeffCustom, non repeatable
paramsStringTruekey1=value1&key2=value2

Demo

URL: /api/v1/test
METHOD: POST
timestamp:1701024967796
requestid: a0a47bf4-0507-4f04-894b-047d15bfeeff
params: key1=value1&key2=value2

// request method + URL + time + requestid + request parameters
POST|/api/v1/test|1701024967796|a0a47bf4-0507-4f04-894b-047d15bfeeff|key1=value1&key2=value2

If the Sign value is "a3e43e0a51ad9bf25b3fe7dbc40b88e25f1921a1a0f23a912c1c13ac7219c6ca", After Base64, it is "f+B4V5buwFnDLNmMlniNPhswh15G1lwFogGjX6Yrf2I=", Put the "o+Q+ClGtm/JbP+fbxAuI4l8ZIaGg8jqRLBwTrHIZxso=" in the request header X-URA-SIGN

Demo Code

import hmac
import base64
from hashlib import sha256


def sign_data_sha256(data, secret):
    data = data.encode('utf8')
    secret = secret.encode('utf8')
    signature = base64.b64encode(hmac.new(secret, data, digestmod=sha256).digest())
    return signature.decode()
      

if __name__ == '__main__':
    url = "/api/v1/test"
    method = "POST"
    requestid = "a0a47bf4-0507-4f04-894b-047d15bfeeff"
    params = {"key1": "value1", "key2": "value2"}
    time = "1701024967796"
    unsign_data = "|".join([method, url, time, requestid, "&".join(["{k}={v}".format(k=k, v=params[k]) for k in sorted(params)])])
    
    api_secret = "0a227672c20f48989b5fcdb4c00038ff"
    sign_data = sign_data_sha256(unsign_data, api_secret)
    print("sign data is : ", sign_data)
    # print to o+Q+ClGtm/JbP+fbxAuI4l8ZIaGg8jqRLBwTrHIZxso=
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

public class HMACSHA256Signer {

    public static String signDataSHA256(String data, String secret) throws Exception {
        Mac sha256HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256");
        sha256HMAC.init(secretKey);

        byte[] hash = sha256HMAC.doFinal(data.getBytes("UTF-8"));
        return Base64.getEncoder().encodeToString(hash);
    }

    public static void main(String[] args) throws Exception {
        String url = "/api/v1/test";
        String method = "POST";
        String requestid = "a0a47bf4-0507-4f04-894b-047d15bfeeff";
        Map<String, String> params = new TreeMap<>();
        params.put("key1", "value1");
        params.put("key2", "value2");
        String time = "1701024967796";

        String unsignData = String.join("|",
                method,
                url,
                time,
                requestid,
                params.entrySet().stream()
                        .map(entry -> entry.getKey() + "=" + entry.getValue())
                        .collect(Collectors.joining("&"))
        );

        String apiSecret = "0a227672c20f48989b5fcdb4c00038ff";
        String signData = signDataSHA256(unsignData, apiSecret);
        System.out.println("sign data is : " + signData);
    }
}

Data encryption

Encrypt basic information

  • Process:
    Encryption is divided into two pairs of public and private keys, one is the platform key and the other is the merchant key. The platform uses the merchant's public key for encryption and the merchant's private key for decryption. Similarly, the merchant uses the platform's public key for encryption and the platform uses its own private key for decryption.
  • Algorithm: JWE RSA-OAEP-256
  • encrypt: A128GCM

RSA generation rule: RSA 2048 bit, can be directly generated using JWK, or generate RSA 2048 public and private keys by oneself

📘

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

Encryption parameters

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

How to calculate KID by performing Sha256 on the PEM format of the public key to obtain the fingerprint.

Parameter function

ParameterExplainAlgorithmRemark
algKey management algorithmRSA-OAEP-256
encEncryption algorithmA128GCM
typEncryption typeJOSE
zipCompression typeDEFIncrease transmission speed and reduce transmission content size
kidKey pairs usedsha256(public_pem).hexdigest()Hash fingerprint signature on the public key, telling the other party which public key was used to sign the set of data. This parameter is used to address the issue of finding a suitable public/private key pair for encryption and decryption when one key encounters problems but multiple public keys exist.

Request

  • Business Private Key: Used to decrypt data in response to the platform, ensuring that the data can only be decrypted in the business.
  • Platform public key: Used to encrypt data requested or responded to by the platform, which can only be decrypted by the business party.

Response

  • Business Public Key: Used to encrypt data requested or responded to by the platform, which can only be decrypted by the business party.
  • Platform private key: Used to decrypt data requested from the platform, ensuring that the data can only be decrypted on the platform.

Demo

字段内容
请求报文{"payload": "Encrypted string" }
Header{ "alg": "RSA-OAEP-256", "enc": "A128GCM", "kid": "4B7AC601795594F8EB3C50F07E5FE4569E9B04D95A2C83F2B680BE0AA89A9E0B", "typ": "JWE", "zip": "DEF" }
原始报文{ "id": "123", "hello": "world", "time": 1723976484, "tag": "UPay Encryption and decryption" }
加密报文eyJhbGciOiAiUlNBLU9BRVAtMjU2IiwgImVuYyI6ICJBMTI4R0NNIiwgImtpZCI6ICI0QjdBQzYwMTc5NTU5NEY4RUIzQzUwRjA3RTVGRTQ1NjlFOUIwNEQ5NUEyQzgzRjJCNjgwQkUwQUE4OUE5RTBCIiwgInR5cCI6ICJKV0UiLCAiemlwIjogIkRFRiJ9.vBFtYk_yN8MG_iYGA-xpU_eBnswO3e9jXimmWuuIOh54yO9umpEttC-9Ns3beJ8QKx8U37ZilRIBQZMLnBAGPvAl7v3hGx6yzSKBp3sCpbV5Ueiv4Q7CudLDbaOPk0VkGJXzSe4UidKWDs_Qzk_q7GNVHwZNYveiY28wi4hLsRc5Jzb1CQ-oLo-NnI66zs-QttPSarA-9aP_TSOSiPStNFGgRK_fP-RBwNBmx6ZrKS6WjPqc8QjaykrEgxL8FgaSgi-5C9sss8uty6kJWb6R9zecbpMpQUHvRRUefVFQqI7hcruvv2m8Yol5HHWRRGDYBNS3QFi3NPbodQT3VjxWig.Xi_RKtZ6KMmRStA9.8zXIO3mzF1XfHhUkfxqG5W4aprGR68gB_0QyyHU2nQkjBzIPHDmVWryB8-Ie_zySCOT7B-4ZqEb-n3T1untzMGD4eBaJ_87Apa8AdWBm.Sgyj_8opMH_Gj_C-EWvzhg
公钥-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvcnAzEJgN3tcyed3SayO o+2go3LrYxuNRWiYFwkDPMmcpgP4cyeasY7XjWr7dUPlwjmUf5DfG0UeasQlVu16 c/Ab8Qkj4ypopJ4hXwZUb45KVynHjcNsZVAC4fDOFBWCKSn2MJs28N4D5tZnvY9Z BQIQLMkyc0tUGl6VbSb+EOP5mZuK5lZ9yBAZioQl49rXTHQpvzz1AISmperUpsU9 oYJch5/sWS2+rkinI6cC3LZkGndSl5ruUQ1FAyLhd71JX++zVuP6EayByGnwKSg8 7RqnaSKSzPnVq4bClWE+hNlVrkuSlh1VtBXIK14qVpyNXyW01I075kbLm7zDr9LW /wIDAQAB -----END PUBLIC KEY-----
私钥-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC9ycDMQmA3e1zJ 53dJrI6j7aCjcutjG41FaJgXCQM8yZymA/hzJ5qxjteNavt1Q+XCOZR/kN8bRR5q xCVW7Xpz8BvxCSPjKmikniFfBlRvjkpXKceNw2xlUALh8M4UFYIpKfYwmzbw3gPm 1me9j1kFAhAsyTJzS1QaXpVtJv4Q4/mZm4rmVn3IEBmKhCXj2tdMdCm/PPUAhKal 6tSmxT2hglyHn+xZLb6uSKcjpwLctmQad1KXmu5RDUUDIuF3vUlf77NW4/oRrIHI afApKDztGqdpIpLM+dWrhsKVYT6E2VWuS5KWHVW0FcgrXipWnI1fJbTUjTvmRsub vMOv0tb/AgMBAAECggEABT4L7yMhkOwaCKe191IRZmBbe2obnaFhXcVIWxhQIxSM mQIH2M0f3g2awvxQ6tgIrNkLUuEwnrUM5IptQyvwBV1EQ9/ql7DXeeiZ2X24GlwV /7dd49ATRUR76a14USBCvxo6kdglEsDAGA/43CxtVB8HopJlummsp3C0LSv18sBx h8/TtUa0ttG8UodTtBdAbIwolb55lu92XV0mGHpOc9ZsgLlyn2C7o+jlujNqOhi3 YxKCkhL2jzl88qZ4mmRPGPYQQwTo1Xzd4brixwZ1Fyag2kXziE6+v9PX8s+yFsfW 3E/0ye16tfa0/qAx/4oGr9Ox4e7wXLLiN2QpR8UkEQKBgQD4W1r9y3/uKPDne/UR srt0oZQOG9OqouVNgIwzZYw8WG6fu9h+RuihRpLLLqBwCDCiPo17xyHkPufonntJ 4FkfzvL9RV2/d7FERIOPy9umsoFhv1cqD94zlf96hNj5EK1VZpHGhhZ1MCuiRr8W J/tPiCMMSOxVus3SRPw9M/IebwKBgQDDoPjR1WJb1i23uCQm9uvKXl3AgJ9ltfHA JuRR2SO5XxOkZ5HxG+QSAkd9Og4kQAN6MQ9HMh1sR8xa75ck9G5YgmAGBtzRr68L RpcWvT1MNN81hkOrq3pvkrLkepSyz174EIV3m3aTOXaqSe0TOtPN/mU57NxWgyFz QyrubvcYcQKBgQCi2VxfwHlhU+0rDIfUlAsA3hYz7iEr6WZMHHdSGEsNIrte+BBs NNjDL4B/xFIlQ+mH6VZijF93x5vPV2PmPDqUdeG1Gy/upXBSIE7YEkc0FiZqTsm7 5e3Ai6Ga19Nh9YKC1h/OOgwtyWlDHfqcb5kFPHIm0wZM7JGbR+rRwucG9wKBgFc7 JRgz5Rr8QwCD/KLfQb7IG1fmQq1Q1w6OiQqyH0reSVzqzdnMTQN2vrfpNjsVzDcl PyHJB/OrhEUcRrI0yCtxXy3wy3A4vX3yqRZ7paSggtNUHkKjQQIVUO2udzXQYBGP FGSEDviRDAfc1PjZJ27YO/z7UDjHWhgSYEdXdZkRAoGAPpHataBL9NOHZG4zSmCZ jw9yXHTe9xuZl2mkYFgMVzcoGvMjS4PFWDncpiDaVef9rvkY1SlmkMjejOb4KxyI eUkVcbUjvHQ786s4Ul+kdPJehx5B9Av0QhE1D1TVXPUxkx5Xa7usYW3HR0CmeN/1 IFK8O6Hrcn4W90kIgq86hng= -----END PRIVATE KEY-----

Demo Code

from jwcrypto import jwk, jwe
import json
from hashlib import sha256


class JWECrypto(object):
    header = {
        'alg': 'RSA-OAEP-256',
        'enc': 'A128GCM',
        'kid': None,
        "typ": "JWE",
        "zip": "DEF"
    }

    def __init__(self, public_key=None, private_key=None):
        if private_key and public_key:
            self.private_key = jwk.JWK.from_pem(private_key.encode('utf-8'))
            self.public_key = jwk.JWK.from_pem(public_key.encode('utf-8'))
        elif private_key and public_key is None:
            self.private_key = jwk.JWK.from_pem(private_key.encode('utf-8'))
            self.public_key = self.private_key.public()
        elif public_key and private_key is None:
            self.private_key = None
            self.public_key = jwk.JWK.from_pem(public_key.encode('utf-8'))
        elif public_key is None and private_key is None:
            # 生产上不要用,这里只是为了 Demo 而已,生产上会千万数据异常。因为 RSA Key 不可控。
            self.private_key = jwk.JWK(generate='RSA')
            self.public_key = self.private_key.export_public()
            self.public_key = self.private_key.public()

    def encrypt(self, payload: dict):
        header = self.header.copy()
        kid = sha256(self.public_key.export_to_pem()).hexdigest().upper()
        header['kid'] = kid

        jwetoken = jwe.JWE(
            json.dumps(payload),
            json.dumps(header)
                           )
        jwetoken.add_recipient(self.public_key)

        encrypt_token = jwetoken.serialize(True)

        return encrypt_token

    def decrypt(self, encrypt_token: str):
        jwetoken = jwe.JWE()
        jwetoken.deserialize(encrypt_token, key=self.private_key)
        decrypted_payload = jwetoken.payload
        payload_json = json.loads(decrypted_payload)
        return payload_json


if __name__ == '__main__':
    private_key = """
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC9ycDMQmA3e1zJ
53dJrI6j7aCjcutjG41FaJgXCQM8yZymA/hzJ5qxjteNavt1Q+XCOZR/kN8bRR5q
xCVW7Xpz8BvxCSPjKmikniFfBlRvjkpXKceNw2xlUALh8M4UFYIpKfYwmzbw3gPm
1me9j1kFAhAsyTJzS1QaXpVtJv4Q4/mZm4rmVn3IEBmKhCXj2tdMdCm/PPUAhKal
6tSmxT2hglyHn+xZLb6uSKcjpwLctmQad1KXmu5RDUUDIuF3vUlf77NW4/oRrIHI
afApKDztGqdpIpLM+dWrhsKVYT6E2VWuS5KWHVW0FcgrXipWnI1fJbTUjTvmRsub
vMOv0tb/AgMBAAECggEABT4L7yMhkOwaCKe191IRZmBbe2obnaFhXcVIWxhQIxSM
mQIH2M0f3g2awvxQ6tgIrNkLUuEwnrUM5IptQyvwBV1EQ9/ql7DXeeiZ2X24GlwV
/7dd49ATRUR76a14USBCvxo6kdglEsDAGA/43CxtVB8HopJlummsp3C0LSv18sBx
h8/TtUa0ttG8UodTtBdAbIwolb55lu92XV0mGHpOc9ZsgLlyn2C7o+jlujNqOhi3
YxKCkhL2jzl88qZ4mmRPGPYQQwTo1Xzd4brixwZ1Fyag2kXziE6+v9PX8s+yFsfW
3E/0ye16tfa0/qAx/4oGr9Ox4e7wXLLiN2QpR8UkEQKBgQD4W1r9y3/uKPDne/UR
srt0oZQOG9OqouVNgIwzZYw8WG6fu9h+RuihRpLLLqBwCDCiPo17xyHkPufonntJ
4FkfzvL9RV2/d7FERIOPy9umsoFhv1cqD94zlf96hNj5EK1VZpHGhhZ1MCuiRr8W
J/tPiCMMSOxVus3SRPw9M/IebwKBgQDDoPjR1WJb1i23uCQm9uvKXl3AgJ9ltfHA
JuRR2SO5XxOkZ5HxG+QSAkd9Og4kQAN6MQ9HMh1sR8xa75ck9G5YgmAGBtzRr68L
RpcWvT1MNN81hkOrq3pvkrLkepSyz174EIV3m3aTOXaqSe0TOtPN/mU57NxWgyFz
QyrubvcYcQKBgQCi2VxfwHlhU+0rDIfUlAsA3hYz7iEr6WZMHHdSGEsNIrte+BBs
NNjDL4B/xFIlQ+mH6VZijF93x5vPV2PmPDqUdeG1Gy/upXBSIE7YEkc0FiZqTsm7
5e3Ai6Ga19Nh9YKC1h/OOgwtyWlDHfqcb5kFPHIm0wZM7JGbR+rRwucG9wKBgFc7
JRgz5Rr8QwCD/KLfQb7IG1fmQq1Q1w6OiQqyH0reSVzqzdnMTQN2vrfpNjsVzDcl
PyHJB/OrhEUcRrI0yCtxXy3wy3A4vX3yqRZ7paSggtNUHkKjQQIVUO2udzXQYBGP
FGSEDviRDAfc1PjZJ27YO/z7UDjHWhgSYEdXdZkRAoGAPpHataBL9NOHZG4zSmCZ
jw9yXHTe9xuZl2mkYFgMVzcoGvMjS4PFWDncpiDaVef9rvkY1SlmkMjejOb4KxyI
eUkVcbUjvHQ786s4Ul+kdPJehx5B9Av0QhE1D1TVXPUxkx5Xa7usYW3HR0CmeN/1
IFK8O6Hrcn4W90kIgq86hng=
-----END PRIVATE KEY-----
        """
    public_key = """
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvcnAzEJgN3tcyed3SayO
o+2go3LrYxuNRWiYFwkDPMmcpgP4cyeasY7XjWr7dUPlwjmUf5DfG0UeasQlVu16
c/Ab8Qkj4ypopJ4hXwZUb45KVynHjcNsZVAC4fDOFBWCKSn2MJs28N4D5tZnvY9Z
BQIQLMkyc0tUGl6VbSb+EOP5mZuK5lZ9yBAZioQl49rXTHQpvzz1AISmperUpsU9
oYJch5/sWS2+rkinI6cC3LZkGndSl5ruUQ1FAyLhd71JX++zVuP6EayByGnwKSg8
7RqnaSKSzPnVq4bClWE+hNlVrkuSlh1VtBXIK14qVpyNXyW01I075kbLm7zDr9LW
/wIDAQAB
-----END PUBLIC KEY-----
        """
    payload = {
        "id": "123",
        "hello": "world",
        "time": 1723976484,
        "tag": "UPay Encryption and decryption"
    }
    # No Key
    jwe_crypto = JWECrypto()
    print(jwe_crypto.private_key)
    print(jwe_crypto.public_key)

    encrypt_token = jwe_crypto.encrypt(payload)
    print(encrypt_token)
    decrypted_payload = jwe_crypto.decrypt(encrypt_token)
    print(decrypted_payload)

    # Just public Key, only Sign
    jwe_crypto = JWECrypto(public_key=public_key)
    encrypt_token = jwe_crypto.encrypt(payload)
    print(encrypt_token)

    # Just private key, only design
    jwe_crypto = JWECrypto(private_key=private_key)
    # design last encrypt token
    decrypted_payload = jwe_crypto.decrypt(encrypt_token)
    print(decrypted_payload)

    # have both of key of rsa
    jwe_crypto = JWECrypto(public_key=public_key, private_key=private_key)
    encrypt_token = jwe_crypto.encrypt(payload)
    print(encrypt_token)
    decrypted_payload = jwe_crypto.decrypt(encrypt_token)
    print(decrypted_payload)
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jose.jwk.*;
import com.nimbusds.jose.util.Base64URL;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;

import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.PrivateKey;
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);
        }
    }

    public String encrypt(String payload) throws Exception {
        String kid = generateKid(publicKey);

        JWEHeader header = new JWEHeader.Builder(JWE_ALG, ENC_METHOD)
                .type(JOSEObjectType.JWE)
                .keyID(kid)
                .compressionAlgorithm(CompressionAlgorithm.DEF)
                .build();

        JWEObject jweObject = new JWEObject(header, new Payload(payload));
        RSAEncrypter encrypter = new RSAEncrypter(publicKey);
        jweObject.encrypt(encrypter);

        return jweObject.serialize();
    }

    public String decrypt(String jweString) throws Exception {
        JWEObject jweObject = JWEObject.parse(jweString);
        RSADecrypter decrypter = new RSADecrypter(privateKey);
        jweObject.decrypt(decrypter);

        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);
    }

    private 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();
    }

    public static void main(String[] args) throws Exception {
        String privateKeyPEM = """
        -----BEGIN PRIVATE KEY-----
        MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC9ycDMQmA3e1zJ
        53dJrI6j7aCjcutjG41FaJgXCQM8yZymA/hzJ5qxjteNavt1Q+XCOZR/kN8bRR5q
        xCVW7Xpz8BvxCSPjKmikniFfBlRvjkpXKceNw2xlUALh8M4UFYIpKfYwmzbw3gPm
        1me9j1kFAhAsyTJzS1QaXpVtJv4Q4/mZm4rmVn3IEBmKhCXj2tdMdCm/PPUAhKal
        6tSmxT2hglyHn+xZLb6uSKcjpwLctmQad1KXmu5RDUUDIuF3vUlf77NW4/oRrIHI
        afApKDztGqdpIpLM+dWrhsKVYT6E2VWuS5KWHVW0FcgrXipWnI1fJbTUjTvmRsub
        vMOv0tb/AgMBAAECggEABT4L7yMhkOwaCKe191IRZmBbe2obnaFhXcVIWxhQIxSM
        mQIH2M0f3g2awvxQ6tgIrNkLUuEwnrUM5IptQyvwBV1EQ9/ql7DXeeiZ2X24GlwV
        /7dd49ATRUR76a14USBCvxo6kdglEsDAGA/43CxtVB8HopJlummsp3C0LSv18sBx
        h8/TtUa0ttG8UodTtBdAbIwolb55lu92XV0mGHpOc9ZsgLlyn2C7o+jlujNqOhi3
        YxKCkhL2jzl88qZ4mmRPGPYQQwTo1Xzd4brixwZ1Fyag2kXziE6+v9PX8s+yFsfW
        3E/0ye16tfa0/qAx/4oGr9Ox4e7wXLLiN2QpR8UkEQKBgQD4W1r9y3/uKPDne/UR
        srt0oZQOG9OqouVNgIwzZYw8WG6fu9h+RuihRpLLLqBwCDCiPo17xyHkPufonntJ
        4FkfzvL9RV2/d7FERIOPy9umsoFhv1cqD94zlf96hNj5EK1VZpHGhhZ1MCuiRr8W
        J/tPiCMMSOxVus3SRPw9M/IebwKBgQDDoPjR1WJb1i23uCQm9uvKXl3AgJ9ltfHA
        JuRR2SO5XxOkZ5HxG+QSAkd9Og4kQAN6MQ9HMh1sR8xa75ck9G5YgmAGBtzRr68L
        RpcWvT1MNN81hkOrq3pvkrLkepSyz174EIV3m3aTOXaqSe0TOtPN/mU57NxWgyFz
        QyrubvcYcQKBgQCi2VxfwHlhU+0rDIfUlAsA3hYz7iEr6WZMHHdSGEsNIrte+BBs
        NNjDL4B/xFIlQ+mH6VZijF93x5vPV2PmPDqUdeG1Gy/upXBSIE7YEkc0FiZqTsm7
        5e3Ai6Ga19Nh9YKC1h/OOgwtyWlDHfqcb5kFPHIm0wZM7JGbR+rRwucG9wKBgFc7
        JRgz5Rr8QwCD/KLfQb7IG1fmQq1Q1w6OiQqyH0reSVzqzdnMTQN2vrfpNjsVzDcl
        PyHJB/OrhEUcRrI0yCtxXy3wy3A4vX3yqRZ7paSggtNUHkKjQQIVUO2udzXQYBGP
        FGSEDviRDAfc1PjZJ27YO/z7UDjHWhgSYEdXdZkRAoGAPpHataBL9NOHZG4zSmCZ
        jw9yXHTe9xuZl2mkYFgMVzcoGvMjS4PFWDncpiDaVef9rvkY1SlmkMjejOb4KxyI
        eUkVcbUjvHQ786s4Ul+kdPJehx5B9Av0QhE1D1TVXPUxkx5Xa7usYW3HR0CmeN/1
        IFK8O6Hrcn4W90kIgq86hng=
        -----END PRIVATE KEY-----
        """;

        String publicKeyPEM = """
        -----BEGIN PUBLIC KEY-----
        MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvcnAzEJgN3tcyed3SayO
        o+2go3LrYxuNRWiYFwkDPMmcpgP4cyeasY7XjWr7dUPlwjmUf5DfG0UeasQlVu16
        c/Ab8Qkj4ypopJ4hXwZUb45KVynHjcNsZVAC4fDOFBWCKSn2MJs28N4D5tZnvY9Z
        BQIQLMkyc0tUGl6VbSb+EOP5mZuK5lZ9yBAZioQl49rXTHQpvzz1AISmperUpsU9
        oYJch5/sWS2+rkinI6cC3LZkGndSl5ruUQ1FAyLhd71JX++zVuP6EayByGnwKSg8
        7RqnaSKSzPnVq4bClWE+hNlVrkuSlh1VtBXIK14qVpyNXyW01I075kbLm7zDr9LW
        /wIDAQAB
        -----END PUBLIC KEY-----
        """;

        String payload = "{\"id\":\"123\",\"hello\":\"world\",\"time\":1723976484,\"tag\":\"UPay Encryption and decryption\"}";

        // No Key
        JWECrypto jweCrypto = new JWECrypto(null, null);
        System.out.println("Private Key: " + jweCrypto.privateKey);
        System.out.println("Public Key: " + jweCrypto.publicKey);

        String encryptToken = jweCrypto.encrypt(payload);
        System.out.println("Encrypted Token: " + encryptToken);
        String decryptedPayload = jweCrypto.decrypt(encryptToken);
        System.out.println("Decrypted Payload: " + decryptedPayload);

        // Just public Key, only Sign
        jweCrypto = new JWECrypto(publicKeyPEM, null);
        encryptToken = jweCrypto.encrypt(payload);
        System.out.println("Encrypted Token with Public Key: " + encryptToken);

        // Just private key, only design
        jweCrypto = new JWECrypto(null, privateKeyPEM);
        // design last encrypt token
        decryptedPayload = jweCrypto.decrypt(encryptToken);
        System.out.println("Decrypted Payload with Private Key: " + decryptedPayload);

        // have both of key of rsa
        jweCrypto = new JWECrypto(publicKeyPEM, privateKeyPEM);
        encryptToken = jweCrypto.encrypt(payload);
        System.out.println("Encrypted Token with Both Keys: " + encryptToken);
        decryptedPayload = jweCrypto.decrypt(encryptToken);
        System.out.println("Decrypted Payload with Both Keys: " + decryptedPayload);
    }
}

Demo Key

KeyValue
CUSTOMERID1111111111
APIKEY51d6dc09-e641-4162-a9e9-43fd0f3cf903
SECRETKEY0a227672c20f48989b5fcdb4c00038ff
PublicKey-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvcnAzEJgN3tcyed3SayO o+2go3LrYxuNRWiYFwkDPMmcpgP4cyeasY7XjWr7dUPlwjmUf5DfG0UeasQlVu16 c/Ab8Qkj4ypopJ4hXwZUb45KVynHjcNsZVAC4fDOFBWCKSn2MJs28N4D5tZnvY9Z BQIQLMkyc0tUGl6VbSb+EOP5mZuK5lZ9yBAZioQl49rXTHQpvzz1AISmperUpsU9 oYJch5/sWS2+rkinI6cC3LZkGndSl5ruUQ1FAyLhd71JX++zVuP6EayByGnwKSg8 7RqnaSKSzPnVq4bClWE+hNlVrkuSlh1VtBXIK14qVpyNXyW01I075kbLm7zDr9LW /wIDAQAB -----END PUBLIC KEY-----
PrivateKey-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC9ycDMQmA3e1zJ 53dJrI6j7aCjcutjG41FaJgXCQM8yZymA/hzJ5qxjteNavt1Q+XCOZR/kN8bRR5q xCVW7Xpz8BvxCSPjKmikniFfBlRvjkpXKceNw2xlUALh8M4UFYIpKfYwmzbw3gPm 1me9j1kFAhAsyTJzS1QaXpVtJv4Q4/mZm4rmVn3IEBmKhCXj2tdMdCm/PPUAhKal 6tSmxT2hglyHn+xZLb6uSKcjpwLctmQad1KXmu5RDUUDIuF3vUlf77NW4/oRrIHI afApKDztGqdpIpLM+dWrhsKVYT6E2VWuS5KWHVW0FcgrXipWnI1fJbTUjTvmRsub vMOv0tb/AgMBAAECggEABT4L7yMhkOwaCKe191IRZmBbe2obnaFhXcVIWxhQIxSM mQIH2M0f3g2awvxQ6tgIrNkLUuEwnrUM5IptQyvwBV1EQ9/ql7DXeeiZ2X24GlwV /7dd49ATRUR76a14USBCvxo6kdglEsDAGA/43CxtVB8HopJlummsp3C0LSv18sBx h8/TtUa0ttG8UodTtBdAbIwolb55lu92XV0mGHpOc9ZsgLlyn2C7o+jlujNqOhi3 YxKCkhL2jzl88qZ4mmRPGPYQQwTo1Xzd4brixwZ1Fyag2kXziE6+v9PX8s+yFsfW 3E/0ye16tfa0/qAx/4oGr9Ox4e7wXLLiN2QpR8UkEQKBgQD4W1r9y3/uKPDne/UR srt0oZQOG9OqouVNgIwzZYw8WG6fu9h+RuihRpLLLqBwCDCiPo17xyHkPufonntJ 4FkfzvL9RV2/d7FERIOPy9umsoFhv1cqD94zlf96hNj5EK1VZpHGhhZ1MCuiRr8W J/tPiCMMSOxVus3SRPw9M/IebwKBgQDDoPjR1WJb1i23uCQm9uvKXl3AgJ9ltfHA JuRR2SO5XxOkZ5HxG+QSAkd9Og4kQAN6MQ9HMh1sR8xa75ck9G5YgmAGBtzRr68L RpcWvT1MNN81hkOrq3pvkrLkepSyz174EIV3m3aTOXaqSe0TOtPN/mU57NxWgyFz QyrubvcYcQKBgQCi2VxfwHlhU+0rDIfUlAsA3hYz7iEr6WZMHHdSGEsNIrte+BBs NNjDL4B/xFIlQ+mH6VZijF93x5vPV2PmPDqUdeG1Gy/upXBSIE7YEkc0FiZqTsm7 5e3Ai6Ga19Nh9YKC1h/OOgwtyWlDHfqcb5kFPHIm0wZM7JGbR+rRwucG9wKBgFc7 JRgz5Rr8QwCD/KLfQb7IG1fmQq1Q1w6OiQqyH0reSVzqzdnMTQN2vrfpNjsVzDcl PyHJB/OrhEUcRrI0yCtxXy3wy3A4vX3yqRZ7paSggtNUHkKjQQIVUO2udzXQYBGP FGSEDviRDAfc1PjZJ27YO/z7UDjHWhgSYEdXdZkRAoGAPpHataBL9NOHZG4zSmCZ jw9yXHTe9xuZl2mkYFgMVzcoGvMjS4PFWDncpiDaVef9rvkY1SlmkMjejOb4KxyI eUkVcbUjvHQ786s4Ul+kdPJehx5B9Av0QhE1D1TVXPUxkx5Xa7usYW3HR0CmeN/1 IFK8O6Hrcn4W90kIgq86hng= -----END PRIVATE KEY-----