Skip to main content

Sensitive Data Decryption

This manual describes encrypted (by MeaWallet) sensitive data decryption, including (but not limited to) PIN management. Mea Token Platform provides PIN management fuctionality, including GET, SET and UNBLOCK methods. In case of implementing /pin/set endpoint, related PIN values are encrypted on MeaWallet side and needs to be decrypted on Issuer side.

PIN Data Decryption

Setup

  1. Issuer generates Public + Private key pair.
  2. Issuer shares public key with MeaWallet (in a format of CSR).
  3. MeaWallet stores the key for data encryption.

High Level Steps

  1. When sensitive data needs to sent, MeaWallet generates AES-256 bit key one-time/secret key (SK).
  2. Encrypts the JSON-formatted sensitive data with this one-time/secret key.
  3. Encrypt (wrap) the one-time/secret key (SK) with Issuer’s Public Key.
  4. Send encrypted payload with encrypted secret key directly to Issuer backend API.
  5. Issuer decrypts the payload and uses received sensitive data.

Encrypted payload structure

Card Data Payload

Configuration

Sensitive Data Encryption

Key Length = 256 bits
Algorithm = AES
Block Cipher Mode = CBC
Padding = PKCS#5/PKCS#7

Key Encryption

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

Input JSON (e.g. /pin/set method payload):

{
"requestId": "...",
"cardId": "...",
"secret": "...",
"encryptedData": "D37DDEFEC50DCE041EC3D81CA3AB0AE651243F373B2B73F0824CD034D0892B8D525C46B89A86C294D090AB13A7492D45",
"iv": "ED61BF77169B0111545C6528FE5F96D8",
"encryptionKey": "313077F13D9FECAF3319CF17AED65C4D9323A41248ED8DDE98DD1D9E20C96C284B65E41688284F06F8479F3FD1B3A64219D138A8D8A702D5EFF59D6035C6656C4F3D2A879E56801228AD6B3463A1F89405771995AFA3C5F77F624299E06E3D34F2175A40D334A2E03A5FB61F905231226E170D504448C34CA3F1A12A871BDE136597C812D423215C2FD6F829711AB9A4FF35776585BE99AFD3BF686143590222D92E7BC9D4544F98EA2B768D506291923C43D88E49F988AD22B0A7FADB270B515204F4DE444706AA157DA99DFD69AADAAD53B817297ECF86449FAF4D62150425D9DA47C2B5331C43CDBE369DFE5D761A757A28530292BD1A50B507F6A114D783",
"publicKeyFingerprint": "..."
}

Decrypted payload (field "encryptedData") JSON:

{
"oldValue": "1234",
"newValue": "4321"
}
info

Below is a sample of Java implementation with real data. However, it's expected by the PCI-DSS compliant issuer to use Hardware Security Module (HSM) when encrypting the card data.

Complete decryption example

private static final String EXPECTED_DATA = "{\n"
+ " \"oldValue\": \"1234\",\n"
+ " \"newValue\": \"4321\"\n"
+ "}";

