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
| Parameter | Example |
|---|---|
| SecretKey | g3)&~8P9S+ZhqO@G8b9Ate%xa5Jh-.E% |
| METHOD | POST |
| URI | /api/v1/customer/add |
| TIMESTAMP | 1774573955994 |
| REQUEST_ID | 366dde77c57a4e7e988c074a21a4f04c |
| Signature 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 |
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
| Parameter | Algorithm | Remarks |
|---|---|---|
| alg | RSA-OAEP-256 | Key management algorithm |
| enc | A128GCM | Encryption algorithm |
| typ | JOSE | Encryption type |
| zip | DEF | Increases transmission speed, reduces transmission content size |
| kid | sha256(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!")
