ID TECH
문의
기술 게시물 전체 보기

기술 포스트

신용카드 데이터 복호화 방법, 2부

고객들이 자주 묻는 질문: ID TECH 신용카드 리더기에서 출력되는 데이터를 어떻게 복호화할 수 있나요?

정답: 데이터를 암호화하는 데 사용된 알고리즘과 키를 알아야 합니다. 그러면 해당 키를 사용하여 데이터를 복호화할 수 있습니다.

오늘날 신용카드 데이터는 거의 대부분 DUKPT라는 특수 키 관리 방식을 통해 생성된 일회용 키로 암호화됩니다. DUKPT (Derived Unique Key Per Transaction의 약자)는 거래마다 고유한 키를 사용하는 방식입니다. DUKPT 환경에서는 각 거래마다 별도의 키가 생성되며, 해당 키는 다른 거래에 재사용할 수 없습니다. 따라서 재전송 공격(replay attack)은 원천적으로 불가능합니다.

그렇다면 특정 거래를 복호화할 DUKPT 키는 어떻게 도출할 수 있을까요? 일반적으로는 해당 거래의 키 일련번호(KSN)와 신용카드 리더기에 주입된 초기 키인 IPEK(Initial PIN Encryption Key)가 필요합니다. IPEK는 카드 리더기에 직접 주입되지 않는 최고 기밀 키인 BDK(Base Derivation Key)로부터 파생됩니다. BDK와 달리 IPEK는 특정 물리적 장치에 고유하게 할당됩니다. (하나의 BDK로 여러 개의 고유한 IPEK를 생성할 수 있습니다.) 장치의 IPEK를 모르는 경우(IPEK는 어디에도 기록되지 않으므로 알 수 없는 것이 당연합니다), KSN과 Base Derivation Key를 사용하여 IPEK를 도출할 수 있습니다. 구체적인 방법은 본 문서의 1부.

세션 키(워킹 키 또는 단순히 '데이터 키'라고도 함)를 도출하는 과정은 실질적으로 3단계로 이루어집니다. 각 단계는 다음과 같습니다:

1. BDK와 KSN을 사용하여 IPEK를 도출합니다. (자세한 방법은 본 문서의 1부 을 참조하십시오.)

2. ANSI X9.24 (DUKPT) 키 파생 알고리즘을 사용하여 KSN과 IPEK로부터 기본 키(초기 "파생 키")를 도출합니다.

3. 2단계에서 도출한 파생 키를 Data Key, PIN Key, MAC Key 중 원하는 형태로 변환합니다. (대부분의 신용카드 리더기는 트랜잭션 세션 키로 Data 변형 키를 사용하도록 설정되어 있지만, 일부는 PIN 변형 키를 사용하도록 설정된 경우도 있습니다.)

3단계 프로세스 중 가장 까다로운 부분인 "파생 키" 도출(2단계)에 대해 먼저 살펴보겠습니다. 파생 키를 얻은 후에는 이를 Data, PIN, 또는 MAC 변형으로 변환하는 방법을 설명할 예정이며, 이 변환 과정은 비교적 간단합니다.

아래에서 상당량의 의사 코드(pseudocode)를 사용할 예정이지만, 다음의 모든 단계에 대한 완전하고 실행 가능한 소스 코드(JavaScript)는 널리 사용되는 저희의 암호화/복호화 도구에서 확인하실 수 있습니다. (아직 사용해 보지 않으셨다면 지금 바로 시도해 보세요. 최신 브라우저라면 어디서든 작동하는 독립형 웹 페이지입니다.)

키 파생

Data, PIN, 또는 MAC 변형 키의 기반이 되는 기본 키를 도출하려면 트랜잭션 KSN과 IPEK가 필요합니다. 이 두 값을 확보한 후(다시 한번 강조하지만: 이 시리즈의 1부참조), 다음 단계를 수행하십시오:

1. 10바이트 KSN의 하위(오른쪽) 8바이트를 추출합니다. 상위 2바이트는 버립니다.

2. 8바이트 KSN의 마스킹된 버전을 저장할 BaseKSN 변수를 생성합니다. 1단계의 8바이트 KSN과 (16진수) 값 0xFFFFFFFFFFE00000을 AND 연산하여 마스킹된 버전을 구합니다.

3. 원본(마스킹되지 않은!) 10바이트 KSN의 하위 3바이트와 0x1FFFFF를 AND 연산하여 카운터 비트를 추출합니다. (KSN의 하위 21비트가 트랜잭션 카운터를 구성한다는 점을 기억하십시오.) 이 값을 변수명 counter.

