One-time Session Key Wrapping
The client must wrap one-time Session Key (SK) with the RSA public key (G1) supplied by MeaWallet, in an RSA digital envelope computed using PKCS#11’s C_WrapKey method, which is defined on section 11.14 (page 178) of the PKCS#11 v2.20 standard.
When MeaWallet backend will send back sensitive PCI/PII card data, it will uses this Session Key to encrypt the plaintext payload containing sensitive PCI/PII card data.
The client must regenerate a new Session Key for each new API call.
Setup
- MeaWallet generates Public Private key pair;
- MeaWallet shares public key (G1 key) with the client. (Shared public key is in ASN.1 DER (HEX) format, Java sample bellow uses extracted modulus and exponent components from this public key);
High Level Steps
- Before making request for sensitive card data to MeaWallet, the client generates AES-256 bit one-time session key (SK);
- Encrypt (wrap) the SK with MeaWallet’s Public Key (G1 key);
- Send encrypted (wrapped) SK to MeaWallet;
- MeaWallet decrypts encrypted (wrapped) SK, encrypts card data with SK and sends data back to the client;
- Client decrypts sensitive data using SK;
Configuration
Encryption of the One-time Session Key (SK)
Key length = 4096 bit
Algorithm = RSA
Block Cipher Mode: ECB
Padding = PKCS#1 v2.2 OAEP method
OAEP Mask Generation Function: MGF1
OAEP Mask Generation Function Hash Algorithm = SHA-512
OAEP Parameters = none
Sensitive Data Encryption
Key Length = 256 bits
Algorithm = AES
Block Cipher Mode = CBC
Padding = PKCS#5/PKCS#7
- java
- javascript
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.RSAPublicKeySpec;
final class WrapOneTimeSessionKeyExample {
private static final String MEAWALLET_PUBLIC_KEY_G1_MODULUS_HEX = "C93CD9E6FC2317DB8977DE7007411A7DFCD57A863E5CE16E4A5F96E7D9CC04579E1FA1853CC88E70BDFAA7EBC36EE2E35A4390477ED8944403CDBBB859A7FB930C70BD46CD15D96EA4D9630526A60BD61854ED04E9DFF021EFF8A44A77E38DB4F3F17CF9BFDE38EF52DCB599908724BFCF643F515728776247B68339D59B504B7650646C1B49D291CE25544E729E981698C53B11279DA0856E5C59AB1144459E05C580BFE2B500BAC36091B77C3379EFFB4F16DD17C1965377A92952E6249C0869B2D10E655C7AA0F70021A172178F37CC50603E778A7820E71D65A7DC9E19922D7158C06FBF37505AE5617C51CB1364F7DF48190E82DB2F536124D0C9AA6B24896C854DF6D47538FB15A0F5827B0992778B9ECEA5B7F07EC6D4EDD124A594F2C2930799B7F926C90622EC0D7CF131141549BC1A1E34D50C9FE4E46582FE36287D304DE8DB846B1B545919CA3BF8ABFADDC07975FB9CBBBC6E678B904848653902970EB61CDB45D3EFF16241700DDEB8A675B1A79C41F01FC18D9981F3E0D3B2274788ACB26BE2B982C1B0EA7CD6D4BE26898ACCCE94F1C61513113A6B80D3B8E9AA3FBAFA7F82A8640EE90C7D0D1E45FC81FA23DA0DD572FBF3D9313EE903CCC6565AFEA4D0A4B1CDEA12C7FFED5E9A41C31DAB6D7A1B5E43883FBDDE9E32B4690FF5BC9470716E2135FE3B92D3F077D6E36AC0F5188E21EF1CC52829994289";
private static final String MEAWALLET_PUBLIC_KEY_G1_EXPONENT_HEX = "010001";
private static final String MEAWALLET_RESPONSE_DATA = "{\n" +
" \"pan\": \"5541146445656057\",\n" +
" \"cvv\": \"12\",\n" +
" \"expiry\": \"2025-05-31\",\n" +
" \"embossname\": \"John Doe\",\n" +
" }";
private static final byte[] IV = new byte[16];
public static void main(String[] args) throws Exception {
// 1. Client generates AES-256 bit one-time session key (SK);
byte[] oneTimeSessionKey = generateOneTimeSessionKey();
// 2. Encrypt (wrap) the session key with MeaWallet’s Public Key (G1 key);
PublicKey publicKey = buildRsaPublicKey(
Hex.decodeHex(MEAWALLET_PUBLIC_KEY_G1_MODULUS_HEX),
Hex.decodeHex(MEAWALLET_PUBLIC_KEY_G1_EXPONENT_HEX));
String encryptedSecretKeyHex = encryptSessionKey(oneTimeSessionKey, publicKey);
// 3. Client sends wrapped one time session key to MeaWallet together with publicKeyFingerprint which was used for encryption;
System.out.println("Wrapped one time session key: " + encryptedSecretKeyHex);
// 4. MeaWallet encrypts card data with decrypted session key and sends data back to the client;
String encryptedMeaWalletResponseData = encryptCardData(MEAWALLET_RESPONSE_DATA.getBytes(StandardCharsets.UTF_8), oneTimeSessionKey, IV);
// 5. Client decrypts sensitive data using it's session key;
byte[] decryptedData = decryptCardData(encryptedMeaWalletResponseData, oneTimeSessionKey, IV);
System.out.println("Data decrypted with one time session key: " + new String(decryptedData, StandardCharsets.UTF_8));
}
private static byte[] generateOneTimeSessionKey() throws NoSuchAlgorithmException {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
return keyGen.generateKey().getEncoded();
}
public static PublicKey buildRsaPublicKey(byte[] modulus, byte[] exponent) throws NoSuchAlgorithmException, InvalidKeySpecException {
RSAPublicKeySpec spec = new RSAPublicKeySpec(new BigInteger(1, modulus), new BigInteger(1, exponent));
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePublic(spec);
}
private static String encryptSessionKey(byte[] sessionKey, Key publicKey) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-512AndMGF1Padding");
OAEPParameterSpec oaepParameterSpec = new OAEPParameterSpec(
"SHA-512", "MGF1", MGF1ParameterSpec.SHA512, PSource.PSpecified.DEFAULT);
cipher.init(Cipher.ENCRYPT_MODE, publicKey, oaepParameterSpec);
byte[] encryptedKey = cipher.doFinal(sessionKey);
return Hex.encodeHexString(encryptedKey);
}
/**
* Card Data encryption on MeaWallet side.
*/
private static String encryptCardData(byte[] data, byte[] secretKey, byte[] initVector) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(initVector);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(secretKey, "AES"), iv);
byte[] encryptedKey = cipher.doFinal(data);
return Hex.encodeHexString(encryptedKey);
}
static byte[] decryptCardData(String encryptedData,
byte[] secretKey,
byte[] initVector) throws GeneralSecurityException, DecoderException {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(initVector);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(secretKey, "AES"), iv);
byte[] decryptedData = cipher.doFinal(Hex.decodeHex(encryptedData));
return decryptedData;
}
}
// 1. Client generates AES-256 bit one-time session key (SK);
// 2. Encrypt (wrap) the SK with MeaWallet’s Public Key (G1 key);
const { sessionKey, wrappedKey } = await wrapSessionKey(
MW_PUBLIC_KEY,
{ name: "RSA-OAEP", hash: "SHA-512" },
{ name: "AES-CBC", length: 256 }
);
// 5. Client decrypts sensitive data using it's SK;
const result = await decryptWithSessionKey(
this.state.sessionKey,
"AES-CBC",
cardDetails.encryptedData,
cardDetails.iv
);
var pem2jwk = require("pem-jwk").pem2jwk;
var KeyEncoder = require("key-encoder").default;
const arrayBufferToHex = (buffer) => {
return Array.prototype.map
.call(new Uint8Array(buffer), (x) => ("00" + x.toString(16)).slice(-2))
.join("");
};
export const wrapSessionKey = async (
publicKeyHexa,
algorithm,
sessionKeyAlgorithm
) => {
const keyEncoder = new KeyEncoder("secp256k1");
const pemPublicKey = keyEncoder.encodePublic(publicKeyHexa, "raw", "pem");
const jwkA = pem2jwk(pemPublicKey);
const [rsaKey, sessionKey] = await Promise.all([
window.crypto.subtle.importKey("jwk", jwkA, algorithm, false, [
"wrapKey",
]),
window.crypto.subtle.generateKey(sessionKeyAlgorithm, true, [
"decrypt",
]),
]);
const wrappedKey = await window.crypto.subtle.wrapKey(
"raw",
sessionKey,
rsaKey,
algorithm
);
return {
wrappedKey: arrayBufferToHex(wrappedKey).toUpperCase(),
sessionKey,
};
};
export const decryptWithSessionKey = async (
sessionKey,
algorithm,
encryptedData,
iv
) => {
const bufferResult = await window.crypto.subtle.decrypt(
{
name: algorithm,
iv: Uint8Array.from(Buffer.from(iv, "hex")).buffer,
},
sessionKey,
Buffer.from(encryptedData, "hex")
);
return String.fromCharCode.apply(null, new Uint8Array(bufferResult));
};