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

  1. Generate a sessionKey (SK)
  2. Wrap the sessionKey accordingly to the specification, i.e. encrypting the AES-256 key with the public PGP key of MeaWallet, thus giving you the encryptionKey
  3. Generate a TOTP value with the TOTP Library from MeaWallet, see example code down below.
  4. Call the MeaWallet API and provide the necessary parameters and HTTP headers.
  5. Retrieve the response from the API call
  6. Use the returned iv and previously generated sessionKey to decrypt the encryptedData returned by the MeaWallet API
  7. Show the PAN and CVV to the user or use otherwise in your backend system.

Parameters

ParameterExample DataDescription
cardId4414ecdc4264The MeaWallet cardId, this is the token in our Pliant API.
secret001#79897399TOTP value, see example code below on how to generate this.
encryptionKey1adfab...Encrypted session key generated by you. Will be used to encrypt the response sent back.
publicKeyFingerprint66b37b69...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

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]