4. 16바이트 IPEK를 curKey.

5. 이제 루프를 설정해야 합니다. 루프를 반복할 때마다 카운터 비트를 검사합니다(최상위 비트, 즉 21번째 비트부터 시작하여, 두 번째 반복 시 20번째 비트, 그 다음 19번째 비트 순으로 진행). 켜져 있는 비트를 발견할 때마다 해당 비트를 BaseKSN에 OR 연산으로 반영한 후, generateKey() 를 호출하여 curKey을 업데이트합니다. BaseKSN은 루프를 반복할 때마다 비트를 누적하며, curKey 값은 켜진 카운터 비트를 발견할 때마다 갱신됩니다.

그렇다면 generateKey() 는 어떤 역할을 할까요? 좋은 질문입니다! 프로그래밍 언어에서 BigInteger 연산을 지원한다면, 코드는 대략 다음과 같은 형태가 됩니다:

이해하셨나요. 16바이트 키에 마스킹을 적용한 후, 이를 사용하여 8바이트 ksn 값을 암호화하면 새로운 키의 왼쪽 절반(왼쪽 8바이트)이 생성됩니다. 새 키의 오른쪽 절반은 동일한 ksn을 사용하되, 마스킹이 적용되지 않은 키로 생성한 암호문입니다.

마지막으로, encryptRegister() 의 모습은 다음과 같습니다:

여기서 Cipher Block Chaining은 실질적으로 의미가 없습니다. 암호화 대상이 8바이트 값(데이터 블록 하나)이기 때문에 "체이닝"할 내용이 없기 때문입니다. 이 옵션이 코드에 포함된 이유는, 단순히 암호화 루틴이 체이닝 사용 여부를 지정하는 매개변수를 요구하기 때문입니다.

또한, 암호화에 8바이트 키가 사용된다는 점도 주목할 필요가 있습니다. TDES는 키가 8바이트에 불과할 경우 단일 DES로 동작합니다. 이는 8바이트 키를 트리플 DES에 적용하면 암호화/복호화/암호화 사이클이 단일 암호화와 동일한 결과를 내기 때문입니다.

이 과정을 쉽게 설명하면, 해당 루틴은 16바이트 키의 상위 8바이트를 사용하여 특별한 값을 암호화합니다. 이 특별한 값은 키의 하위 8바이트와 8바이트 KSN을 XOR 연산한 결과입니다. 최종 결과는 KSN의 단방향 해시값이 됩니다.

이 모든 과정을 종합하면, 위 5단계의 루프는 curKey 값을 생성하며, 이 값은 Data, PIN, 또는 MAC 파생 키를 도출하는 기반 키(basis key)로 활용됩니다. (5단계의 루프는 최종적으로 기반 키인 curKey를 반환하는 함수의 일부이거나, 그래야 합니다.)

이제 세 가지 "키 파생(key variant)" 옵션을 더 자세히 살펴볼 차례입니다.

Data, PIN, MAC 키 파생(Key Variant) 생성

ANSI X9.24에서는 DUKPT 키가 MAC, PIN, Data의 세 가지 최종 형태, 즉 파생(variant)으로 사용될 수 있도록 허용합니다. 각 키 유형의 용도에 대한 논의는 잠시 미루고, 우선 이들이 어떻게 생성되는지에 집중하겠습니다.

모든 파생 키의 출발점은 DUKPT 기반 키(위 5단계에서 curKey 로 칭한 파생 키)입니다. MAC 파생 키를 얻으려면, 기반 키("파생 키")를 특정 상수값과 XOR 연산하기만 하면 됩니다:

PIN 변형 키도 동일한 방식으로 생성되지만, 서로 다른 상수를 사용합니다:

Data 변형 키에는 또 다른 상수가 필요합니다:

MAC 및 PIN 변형 키의 경우, XOR 연산이 해당 세션 키 생성의 최종 단계입니다. Data 변형 키의 경우에는 단방향 해시를 적용하는 추가 단계를 수행하는 것이 일반적입니다(Data 키를 MAC 키로 역변환할 수 있는 가능성을 원천적으로 차단하기 위함입니다). 의사 코드(pseudocode)로 표현하면 다음과 같습니다:

