Skip to content

Instantly share code, notes, and snippets.

@hpkaushik121
Created May 30, 2023 17:54
Show Gist options
  • Save hpkaushik121/ba954703e48c02a750e0917dd37fcbeb to your computer and use it in GitHub Desktop.
Save hpkaushik121/ba954703e48c02a750e0917dd37fcbeb to your computer and use it in GitHub Desktop.
/**
* The VERIFY command is used for OFFLINE authentication.
* The Transaction PIN Data (input) is compared with the Reference PIN Data
* stored in the application (ICC).
*
* NOTE: The EMV command "Offline PIN" (plaintext) is vulnerable to a Man-in-the-middle attack.
* Terminals should request online pin verification instead (or encipher PIN) !!
*
* Case 3 C-APDU
*
* @param pin the PIN to verify
* @param transmitInPlaintext
* @return
*/
public static byte[] verifyPIN(char[] pin, boolean transmitInPlaintext) {
if (pin.length < 4 || pin.length > 12) { //0x0C
throw new RuntimeException("Invalid PIN length. Must be in the range 4 to 12. Length=" + pin.length);
}
for(char c : pin){
if(!Character.isDigit(c)) {
throw new RuntimeException("Only decimal digits allowed in PIN");
}
}
byte[] cmd = new byte[13];
cmd[0] = 0x00;
cmd[1] = 0x20;
cmd[2] = 0x00;
//EMV book 3 Table 23 (page 88) lists 7 qualifiers,
//but only 2 are relevant in our case (hence the use of boolean)
byte p2QualifierPlaintextPIN = (byte) 0x80;
byte p2QualifierEncipheredPIN = (byte) 0x88;
if (transmitInPlaintext) {
cmd[3] = p2QualifierPlaintextPIN;
cmd[4] = 0x08; //Lc
cmd[5] = (byte)(0x20 | pin.length); //Control field (binary 0010xxxx)
//The remaining 8 bytes is the plaintext Offline PIN Block. This block is split into nibbles (4 bits)
Arrays.fill(cmd, 6, cmd.length, (byte)0xFF); //Filler bytes
boolean highNibble = true; //Alternate between high and low nibble
for (int i = 0; i < pin.length; i++) { //Put each PIN digit into its own nibble
int pos = i / 2;
int digit = Character.digit(pin[i], 10);
if (highNibble) {
cmd[6 + pos] &= (byte) 0x0F; //Clear bits
cmd[6 + pos] |= (byte) (digit << 4);
} else {
cmd[6 + pos] &= (byte) 0xF0; //Clear bits
cmd[6 + pos] |= (byte) (digit);
}
highNibble = !highNibble;
}
} else {
cmd[3] = p2QualifierEncipheredPIN;
//Encipher PIN
//TODO: Implement enciphered PIN
throw new UnsupportedOperationException("Enciphered PIN not implemented");
}
return cmd;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment