Created
May 30, 2023 17:54
-
-
Save hpkaushik121/ba954703e48c02a750e0917dd37fcbeb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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