Smart Card APDU Test

HU·2021년 11월 12일
0

Smart Card

목록 보기
2/2

Authentication Flow

사용한 개발 도구 및 버전 STS, JDK version: 1.8

순서

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)

  • 이 과정 중 Card에서 HOST Random(8byte) 값을 받아 challenge(랜덤 값), cryptogram을 생성한다.
// 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을 추출한다.

  • card challenge의 앞 2byte는 sequence counter(카드 인증에 성공한 횟수)를 의미한다.
// 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키는 Initial Vector(8byte의 0 패딩), Input Data(0182 + sequence counter + 12byte의 0 패딩), Master Key를 triple DES(CBC 방식) 암호화해서 생성
// 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은 ENC Session 생성과 비슷하게 Initial vector, Input(Host chellenge + Sequence counter + Card random{6byte} + 80 padding{8byte}), ENC Session key를 triple DES(CBC 방식) 암호화를 통해 생성
// 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을 생성한다.

  • IV(8byte 0패딩), Input Data(sequence counter + Card Random + Host Random + 80 padding{8byte}), ENC Session Key를 Triple DES(CBC 방식) 암호화를 통해 생성
// 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를 생성한다.

  • IV(8byte의 0 padding) + Input(0101 + sequence counter + 12byte의 0 padding) + master key를 이용해서 Triple DES(CBC 방식) 암호화를 하여 생성
// 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을 생성한다.

  • IV(0패딩{8byte}), Input Data(APUD data(84 82 00 00 10) + HOST Cryptogram + 80 padding{8byte}}, MAC Session을 이용해서 Retail-DES 암호화를 통해 생성한다.
// 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을 시도한다.

  • 84 82 00 00 10 + HOST Cryptogram + MAC Cryptogram을 송신하여 "9000"을 수신하면 인증에 성공한 것이다.
// 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;
}


참고한 코드의 출처

smartcardio 사용하기(2)

profile
지식 쌓기

0개의 댓글