private static final String MODULUS = "00A40828F91B332BEA96F9B15DD220A34BF5322F7A3D99C5EC2FC5398CABE52F43F408444368EDDA144D3A25F2DEF231759537F1C9DA55CF60D166DA5C5C04E161B4F7E787C15044C7368AADA3744CDAA12C9F604F16939A99106BDAC77E4D9B446CB3A56791E4409F4812010087F16D10BFFA3EE34D66A9B2EDE5FE8971C6C2AF1870BE1F5D3AFCAAE558D695F53261EB4BF2AA7F7B1FC2FF7394C78CB6542A4F2F7B4842A1DC05968614CF9B7B9BD125D742A701CA6F8A9BA08D4AE58A7BFC3F2975A2E2FABE26853DFF0DC1EA5C8C471938846BA21DF26CF90C1E2D680B92BDE7D76918CCF1DEFDD0ABC34322CED7D73619B40CA6D12C50AD5179F0A4EDFA1B";
private static final String PUBLIC_EXPONENT = "010001";
private static final String PRIVATE_EXPONENT = "40878D46E27271166AAA74224AA5D857EB7000C9802E5C749C1E6493789442D1E2D0D5BA072F7B197D8020A9B9176269B5783AC0840A39F906A805C34BD807AA5E7D203281D4481A6ED48396C80BEBFE780582E7DA23DB2FD80BF61781129AC0825AF1F91AA5C6DE6285E2AB08CFEB6E297813016CD3B472D5B0234082E804722763254EE4086E8FFDCD10D9B4B4F557BAC015DEC7CCB46DDAC20229CE0E546EA2F69D1500A412A24368B943E2781D1D6B63C4141CD966833331E6CE7D00F028BE688A175D875A39BB837923CB407ED741B749975BD49EF62859B3C78F565EF4D521016487DD04BEEDB6C15E055D2EA1DF91928501B9C5359476551BCD5017C1";
private static final String DP_PRIME_EXPONENT_P = "59856A90E43E96D6EA3E164A9F5B740B03C1150F981E67783E0CC6C515A7B261667CD05A87921F54EC9C6F53DEBBCBB70E089011007AA6FF48B9930C9D8610CED9D9CAC0E69EBAE7BADACCA2E5AFA7B6CF228F8FB4F041756DC31D3005CC5653A46E03E94040EE5881F14FBB972098F8B12FE4056C514A6B6F8919D9A603B06B";
private static final String DQ_PRIME_EXPONENT_Q = "00C8CA8BFDC0CED8CED310D6D9A2B31BCF8964C6FC93056AFB982CF98B2896EFF433F69AEFB637B98FFB9D0193017B7FA6FE12FDD2A3AAF2971E32F07A439F8C6E22CDA6B15049C6C11647D33319E6CA037B182BE20E72277069CB00845E5F9F59FBF53958372158109FF58BB2C2561CCEF4D2D54EEB8A57FD6F1E847EF68F9E01";
private static final String PRIME_P = "00B7B42BE17F995BCCFF09C26DAF64D77BE5F72C531DF892F76D622D03F8A1BA07F14CFF91E5062BDEE90FCC71E79D023A2915F8D8AFDD0C07FB42F439F1520F69E6DA049F6594BEBB2EBE87F1F99780605F5DE0DE6D07C53DE64E461129636BB53805012C24EC11CE7B16C11348EBCB028EED11824C9AC23235E2E9F0459EA8EB";
private static final String PRIME_Q = "00E49613F586D0BA4313A2D635C2DDFBBE233D1EBE3AEBED3881F58C6A97D843C8F2631BA271299F44F7CDE5BED6F07E8454800D8B2D622501ECB1168AD54837D7CBE624B5894C84AC504C2C601C934C9A8FD8E5EEE60C7A3F8B0209BEADC48208F1B1F8A8FC2114DB1BC6B4552898669F2BD6355EEB31ADC8D1977EEBE9DDA791";
private static final String CRT_COEFFICIENT = "00B1109F5411A23D9D23AFCEAF5EBC50D5156E6F51A59D5A1C0CB07FE34130DFE5B40C366955C007E01D35DC7A9DA1B98F33982F344A2D2D85FFBB44C5EB3D9D717625BEA673DAD58D5D3B0DDDA120940F288F5F6EC0CD7E984A9C5A44462273EACC620A91E450FF664DB3491308DF8EB2600E61D28876EFB4BCF0D7C2BBC519A6";

private static final String PROVIDER = "SunJCE";
private static final String ENCRYPTION_PADDING_PREFIX = "OAEPWith";

private static final String SECRET_KEY_ALGORITHM = "AES";
private static final String SECRET_KEY_TRANSFORMATION = "AES/CBC/PKCS5Padding";

private static final String WRAPPER_KEY_ALGORITHM = "RSA";
private static final String WRAPPER_KEY_TRANSFORMATION = "RSA/ECB/OAEPWithSHA-512AndMGF1Padding";

private static final String EXAMPLE_ENCRYPTED_DATA = "A9156F522C51FCEA056D1E775731C1F4F875FDFE341DF84035A870A1CCCA631C2D213FB58D3A02A064C3E8CAF8B7B082";
private static final String EXAMPLE_ENCRYPTION_KEY = "7F8B60DBE9439BAECCB9F72862BA50452786CBBE9DDA755E5ECC7B95F15E37052FB2E16C6FD9870147A0899388F401E32CB42CA42C4EE12D32E7383701DF508716F4E47B84331D40C4F266DDF4E1163B71086705D6C6883812B65B3A946E9DE75B7299F6E12989FB8E639ACF66CCC7D06EF07ED342A5C85B596483F66C2B3195C8E65F577856F73BD8DF7891239CA5C4545E5607F0AD5DBD6740CE133E8D9F1E3721479A105DB4E5C360327995BCD9048A565735F5809E5B07B35FE4E428C41C964B57A9D73174C06788F276509FA53F7BCFEEB0806DBDC425B195BE1D287204EA6C955BEC09419E26F85C1FBCC71E066D3F78AC77AE9923F36C44F30B382508";
private static final String EXAMPLE_IV = "F40C7CCD16CAF2790BA375E2E8B52C3D";


