Authentication Flow
1. 리더기의 목록을 가져온다.
// TerminalFactory 인스턴스 얻기.
TerminalFactory terminalFactory = TerminalFactory.getInstance("PC/SC", null);
// 사용 가능한 CardTerminal 가져오기
List<CardTerminal> list = terminalFactory.terminals().list();
public String[] getReaderList() throws CardException {
List<CardTerminal> list = terminalFactory.terminals().list();
String[] readerList = new String[list.size()];
for(int i = 0;i<list.size();i++) {
readerList[i] = ((CardTerminal)list.get(i)).getName();
}
return readerList;
}
2. 가져온 리더기에 카드를 연결한다.
// 연결할 카드 터미널(스마트카드 리더) 가져오기
CardTerminal cardTerminal = terminalFactory.terminals().getTerminal("리더기 이름을 넣는다.");
// 프로토콜 설정하기
Card card = cardTerminal.connect("*");
// 채널 가져오기
CardChannel cardChannel = card.getBasicChannel();
public boolean connect(String readerName) {
try {
CardTerminal cardTerminal = terminalFactory.terminals().getTerminal(readerName);
card = cardTerminal.connect("*");
cardChannel = card.getBasicChannel();
return true;
} catch(Exception e) {
e.printStackTrace();
return false;
}
}
3. APDU를 전송해서 응답을 받는다. (Select Response)
public ResponseAPDU apdu(CommandAPDU cmd) {
try {
return cardChannel.transmit(cmd);
} catch(Exception e) {
e.printStackTrace();
return null;
}
}
4. HOST Random 값을 더해서 다시 APDU를 전송하고 응답을 받는다. (Initialize Update & Response)
// Initial update & Response
private String initialUpdate(){
byte[] bytes = ScmsUtils.hexToByteArray("80" + "50" + "00" + "00" + "08" + this.hostRandom);
CommandAPDU commandApdu = new CommandAPDU(bytes);
ResponseAPDU responseApdu = responseAPDU(commandApdu);
return ScmsUtils.byteArrayToHex(responseApdu.getBytes());
}
5. HOST 측에서 Response 받은 결과 값을 파싱해서 Card challenge, cryptogram을 추출한다.
// Initialize update Response
String responseApduHex = initialUpdate();
// parsing
//this.keyDiversificationData = responseApduHex.substring(0, 20);
//this.keyInformation = responseApduHex.substring(20, 24);
this.sequenceCounter = responseApduHex.substring(24, 28);
this.cardChallenge = responseApduHex.substring(28, 40);
this.cardCryptogram = responseApduHex.substring(40, responseApduHex.length() - 4);
6-1. HOST 측에서 파싱을 통해 얻은 card chellenge를 이용해서 card cryptogram을 만들고 이를 Card 측의 Card cryptogram과 서로 일치한지 비교해야한다.
6-2 ~ 6-4번 참고
6-2. HOST 측에서 card cryptogram을 만들기 위해서는 ENC Session키를 생성한다.
// ENC Session key 구하기.
public String encSession(){
String iv = "0000000000000000"; // 8 byte. 고정.
String inputData = "0182" + this.sequenceCounter + "000000000000000000000000";
String key = "404142434445464748494A4B4C4D4E4F"; // 16 byte.
key = key + key.substring(0, 16); // 24 byte. 고정된 마스터 키.
return this.tripleDes.tripleDesEncrypt(iv, inputData, key); // DES encrypt. 16byte.
}
6-3. 생성한 ENC Session key로 Card cryptogram을 생성한다.
// Card Cryptogram 구하기
public String createCardCryptogram(final String encSession) {
String iv = "0000000000000000";
String inputData = this.hostRandom + this.sequenceCounter + this.cardChallenge + "8000000000000000";
String cryptogram = this.tripleDes.tripleDesEncrypt(iv, inputData, encSession + encSession.substring(0, 16));
cryptogram = cryptogram.substring(cryptogram.length() - 16);
return cryptogram;
}
6-4. 위 과정을 통해 HOST 측과 Car 측에서 만든 각각의 card cryptogram이 서로 일치한지 확인한다.
if ( !this.cardCryptogram.equals(cardCryptogramInHost) ) {
return null;
}
7. cryptogram이 서로 일치할 경우 HOST cryptogram을 생성한다.
// Host Cryptogram 구하기
public String createHostCryptogram(final String encSession) {
String iv = "0000000000000000";// 8 byte. 고정.
String inputData = this.sequenceCounter + this.cardChallenge + this.hostRandom + "8000000000000000";
String hostCryptogram = this.tripleDes.tripleDesEncrypt(iv, inputData, encSession + encSession.substring(0, 16));
hostCryptogram = hostCryptogram.substring(hostCryptogram.length() - 16);
return hostCryptogram;
}
8. MAC Session key를 생성한다.
// MAC Session key 구하기.
public String macSession() {
String iv = "0000000000000000"; // 8 byte. 고정.
String inputData = "0101" + this.sequenceCounter + "000000000000000000000000";
String key = "404142434445464748494A4B4C4D4E4F"; // 16 byte.
key = key + key.substring(0, 16); // 24 byte. 고정된 마스터 키.
String macSession = this.tripleDes.tripleDesEncrypt(iv, inputData, key); // DES encrypt. 16byte.
return macSession;
}
9. C-MAC을 생성한다.
// MAC Cryptogram 구하기.
private String macCryptogram(final String hostCryptogram, final String macSession) throws Exception {
String iv = "0000000000000000";
String apdu = "84" + "82" + "00" + "00" + "10" + hostCryptogram;
String key = macSession;
byte[] ivByte = ScmsUtils.hexToByteArray(iv);
byte[] apduByte = ScmsUtils.hexToByteArray(apdu);
byte[] keyByte = ScmsUtils.hexToByteArray(key);
byte[] test = SinglePlusFinalTriple.generateCmac(apduByte, keyByte, ivByte);
String macCryptogram = ScmsUtils.byteArrayToHex(test);
return macCryptogram;
}
10. 마지막으로 APDU Transmit을 시도한다.
// External Authenticate
private String externalAuthenticate(final String cryptogramInHost, final String macSession) {
byte[] bytes = ScmsUtils.hexToByteArray("84" + "82" + "00" + "00" + "10" + cryptogramInHost + macSession);
CommandAPDU commandApdu = new CommandAPDU(bytes);
ResponseAPDU responseApdu = responseAPDU(commandApdu);
String transmitResult = ScmsUtils.byteArrayToHex(responseApdu.getBytes());
return transmitResult;
}