MPoC SDK
API Reference
Sign & Encrypt Data

Sign & Encrypt Data

Overview

Note: These are optional helper methods for your app, to use only if you need them. They are not part of the core MPoC card reading and payment flow: a transaction does not call them, and you do not need them to accept payments.

Two helpers for data your app needs to protect with the SDK's keys: signing with the SDK's per-device key, and encrypting a payload with a card working key. Both are functions of MPoCAPI, are synchronous, and may wait for the SDK to become ready on a cold process, so do not call them on the main thread. The SDK has to be initialized first, see Init SDK.

Methods

MPoCAPI.generateSignature

Signs rawData with the SDK's per-device signing key (SHA256withRSA). The certificate that holds the matching public key is SdkInfo.signCert, returned by MPoCAPI.getSdkInfo(), so your backend can verify the signature with it. The backend can also fetch the same certificate from the AMS API, in the signCert field of the Query SDK Information response.

fun generateSignature(rawData: ByteArray): MPoCResult<ByteArray>

Parameters

NameTypeConditionDescription
rawDataByteArrayRequiredThe bytes to sign, signed exactly as given.

Returns

Return TypeDescription
MPoCResult<ByteArray>Success carries the raw signature bytes. Failure carries the error code and message.
// off the main thread
when (val result = MPoCAPI.generateSignature("order-1234".toByteArray())) {
  is MPoCResult.Success -> {
    val signature: ByteArray = result.data
    // send the data and Base64.encodeToString(signature, Base64.NO_WRAP) to your backend
  }
  is MPoCResult.Failure -> {
    // result.code, result.message
  }
}

Verifying (backend). Read the public key from the signCert certificate and verify the signature over the same bytes with SHA256withRSA. Your backend can also get the certificate itself, without the app sending it, from the signCert field of the Query SDK Information response.

MPoCAPI.encryptData

Encrypts data with a card working key derived at a new KSN ("KSN2"), which is separate from the KSN the SDK uses for the sale's track 2 ("KSN1"). The bytes are encrypted exactly as given, so there is no encoding step: your app decides how its own data becomes bytes (for a string, text.toByteArray() is UTF-8), and the backend decodes the decrypted bytes the same way.

const val MAX_ENCRYPT_DATA_BYTES = 4096
 
fun encryptData(
  data: ByteArray,
  mode: EncryptMode = EncryptMode.CBC
): MPoCResult<MhdEncryptedData>

Parameters

NameTypeConditionDescription
dataByteArrayRequired1 to MPoCAPI.MAX_ENCRYPT_DATA_BYTES (4096) bytes. Anything else returns MPoCErrorCode.ErrInvalidParameters (41500).
modeEncryptModeOptionalEncryptMode.CBC (default, PKCS7 padding) or EncryptMode.GCM. See EncryptMode.

Returns

Return TypeDescription
MPoCResult<MhdEncryptedData>Success carries the encrypted data and its KSN. Failure carries the error code and message.
data class MhdEncryptedData(
  // hex of IV || ciphertext (CBC, 16-byte IV) or IV || ciphertext || tag (GCM, 12-byte IV, 16-byte tag).
  // The IV is random on every call
  val encryptedHex: String,
  // the 12-byte KSN (24 hex characters) the key was derived at
  val ksn: String,
  // the mode that was used
  val mode: EncryptMode
)
// off the main thread
when (val result = MPoCAPI.encryptData("hello".toByteArray(), EncryptMode.GCM)) {
  is MPoCResult.Success -> {
    // send result.data.encryptedHex and result.data.ksn to your backend
  }
  is MPoCResult.Failure -> {
    // result.code, result.message
  }
}

Decrypting (backend). Derive the card working key from the BDK (or the IPEK) and the returned ksn, with DUKPT-AES key usage DataEncryptionBothWays (the same derivation as for track 2). Split the IV off the front of the decoded hex, then decrypt.

Behaviour to know

  • Each call consumes one KSN from the DUKPT counter and never reuses one, including across calls from different threads and after a process restart. The counter allows about 4.29 billion values. When it is spent the call fails and nothing changes.
  • It needs no sale and no earlier call. An app that has only initialized the SDK can call it, and the sale's own card key (the one used for track 2) is not touched.
  • Once the payment keys are provisioned it works without network. If they are missing or expired, the SDK downloads them first, the same way a sale does.