설명하자면 다음과 같습니다: 먼저 EDE3 확장 방식을 사용하여 파생 키의 24바이트 버전을 생성합니다. (이는 16바이트 키의 첫 8바이트를 키의 끝에 복사하여, 첫 번째와 마지막 8바이트가 동일한 24바이트 키를 만드는 것을 의미합니다.) 해당 키를 사용하여 16바이트 파생 키의 앞쪽 8바이트를 TDES 암호화함으로써 8바이트 암호문을 생성합니다. 이것이 최종 데이터 키의 왼쪽 절반입니다. 오른쪽 절반을 생성하려면, 동일한 24바이트 키를 사용하여 파생 키(derivedKey)의 하위 8바이트를 암호화합니다. 두 개의 8바이트 암호문(왼쪽 및 오른쪽 부분)을 결합하면 완성됩니다.

검증용 기준값

직접 구현을 시도하고 있다면, 알려진 기준값과 결과를 비교하여 검증해 보실 수 있습니다. 먼저 16바이트 BDK로 0123456789ABCDEFFEDCBA9876543210(16진수)을 사용하십시오. 이는 일반적으로 널리 사용되는 테스트 키 값입니다. 테스트 KSN 값으로는 629949012C0000000003을 사용해 보십시오. 이 두 값을 통해 D2943CCF80F42E88E23C12D1162FD547의 IPEK를 도출할 수 있어야 합니다. (IPEK 도출 방법을 확인하려면 본 문서의 1부 을 참조하십시오.)

앞서 언급한 IPEK를 시작점으로 하여 "파생 키"(또는 DUKPT 기반 키)를 도출할 때, 다음과 같은 값들을 확인할 수 있어야 합니다:

KSN 카운터 루프의 "if" 문을 처음 통과할 때, BaseKSN 의 값은 49012C0000000002가 되며, curKey 은 이후 B58CDA5C7A1E9FF5E7335B988626D01A가 됩니다. generateKey().

카운터 루프의 "if" 구문을 두 번째로 통과하면, 카운터의 "ON" 비트 두 개를 모두 처리한 상태가 됩니다. 따라서 BaseKSN은 49012C0000000003이 되고, 그 결과 curKey는 841AB7B94ED086EBC2B8A8385DA7DFCA가 됩니다. (참고로, 카운터 비트를 MSB 우선으로 BaseKSN에 OR 연산하는 방식입니다. 카운터가 0x0F로 끝나는 경우, BaseKSN은 49012C0000000008에서 49012C000000000C, 49012C000000000E, 49012C000000000F 순으로 비트를 순차적으로 OR 연산하며 변화합니다.)

따라서 "파생 키(derived key)"는 841AB7B94ED086EBC2B8A8385DA7DFCA가 됩니다.

데이터 변형 상수(data-variant constant)와 XOR 연산을 수행하면, 파생 키는 841AB7B94E2F86EBC2B8A8385D58DFCA로 변경됩니다.

위 값의 상위 절반과 하위 절반을 각각 암호화할 때, EDE3 확장 키 841AB7B94E2F86EBC2B8A8385D58DFCA841AB7B94E2F86EB를 사용하면 최종 데이터 키로 F739AEF595D3877F731782D28BB6AC4F를 얻을 수 있습니다. 즉, 24바이트 EDE3 키 값으로 841AB7B94E2F86EB를 암호화하면 암호문 F739AEF595D3877F가 생성되고, 동일한 키로 C2B8A8385D58DFCA를 암호화하면 암호문 731782D28BB6AC4F가 생성됩니다. 두 암호문을 연결하면 작업이 완료됩니다. 이제 KSN이 629949012C0000000003인 거래의 데이터를 복호화할 수 있는 16바이트 키를 확보하게 됩니다.

예제 코드: ID TECH 암호화/복호화 도구

여기서 다룬 모든 DUKPT 키 파생 루틴의 전체 소스 코드를 확인하려면, HTML 및 JavaScript 기반의 당사 도구를 다운로드하여 소스 코드를 살펴보시기 바랍니다. 이 도구는 암호화/복호화 도구IPEK 계산, 세 가지 DUKPT 키 변형 파생, TDES 또는 AES를 이용한 데이터 암호화·복호화 등 다양한 기능을 제공합니다. Chrome의 강력한 개발자 콘솔 도구를 활용하면 도구의 코드를 실시간으로 단계별로 실행하고, 변수 값 변화를 확인하며, 중단점을 설정하는 것도 가능합니다. 학습에 매우 유용한 도구이며, 무료로 제공됩니다. 지금 바로 암호화/복호화 도구를 다운로드하여 직접 활용해 보시고, 주변에도 알려주세요. 제가 아는 한, 웹상에서 순수 JavaScript로 구현된 DUKPT 구현체는 이것이 유일합니다.