API (MeaWallet)
THIS API ACCESS IS DEPRECATED. Please us the Pliant-based one.
Important - ONLY for PCI-DSS compliant companies!
We provide an API endpoint to fetch sensitive card data like the PAN and CVV. This endpoint is ONLY available for companies that are PCI-DSS certified. All other API consumer need to use the web widget solution.
Introduction
Part of our API for PCI-DSS certified companies is the access to sensitive card data like PAN and CVV.
To access sensitive card data, a special encryption flow is necessary. This ensures that only the API consumer can use the sensitive card data, and no third party has access to it. This aligns with the PCI-DSS compliance framework and the secure handling of such data.
Overview of How to Access Sensitive Card Data
For accessing sensitive card data, we partnered with MeaWallet and use their endpoint for fetching a PAN. This endpoint also returns the CVV, since both belong together.
The caller provides the cardToken
of which the PAN and CVV should be fetched and the wrapped AES-256 session key.
The API then returns the encrypted data, containing PAN and CVV, together with the initialization vector (IV) of the AES-256 encryption. With this information and your previously generated session key (SK) you can decrypt the data and obtain the sensitive card data.
PCI-DSS
Please make sure to handle such sensitive card data according to the PCI-DSS framework.
Requesting and Decrypting Sensitive Card Data
- Generate a
sessionKey
(SK) - Wrap the
sessionKey
accordingly to the specification, i.e. encrypting the AES-256 key with the public PGP key of MeaWallet, thus giving you theencryptionKey
- Generate a TOTP value with the TOTP Library from MeaWallet, see example code down below.
- Call the MeaWallet API and provide the necessary parameters and HTTP headers.
- Retrieve the response from the API call
- Use the returned
iv
and previously generatedsessionKey
to decrypt theencryptedData
returned by the MeaWallet API - Show the PAN and CVV to the user or use otherwise in your backend system.
Parameters
Parameter | Example Data | Description |
---|---|---|
cardId | 4414ecdc4264 | The MeaWallet cardId , this is the token in our Pliant API. |
secret | 001#79897399 | TOTP value, see example code below on how to generate this. |
encryptionKey | 1adfab... | Encrypted session key generated by you. Will be used to encrypt the response sent back. |
publicKeyFingerprint | 66b37b69... | Fingerprint of key which was used for session key encryption, static value. |
Code Examples for TOTP Secret and HTTP Header
The following code example uses the TOTP library from MeaWallet, which can be accessed in their private Maven repository.
<dependency>
<groupId>com.meawallet.commons</groupId>
<artifactId>mw-totp</artifactId>
<version>4.8.100</version>
</dependency>
import com.meawallet.commons.totp.CardSecretGenerationConfig;
import com.meawallet.commons.totp.CardSecretGenerator;
import com.meawallet.commons.totp.CardSecretKey;
import com.meawallet.commons.totp.GeneratedCardSecret;
import com.meawallet.commons.totp.TimestepProvider;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.time.Clock;
import java.time.Duration;
public class MeaWalletSecretsGenerator {
private static final String SEPARATOR = "#";
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String AES = "AES";
private static final String KEY_ID = "001";
private static final String M12_TOTP_KEY = "...";
private static final String M11_API_KEY_ID = "...";
private static final String M11_API_KEY = "...";
public String generateTotpSecret(String cardToken) {
CardSecretKey sha512key = CardSecretKey
.builder()
.sharedKey(M12_TOTP_KEY)
.cardId(cardToken)
.algorithm(com.meawallet.commons.totp.HmacHashAlgorithm.SHA_512);
TimestepProvider timestepProvider = new TimestepProvider(Clock.systemUTC());
CardSecretGenerator generator = new CardSecretGenerator(timestepProvider);
CardSecretGenerationConfig config = CardSecretGenerationConfig
.builder(sha512key)
.timestepWindowSize(Duration.ofSeconds(60))
.cardSecretLength(8);
GeneratedCardSecret cardSecret = generator.generate(config);
String secret = cardSecret.getSecret();
return KEY_ID + SEPARATOR + secret;
}
public String getEncryptedMeaSecretHeader(String meaTraceId) throws GeneralSecurityException, DecoderException {
String inputForSecret = meaTraceId + SEPARATOR + M11_API_KEY_ID;
byte[] inputBytes = inputForSecret.getBytes(StandardCharsets.US_ASCII);
byte[] encryptedSecret = initCipher().doFinal(inputBytes);
return Hex.encodeHexString(encryptedSecret, false);
}
private Cipher initCipher() throws GeneralSecurityException {
SecretKeySpec secretKeySpec = new SecretKeySpec(Hex.decodeHex(M11_API_KEY), AES);
byte[] ivBytes = new byte[16];
IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
return cipher;
}
}
And this can be used then as follows:
String meaTraceId = UUID.randomUUID().toString();
String encryptedMeaSecretHeader = meaWalletSecretsGenerator.getEncryptedMeaSecretHeader(meaTraceId);
String totpSecret = meaWalletSecretsGenerator.generateTotpSecret(cardToken);
String g1fingerprint = G1_FINGERPRINT_CONSTANT;
EncryptedPanResponse encryptedPanResponse = webClient.post()
.contentType(MediaType.APPLICATION_JSON)
.header(MEA_TRACE_ID_HEADER, meaTraceId)
.header(MEA_API_KEY_ID_HEADER, M11_API_KEY_ID)
.header(MEA_SECRET_HEADER, encryptedMeaSecretHeader)
.bodyValue(new MeaWalletGetPanRequest(cardToken, totpSecret, encryptedSessionKey, g1fingerprint))
.exchange()
.flatMap(clientResponse -> {
return clientResponse.bodyToMono(EncryptedPanResponse.class);
})
.block();
Code Examples for encryptionKey
encryptionKey
The following React/TypeScript example gives you a better idea of how to implement this flow. Also mind the cryptographic helper methods.
const showCardDetails = async () => {
try {
const { sessionKey, encryptedSessionKey } = await getSessionKey( // generate the session key
process.env.REACT_APP_MEA_WALLET_G1_KEY!
);
const { encryptedData, iv } = await api.getCardSensitiveData( // call the Pliant API
card.cardId,
encryptedSessionKey
);
const cardDetailsJSON = await decryptWithSessionKey( // decrypt the received data
sessionKey,
encryptedData,
iv
);
setCardDetails(JSON.parse(cardDetailsJSON));
} catch (error) {
// some error handling
}
};
import { pem2jwk } from 'pem-jwk';
import KeyEncoder from 'key-encoder';
const arrayBufferToHex = (buffer: ArrayBuffer) => {
return Array.prototype.map
.call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2))
.join('');
};
export const getSessionKey = async (rawPublicKey: string) => {
const keyEncoder = new KeyEncoder('secp256k1');
const pemPublicKey = keyEncoder.encodePublic(rawPublicKey, 'raw', 'pem');
const jwkA = pem2jwk(pemPublicKey);
const [rsaKey, sessionKey] = await Promise.all([
crypto.subtle.importKey(
'jwk',
jwkA,
{ name: 'RSA-OAEP', hash: 'SHA-512' },
false,
['wrapKey']
),
crypto.subtle.generateKey({ name: 'AES-CBC', length: 256 }, true, [
'decrypt',
]),
]);
const wrappedKey = await window.crypto.subtle.wrapKey(
'raw',
sessionKey,
rsaKey,
{ name: 'RSA-OAEP' }
);
return {
sessionKey,
encryptedSessionKey: arrayBufferToHex(wrappedKey).toUpperCase(),
};
};
export const decryptWithSessionKey = async (
sessionKey: CryptoKey,
encryptedData: string,
iv: string
) => {
const bufferResult = await crypto.subtle.decrypt(
{
name: 'AES-CBC',
iv: Uint8Array.from(Buffer.from(iv, 'hex')).buffer,
},
sessionKey,
Buffer.from(encryptedData, 'hex')
);
let json = '';
new Uint8Array(bufferResult).forEach(
(byte: number) => (json += String.fromCharCode(byte))
);
return json;
};
HTTP Request
The call to the MeaWallet API then looks like this:
curl --include \
--request POST \
--header "Content-Type: application/json" \
--header "Mea-Trace-Id: e06bd3df-4a75-4cae-baeb-094ef965e129" \
--header "Mea-Api-Key-Id: 68e05e04-a54d-479c-a85f-b7f6c7531598" \
--header "Mea-Secret: 39F15E671F88008B2023526A4F7A431FFFE83C22F4931D85204EAFE019B8CAF38F8E9542BEFE59CD65D95C0F08BC110C6A2B02576A1EF254879AF167DD2AA11206E088BF8D220CEBEAE1BE407DD57972" \
--data-binary "{
\"cardId\": \"4414ecdc4264\",
\"secret\": \"001#79897399\",
\"encryptionKey\": \"1adfab...\",
\"publicKeyFingerprint\": \"66b37b69...\"
}" \
'https://public.test.meawallet.com/gps/v2/infinnity/getPan'
HTTP Response
The (decrypted) response itself then looks like this:
{
"encryptedData": {
"pan": "5555444433338888123",
"cvv": "878",
"expiry": "2025-05-31",
"embossname": "JOHN SMITH"
},
"iv": "31323334353637383930313233343536"
}
Additional Information
For more information on this topic contact [email protected]
Updated about 2 months ago