public static void main(String[] args) throws Exception{
PrivateKey privateKey = buildRsaPrivateKey();
Key decryptionKey = unwrapDecryptionKey(WRAPPER_KEY_TRANSFORMATION, EXAMPLE_ENCRYPTION_KEY, privateKey);
byte[] decryptedPayload = decryptPayload(decryptionKey, EXAMPLE_ENCRYPTED_DATA, EXAMPLE_IV);

String decryptedData = new String(decryptedPayload, StandardCharsets.UTF_8);

System.out.println("Decryption succeed: " + EXPECTED_DATA.equalsIgnoreCase(decryptedData));
}

private static PrivateKey buildRsaPrivateKey() throws NoSuchAlgorithmException, InvalidKeySpecException, DecoderException {
RSAPrivateCrtKeySpec spec = new RSAPrivateCrtKeySpec(
new BigInteger(1, Hex.decodeHex(MODULUS)),
new BigInteger(1, Hex.decodeHex(PUBLIC_EXPONENT)),
new BigInteger(1, Hex.decodeHex(PRIVATE_EXPONENT)),
new BigInteger(1, Hex.decodeHex(PRIME_P)),
new BigInteger(1, Hex.decodeHex(PRIME_Q)),
new BigInteger(1, Hex.decodeHex(DP_PRIME_EXPONENT_P)),
new BigInteger(1, Hex.decodeHex(DQ_PRIME_EXPONENT_Q)),
new BigInteger(1, Hex.decodeHex(CRT_COEFFICIENT))
);
KeyFactory factory = KeyFactory.getInstance(WRAPPER_KEY_ALGORITHM);
return factory.generatePrivate(spec);
}

private static Key unwrapDecryptionKey(String transformation, String encryptionKey, PrivateKey unwrappingKey) throws Exception {
Cipher cipher = Cipher.getInstance(transformation, PROVIDER);
cipher.init(Cipher.UNWRAP_MODE, unwrappingKey, getOAEPParameterSpec(transformation));
return cipher.unwrap(Hex.decodeHex(encryptionKey), SECRET_KEY_ALGORITHM, Cipher.SECRET_KEY);
}

private static byte[] decryptPayload(Key encryptionKey, String encryptedData, String iv) throws Exception {
byte[] encryptedDataBytes = Hex.decodeHex(encryptedData);
IvParameterSpec ivParameterSpec = new IvParameterSpec(Hex.decodeHex(iv));
Cipher payloadCipher = initPayloadDecryptionKey(encryptionKey, ivParameterSpec);
return payloadCipher.doFinal(encryptedDataBytes);
}

private static Cipher initPayloadDecryptionKey(Key secretKey, IvParameterSpec iv) throws Exception {
Cipher payloadCipher = Cipher.getInstance(SECRET_KEY_TRANSFORMATION);
payloadCipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
return payloadCipher;
}

private static OAEPParameterSpec getOAEPParameterSpec(String algorithm) {
if (algorithm.contains(ENCRYPTION_PADDING_PREFIX)) {

// OAEPWith<digest>And<mgf>Padding
// String template = "OAEPWith<digest>And<mgf>Padding";

String digest = resolveDigest(algorithm);
return new OAEPParameterSpec(
digest,
resolvePadding(algorithm),
new MGF1ParameterSpec(digest),
PSource.PSpecified.DEFAULT
);
}
return null;
}

private static String resolveDigest(String algorithm) {
int startDigest = algorithm.indexOf(ENCRYPTION_PADDING_PREFIX) + 8;
int endDigest = algorithm.indexOf("And");
return algorithm.substring(startDigest, endDigest);
}

private static String resolvePadding(String algorithm) {
int startPadding = algorithm.indexOf("And") + 3;
int endPadding = algorithm.indexOf("Padding");
return algorithm.substring(startPadding, endPadding);
}

Calculating Fingerprint

Calculate fingerprint by using SHA-256 from PublicKey, or store it static as provided by MeaWallet:

Hex.bytesToHex(Crypto.calculateSha2(publicKey.getEncoded()));