Agency banking
Kindly note
This API implements the SOAP style.
This service enables withdrawals from POS agents.
Process Flow
Step 1: Make a call to token API
Step 2: Call the cashout API
Step 3: Re-query transaction
Token API
The first step is to send a token request before a transaction is performed. This should be done every 24 hours.
Endpoint: https://qa.interswitchng.com/kmw/requesttoken/perform-process
HTTP Method:
<tokenPassportRequest>
<terminalInformation>
<merchantId>203900000000007</merchantId>
<terminalId>20390007</terminalId>
</terminalInformation>
</tokenPassportRequest>
<? xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<tokenPassportResponse>
<responseCode>00</responseCode>
<responseMessage>Successful</responseMessage>
<token>
eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsia2ltb25vIiwicGFzc3BvcnQiXSwic2NvcGUiOlsicHJvZm
lsZSJdLCJleHAiOjE2MjEyOTU5MDcsImNsaWVudF9uYW1lIjoia2ltb25vIiwianRpIjoiOTJmNThlN
jAtNjE0ZS00NjQ4LTg4NjItZjRkYWJjMTAwNTFiIiwiY2xpZW50X2lkIjoia2ltb25vIn0.UR8V5Nz4
PYnrioCp3X6S6S2MNkdhhNN1hBy4__rLfeEBzZVLI6AqVSi94vkbqUbiNh6Bqy2m_gVz9OKMp3WA2S4H3T8Tqa
oPUHUDATupOvIOrjwL59lSFtfsGMIcJrgqLFs1c777evD0Zq3mzWeCS6WZ7uFQX2nrAm4mFSI80I8xYudWdZ_EwUz2j7LN1634byZvchNVFrP1fuaId3jYLRyWTSwuqdkyiI2DxJbxc6E9Hg
zpkSs6yU52M3jQ1UfWHeDlo_yGSHEKLzj86GVUJPdq8uvA4jzqpEubKl9469aF9R_sLJcLWV7xdv0mp
-h4jSOpR-1vjGr5E8uE1BvA
</token>
</tokenPassportResponse>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<tokenPassportResponse>
<responseCode>03</responseCode>
<responseMessage>Invalid Kimono Terminal id</responseMessage>
<token></token>
</tokenPassportResponse>
Cashout API
Send a cashout request to the settlement account.
Endpoint: https://qa.interswitchng.com/kmw/kimonoservice/amex
HTTP Method: POST
HTTP HEADERS
Authorization: “Bearer “ + Token
Content-Type: Application/xml
Accept: application/xml
<transferRequest>
<terminalInformation>
<batteryInformation>100</batteryInformation>
<currencyCode>566</currencyCode>
<languageInfo>EN</languageInfo>
<merchantId>203900000000007</merchantId>
<merhcantLocation>ABCD STATE GOVERNMENT ON LANG</merhcantLocation>
<posConditionCode>00</posConditionCode>
<posDataCode>510101511344101</posDataCode>
<posEntryMode>051</posEntryMode>
<posGeoCode>00234000000000566</posGeoCode>
<printerStatus>1</printerStatus>
<terminalId>20390007</terminalId>
<terminalType>22</terminalType>
<transmissionDate>2020-09-18T10:52:26</transmissionDate>
<uniqueId>3H661643</uniqueId>
</terminalInformation>
<cardData>
<cardSequenceNumber>01</cardSequenceNumber>
<emvData>
<AmountAuthorized>000000000001</AmountAuthorized>
<AmountOther>000000000000</AmountOther>
<ApplicationInterchangeProfile>3900</ApplicationInterchangeProfile>
<atc>04A0</atc>
<Cryptogram>12345678909876</Cryptogram>
<CryptogramInformationData>80</CryptogramInformationData>
<CvmResults>440302</CvmResults>
<iad>0110A7C003020000E87C00000000000000FF</iad>
<TransactionCurrencyCode>566</TransactionCurrencyCode>
<TerminalVerificationResult>0000008000</TerminalVerificationResult>
<TerminalCountryCode>566</TerminalCountryCode>
<TerminalType>22</TerminalType>
<TerminalCapabilities>E0F8C8</TerminalCapabilities>
<TransactionDate>200806</TransactionDate>
<TransactionType>00</TransactionType>
<UnpredictableNumber>2E170407</UnpredictableNumber>
<DedicatedFileName>A0000000041010</DedicatedFileName>
</emvData>
<track2>
<pan>1234567890123456</pan>
<expiryMonth>05</expiryMonth>
<expiryYear>19</expiryYear>
<track2>1234567890123456D1905201000000000</track2>
</track2>
</cardData>
<originalTransmissionDateTime>2020-09-18T10:52:26</originalTransmissionDateTime>
<stan>000018</stan>
<fromAccount>Savings</fromAccount>
<toAccount></toAccount>
<minorAmount>1</minorAmount>
<receivingInstitutionId>627821</receivingInstitutionId>
<surcharge>1075</surcharge>
<pinData>
<ksnd>605</ksnd>
<ksn></ksn>
<pinType>Dukpt</pinType>
<pinBlock></pinBlock>
</pinData>
<keyLabel>000006</keyLabel>
<destinationAccountNumber>2000000001</destinationAccountNumber>
<extendedTransactionType>6103</extendedTransactionType>
<retrievalReferenceNumber>000000001234</retrievalReferenceNumber>
</transferRequest>
<? xml version="1.0" encoding="UTF-8" standalone="yes"?>
<transferResponse xmlns:ns2="http://tempuri.org/ns.xsd"
xmlns:ns4="http://interswitchng.com" xmlns:ns3="http://ws.waei.uba.com/">
<description>Transaction Approved</description>
<field39>00</field39>
<authId>C33806</authId>
<hostEmvData>
<AmountAuthorized>0</AmountAuthorized>
<AmountOther>0</AmountOther>
<atc>05D1</atc>
<iad>5E62A9C6008000000000000000000000</iad>
<rc>00</rc>
</hostEmvData>
<referenceNumber>000000001234</referenceNumber>
<stan>12</stan>
<transactionChannelName>ASPFEP</transactionChannelName>
<wasReceive>true</wasReceive>
<wasSend>true</wasSend>
</transferResponse>
<? xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<transferResponse xmlns:ns2="http://tempuri.org/ns.xsd"
xmlns:ns4="http://interswitchng.com" xmlns:ns3="http://ws.waei.uba.com/">
<description>System malfunction</description>
<field39>96</field39>
<referenceNumber>000000001234</referenceNumber>
<stan>0</stan>
<transactionChannelName>MY_NEW_PB</transactionChannelName>
<wasReceive>false</wasReceive>
<wasSend>false</wasSend>
</transferResponse>
Re-query API
This API is used to query the status of a transaction.
Endpoint: Thttps://qa.interswitchng.com/kmw/v2/transaction/requery
HTTP Method: POST
HTTP HEADERS
Content-Type: application/xml
Accept: application/xml
Sample Request
<transactionRequeryRequest>
<applicationType>gTransfer</applicationType>
<originalTransStan>000102</originalTransStan>
<originalMinorAmount>1000</originalMinorAmount>
<terminalInformation>
<terminalId>2ISW0001</terminalId>
<merchantId>2ISW1234567TEST</merchantId>
<transmissionDate>2020-01-31T18:16:02</transmissionDate>
</terminalInformation>
</transactionRequeryRequest>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<transactionRequeryResponse>
<description>Transaction Approved</description>
<field39>00</field39>
<referenceNumber>002251442967</referenceNumber>
<stan>102</stan>
</transactionRequeryResponse>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<transactionRequeryResponse>
<description>Transaction not found</description>
<field39>01</field39>
<stan>0</stan>
</transactionRequeryResponse>
Pin Encryption
In Interswitch today, we use a Pin Encryption process called DUKPT which stands for Derived
Unique Key Per Transaction. DUKPT generates a new key per transaction which we call the
PINBLOCK. This technique involves the use of a Base Derivation Key (BDK) and Key Serial Number
(KSN). On each transaction, the terminal generates new encryption keys that are derived from a
secret BDK and a non-secret KSN. It encrypts the PIN with this key, and then forwards both the
encrypted PIN and the key serial number to the acquirer.
The benefit of DUKPT is that if one of these one-time encryption key is discovered, only one
transaction will be compromised, none of the others transactions from the same POS device
would be able to be decrypted with that key.
In the transaction payload, the below is where the Pinblock and KSN are sent upstream during
an Online PIN transaction.
<pinData>
<ksnd>605</ksnd>
<ksn>{Non-secret KSN}</ksn>
<pinType>Dukpt</pinType>
<pinBlock>{New Encryption Key}</pinBlock>
</pinData>
Below will be a step-by-step guide on how to generate the Pinblock using the Blackbox logic
and Kotlin for Android as the base language.
- First step will be to do an XOR with the Clear Pinblock and the Working key.
fun getWorkingKey(IPEK: String = "9F8011E7E71E483B", KSN: String =
"0000000006DDDDE01500"): String {
var initialIPEK: String = IPEK
println("The expected value of the initial IPEK $initialIPEK")
val ksn = KSN.padStart(20, '0')
println("The expected value of the ksn $ksn")
var sessionkey = ""
//Get ksn with a zero counter by ANDing it with 0000FFFFFFFFFFE00000
val newKSN = XORorANDorORfunction(ksn, "0000FFFFFFFFFFE00000", "&")
println("The expected value of the new KSN is $newKSN")
val counterKSN = ksn.substring(ksn.length - 5).padStart(16, '0')
println("The expected value of the counter KSN is $counterKSN")
//get the number of binary associated with the counterKSN number
var newKSNtoleft16 = newKSN.substring(newKSN.length - 16)
println("The expected value of the new KSN to left 16 $newKSNtoleft16")
val counterKSNbin = Integer.toBinaryString(counterKSN.toInt())
println("The expected value of the counter KSN Bin $counterKSNbin")
var binarycount = counterKSNbin
for (i in 0 until counterKSNbin.length) {
val len: Int = binarycount.length
var result = ""
if (binarycount.substring(0, 1) == "1") {
println("The expected value of the result is $result")
binarycount = binarycount.substring(1)
println("The value of the new binary count is $binarycount")
} else {
binarycount = binarycount.substring(1)
println("The value of the new binary count is $binarycount")
continue
}
val counterKSN2 = Integer.toHexString(Integer.parseInt(result, 2))
.toUpperCase().padStart(16, '0')
println("The expected value of the counter ksn 2 is $counterKSN2")
val newKSN2 = XORorANDorORfunction(newKSNtoleft16, counterKSN2, "|")
println("The expected value of the new ksn 2 is $newKSN2")
sessionkey = BlackBoxLogic(newKSN2, initialIPEK) //Call Blackbox here
println("The expected value of the session key here is $sessionkey")
newKSNtoleft16 = newKSN2
initialIPEK = sessionkey
}
val checkWorkingKey = XORorANDorORfunction(
sessionkey,
"00000000000000FF00000000000000FF",
"^"
)
println("**********The value of the working key is $checkWorkingKey")
return
XORorANDorORfunction(sessionkey,"00000000000000FF00000000000000FF","^")
@RequiresApi(api = Build.VERSION_CODES.R)
private String getWorkingKey(String IPEK, String KSN) {
XORorAndorOR xoRorAndorORClass = new XORorAndorOR();
String initialIPEK = IPEK;
String ksn = leftPadding(KSN, 20, "0");
String sessionkey = "";
//Get ksn with a zero counter by ANDing it with 0000FFFFFFFFFFE00000
String newKSN = xoRorAndorORClass.XORorANDorORfunction(ksn,
"0000FFFFFFFFFFE00000", "&");
String counterKSN = leftPadding(KSN.substring(ksn.length() - 5), 16,
"0");
//get the number of binary associated with the counterKSN number
String newKSNtoleft16 = newKSN.substring(newKSN.length() - 16);
String counterKSNbin =
Integer.toBinaryString(Integer.parseInt(counterKSN));
String binarycount = counterKSNbin;
for( int i=0; i < counterKSNbin.length(); i++){
int len = binarycount.length();
String result = "";
if (binarycount.substring(0,1).equals("1")) {
result = rightPadding("1", len, "0");
binarycount = binarycount.substring(1);
}
else {
binarycount = binarycount.substring(1);
continue;
}
String counterKSN2 =
leftPadding(Integer.toHexString(Integer.parseInt(result, 2))
.toUpperCase(), 16, "0");
String newKSN2 =
xoRorAndorORClass.XORorANDorORfunction(newKSNtoleft16, counterKSN2, "|");
BlackBoxLogic blackBoxLogic = new BlackBoxLogic();//Instantiate BBL
sessionkey = blackBoxLogic.BBoxLogic(newKSN2, initialIPEK);
newKSNtoleft16 = newKSN2;
initialIPEK = sessionkey;
}
String checkWorkingKey = xoRorAndorORClass.XORorANDorORfunction(
sessionkey,
"00000000000000FF00000000000000FF",
);
return xoRorAndorORClass.XORorANDorORfunction(sessionkey,
"00000000000000FF00000000000000FF", "^");
}
public static string getSessionKey(string IPEK = "9F8011E7E71E483B ", string KSN =
"0000000006DDDDE00001")
{
string initialIPEK = IPEK, ksn = KSN.PadLeft(20, '0');
string sessionkey = "";
//Get ksn with a zero counter by ANDing it with FFFFFFFFFFFFFFE00000
string newKSN = XORorANDorORfuction(ksn, "0000FFFFFFFFFFE00000", "&");
string counterKSN = ksn.Substring(ksn.Length - 5).PadLeft(16, '0');
//get the number of binaray associated with the counterKSN number
string newKSNtoleft16 = newKSN.Substring(newKSN.Length - 16);
string counterKSNbin = Convert.ToString(Convert.ToInt32(counterKSN), 2);
int count = Convert.ToString(Convert.ToInt32(counterKSN), 2)
.Replace("0", "").Length;
string binarycount = counterKSNbin;
for (int i = 0; i < counterKSNbin.Length; i++)
{
int len = binarycount.Length; string result = "";
if (binarycount.Substring(0, 1) == "1")
{
result = "1".PadRight(len, '0');
binarycount = binarycount.Substring(1);
}
else { binarycount = binarycount.Substring(1); continue; }
string counterKSN2 = Convert.ToInt32(result, 2).ToString("X2").PadLeft(16,
'0');
string newKSN2 = XORorANDorORfuction(newKSNtoleft16, counterKSN2, "|");
sessionkey = BlackBoxLogic(newKSN2, initialIPEK);
newKSNtoleft16 = newKSN2;
initialIPEK = sessionkey;
}
return XORorANDorORfuction(sessionkey, "00000000000000FF00000000000000FF", "^");
}
Since we’re making use of 16bytes IPEK and IKSN, we have to tweak the Blackbox logic to accept
16bytes IPEK and IKSN.
NOTE: Sample leftPadding and rightPadding functions are implemented in Java below
public static String leftPadding(String input, int length, String fill){
String pad = String.format("%"+length+"s", "").replace(" ", fill) +
input.trim();
return pad.substring(pad.length() - length);
}
public static String rightPadding(String input, int length, String fill){
String pad = String.format("%-"+length+"s", input).replace(" ", fill);
return pad.substring(pad.length() - length);
}
fun BlackBoxLogic(ksn: String, iPek: String): String {
if (iPek.length < 32) {
println("The expected value IPEK $iPek and IKSN is $ksn")
val msg = XORorANDorORfunction(iPek, ksn, "^")
println("The expected value of the msg is $msg")
val desreslt = desEncrypt(msg, iPek)
println("The expected value of the desresult is $desreslt")
val rsesskey = XORorANDorORfunction(desreslt, iPek, "^")
println("The expected value of the session key during BBL is
$rsesskey")
return rsesskey
}
val current_sk = iPek
val ksn_mod = ksn
val leftIpek =
XORorANDorORfunction(
current_sk,
"FFFFFFFFFFFFFFFF0000000000000000",
"&"
).substring(16)
val rightIpek =
XORorANDorORfunction(current_sk, "0000000000000000FFFFFFFFFFFFFFFF",
"&").substring(16)
val message = XORorANDorORfunction(rightIpek, ksn_mod, "^")
val desresult = desEncrypt(message, leftIpek)
val rightSessionKey = XORorANDorORfunction(desresult, rightIpek, "^")
val resultCurrent_sk =
XORorANDorORfunction(current_sk, "C0C0C0C000000000C0C0C0C000000000",
"^")
val leftIpek2 = XORorANDorORfunction(
resultCurrent_sk,
"FFFFFFFFFFFFFFFF0000000000000000",
"&"
).substring(0, 16)
val rightIpek2 = XORorANDorORfunction(
resultCurrent_sk,
"0000000000000000FFFFFFFFFFFFFFFF",
"&"
).substring(16)
val message2 = XORorANDorORfunction(rightIpek2, ksn_mod, "^")
val desresult2 = desEncrypt(message2, leftIpek2)
val leftSessionKey = XORorANDorORfunction(desresult2, rightIpek2, "^")
return leftSessionKey + rightSessionKey
}
fun hexStringToByteArray(key: String) : ByteArray {
var result:ByteArray = ByteArray(0)
for (i in 0 until key.length step 2) {
result += Integer.parseInt(key.substring(i, (i + 2)), 16).toByte()
}
return result
}
fun byteArrayToHexString(key: ByteArray) : String {
var st = ""
for (b in key) {
st += String.format("%02X", b)
}
return st
}
private fun desEncrypt(desData: String, key: String): String {
val keyData = hexStringToByteArray(key)
val bout = ByteArrayOutputStream()
try {
val keySpec: KeySpec = DESKeySpec(keyData)
val key: SecretKey =
SecretKeyFactory.getInstance("DES").generateSecret(keySpec)
val cipher: Cipher = Cipher.getInstance("DES/ECB/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, key)
bout.write(cipher.doFinal(hexStringToByteArray(desData)))
} catch (e: Exception) {
print("Exception DES Encryption.. " + e.printStackTrace())
}
return byteArrayToHexString(bout.toByteArray()).substring(0, 16)
}
XORorAndorOR xoRorAndorORClass = new XORorAndorOR();
public String BBoxLogic(String ksn, String iPek) {
if (iPek.length() < 32) {
String msg = xoRorAndorORClass.XORorANDorORfunction(iPek, ksn, "^");
String desreslt = desEncrypt(msg, iPek);
String rsesskey = xoRorAndorORClass.XORorANDorORfunction(desreslt,
iPek, "^");
return rsesskey;
}
String current_sk = iPek;
String ksn_mod = ksn;
String leftIpek =
xoRorAndorORClass.XORorANDorORfunction(
current_sk,
"FFFFFFFFFFFFFFFF0000000000000000",
"&"
).substring(16);
String rightIpek =
xoRorAndorORClass.XORorANDorORfunction(
current_sk,
"0000000000000000FFFFFFFFFFFFFFFF",
"&"
).substring(16);
String message = xoRorAndorORClass.XORorANDorORfunction(rightIpek,
ksn_mod, "^");
String desresult = desEncrypt(message, leftIpek);
String rightSessionKey =
xoRorAndorORClass.XORorANDorORfunction(desresult, rightIpek, "^");
String resultCurrent_sk =
xoRorAndorORClass.XORorANDorORfunction(current_sk,
"C0C0C0C000000000C0C0C0C000000000",
"^"
);
String leftIpek2 = xoRorAndorORClass.XORorANDorORfunction(
resultCurrent_sk,
"FFFFFFFFFFFFFFFF0000000000000000",
"&"
).substring(0, 16);
String rightIpek2 = xoRorAndorORClass.XORorANDorORfunction(
resultCurrent_sk,
"0000000000000000FFFFFFFFFFFFFFFF",
"&"
).substring(16);
String message2 = xoRorAndorORClass.XORorANDorORfunction(rightIpek2,
ksn_mod, "^");
String desresult2 = desEncrypt(message2, leftIpek2);
String leftSessionKey =
xoRorAndorORClass.XORorANDorORfunction(desresult2, rightIpek2, "^");
return leftSessionKey + rightSessionKey;
}
public static byte[] hexStringToByteArray(String key){
byte[] result = new byte[key.length() / 2];
for (int i = 0; i < result.length; i++) {
int index = i * 2;
int j = Integer.parseInt(key.substring(index, index + 2), 16);
result[i] = (byte) j;
}
return result;
}
public static String byteArrayToHexString(byte[] key) {
String st = "";
for (byte b: key) {
st += String.format("%02X", b);
}
return st;
}
private String desEncrypt(String desData, String key) {
byte[] keyData = hexStringToByteArray(key);
byte[] desDataBytes = hexStringToByteArray(desData);
String finalValue = "";
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(keyData, "DES");
Cipher encryptCipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
encryptCipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
finalValue =
byteArrayToHexString(encryptCipher.doFinal(desDataBytes));
} catch (Exception e) {
e.printStackTrace();
}
return finalValue.substring(0,16);
}
public static string BlackBoxLogic(string ksn, string ipek)
{
if (ipek.Length < 32)
{
string msg = XORorANDorORfuction(ipek, ksn, "^");
string desreslt = DesEncrypt(msg, ipek);
string rsesskey = XORorANDorORfuction(desreslt, ipek, "^");
return rsesskey;
}
string current_sk = ipek;
string ksn_mod = ksn;
string leftIpek = XORorANDorORfuction(current_sk,
"FFFFFFFFFFFFFFFF0000000000000000", "&").Remove(16);
string rightIpek = XORorANDorORfuction(current_sk,
"0000000000000000FFFFFFFFFFFFFFFF", "&").Substring(16);
string message = XORorANDorORfuction(rightIpek, ksn_mod, "^");
string desresult = DesEncrypt(message, leftIpek);
string rightSessionKey = XORorANDorORfuction(desresult, rightIpek, "^");
string resultCurrent_sk = XORorANDorORfuction(current_sk,
"C0C0C0C000000000C0C0C0C000000000", "^");
string leftIpek2 = XORorANDorORfuction(resultCurrent_sk,
"FFFFFFFFFFFFFFFF0000000000000000", "&").Remove(16);
string rightIpek2 = XORorANDorORfuction(resultCurrent_sk,
"0000000000000000FFFFFFFFFFFFFFFF", "&").Substring(16);
string message2 = XORorANDorORfuction(rightIpek2, ksn_mod, "^");
string desresult2 = DesEncrypt(message2, leftIpek2);
string leftSessionKey = XORorANDorORfuction(desresult2, rightIpek2, "^");
string sessionkey = leftSessionKey + rightSessionKey;
return sessionkey;
}
public static byte[] StringToByteArray(String hex)
{
var numberChars = hex.Length;
var bytes = new byte[numberChars / 2];
for (var i = 0; i < numberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
public static string DesEncrypt(string desData, string key)
{
byte[] dData = StringToByteArray(desData);
byte[] keyData = StringToByteArray(key);
DESCryptoServiceProvider tdes = new DESCryptoServiceProvider();
tdes.Key = keyData;
tdes.Padding = PaddingMode.None;
tdes.Mode = CipherMode.ECB;
ICryptoTransform transform = tdes.CreateEncryptor();
byte[] result = transform.TransformFinalBlock(dData, 0, dData.Length);
return BitConverter.ToString(result).Replace("-", "");
}
To generate the Clear Pinblock;
fun encryptPinBlock(pan: String, pin: String): String {
val pan = pan.substring(pan.length - 13).take(12).padStart(16, '0')
println("The expected value of the encrypted pan is $pan")
val pin = '0' + pin.length.toString(16) + pin.padEnd(16, 'F')
println("The expected value of the clear pin is $pin")
return XORorANDorORfunction(pan, pin, "^") //the clear pinblock is returned here
}
public String encryptPinBlock(String pan, String pin) {
pan = leftPadding(pan.substring(pan.length()-13, pan.length()-1), 16,
"0");
pin = "0" + pin.length() + rightPadding(pin, 16, "F");
return xoRorAndorORClass.XORorANDorORfunction(pan, pin, "^"); //the clear
pin
public static string encryptPinBlock(string pan, string pin)
{
pan = pan.Substring(pan.Length - 13, 12).PadLeft(16, '0');
pin = pin.Length.ToString("X2") + pin.PadRight(16, 'F');
return XORorANDorORfuction(pan, pin, "^");
}
Below is the XORorANDorOR function;
fun XORorANDorORfunction(valueA: String, valueB: String, symbol: String =
"|"): String {
val a = valueA.toCharArray(); val b = valueB.toCharArray()
var result = ""
for (i in 0 until a.lastIndex + 1) {
if (symbol === "|") {
result += (Integer.parseInt(a[i].toString(),16).or
(Integer.parseInt(b[i].toString(),16)).toString(16).toUpperCase())
}
else if (symbol === "^") {
result += (Integer.parseInt(a[i].toString(), 16).xor
(Integer.parseInt(b[i].toString(),16)).toString(16).toUpperCase())
}
else {
result += (Integer.parseInt(a[i].toString(), 16).and
(Integer.parseInt(b[i].toString(),16))).toString(16).toUpperCase()
}
}
return result
}
public String XORorANDorORfunction(String valueA, String valueB, String
symbol) {
char[] a = valueA.toCharArray();
char[] b = valueB.toCharArray();
String result = "";
for (int i=0; i < a.length; i++) {
if (symbol.equals("|")) {
result +=
Integer.toHexString(Integer.parseInt(String.valueOf(a[i]), 16) |
Integer.parseInt(String.valueOf(b[i]),
16)).toUpperCase();
}
else if (symbol.equals("^")) {
result +=
Integer.toHexString(Integer.parseInt(String.valueOf(a[i]), 16) ^
Integer.parseInt(String.valueOf(b[i]),
16)).toUpperCase();
}
else {
result +=
Integer.toHexString(Integer.parseInt(String.valueOf(a[i]), 16) &
Integer.parseInt(String.valueOf(b[i]),
16)).toUpperCase();
}
}
return result;
public static string XORorANDorORfuction(string valueA, string valueB, string symbol = "|")
{
char[] a = valueA.ToCharArray();
char[] b = valueB.ToCharArray();
string result = "";
for (int i = 0; i < a.Length; i++)
{
if (symbol == "|") result += (Convert.ToInt32(a[i].ToString(), 16) |
Convert.ToInt32(b[i].ToString(), 16)).ToString("x").ToUpper();
else if (symbol == "^") result += (Convert.ToInt32(a[i].ToString(), 16) ^
Convert.ToInt32(b[i].ToString(), 16)).ToString("x").ToUpper();
else result += (Convert.ToInt32(a[i].ToString(), 16) &
Convert.ToInt32(b[i].ToString(), 16)).ToString("x").ToUpper();
}
return result;
}
- Do a DES encryption of the working key and the result from step1. (Where working key is
the key value)
fun DesEncryptDukpt(workingKey: String, pan: String, clearPin: String): String {
val pinBlock = XORorANDorORfunction(workingKey, encryptPinBlock(pan, clearPin), "^")
val keyData = hexStringToByteArray(workingKey)
val bout = ByteArrayOutputStream()
try {
val keySpec: KeySpec = DESKeySpec(keyData)
val key: SecretKey = SecretKeyFactory.getInstance("DES").generateSecret(keySpec)
val cipher: Cipher = Cipher.getInstance("DES/ECB/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, key)
bout.write(cipher.doFinal(hexStringToByteArray(pinBlock))) //DES Encryption
} catch (e: Exception) {
println("Exception .. " + e.message)
}
return XORorANDorORfunction(workingKey, byteArrayToHexString(bout.toByteArray()).substring(0, 16), "^")
}
public String DesEncryptDukpt(String workingKey, String pan, String clearPin)
{
String pinBlock = xoRorAndorORClass.XORorANDorORfunction(workingKey,
encryptPinBlock(pan, clearPin), "^");
byte[] keyData = hexStringToByteArray(workingKey);
String finalValue = "";
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(keyData, "DES");
Cipher encryptCipher = Cipher.getInstance("DES");
encryptCipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] hexPinBlock = hexStringToByteArray(pinBlock);
finalValue =
byteArrayToHexString(encryptCipher.doFinal(hexPinBlock));
} catch (Exception e) {
e.printStackTrace();
}
return xoRorAndorORClass.XORorANDorORfunction(workingKey,
finalValue.substring(0,16), "^");
}
public static string DesEncryptDukpt(string workingKey, string pan, string clearPin)
{
string pinblock = XORorANDorORfuction(workingKey, encryptPinBlock(pan, clearPin),
"^");
byte[] dData = StringToByteArray(pinblock);
byte[] keyData = StringToByteArray(workingKey);
DESCryptoServiceProvider tdes = new DESCryptoServiceProvider();
tdes.Key = keyData;
tdes.Padding = PaddingMode.None;
tdes.Mode = CipherMode.ECB;
ICryptoTransform transform = tdes.CreateEncryptor();
byte[] result = transform.TransformFinalBlock(dData, 0, dData.Length);
return XORorANDorORfuction(workingKey, BitConverter.ToString(result).Replace("-",
""), "^");
}
- Final step is to do an XOR of the result from the DES Encryption from above with the
working key:
desEncryptionResult =
bout.write(cipher.doFinal(hexStringToByteArray(pinBlock)))
} catch (e: Exception) {
println("Exception .. " + e.message)
}
The final value of the Pinblock is derived like so…
return XORorANDorORfunction(
workingKey, byteArrayToHexString(bout.toByteArray()).substring(
0,
16
), "^"
)
val pinBlock = DesEncryptDukpt(
workingKey = getWorkingKey(),
pan = "1234567890123456",
pin = "1234"
)
println("****************The expected value of the pinblock is: $pinBlock")
NOTE: Please ensure that the KSN value you’re passing into the transaction payload is the
Hexadecimal value of the KSN counter used to generate the Pinblock.
i.e., if KSN counter 100 was used to generate the Pinblock like 0000000006DDDDE00100 the KSN
passed into the Transaction payload should be 0000000006DDDDE00064 and be reduced to 8bytes
like 000006DDDDE00064.
CONVENTION
● Status - Status code of response.
● All the possible responses are listed under ‘Responses’ for the call.
● All requests/responses are in XML format.
● All request parameters are mandatory unless explicitly marked as [optional]
Updated almost 3 years ago