ID TECH
すべての技術記事

技術記事

クレジットカードデータの復号方法 パート II

お客様からよくいただくご質問: ID TECHのクレジットカードリーダーから出力されるデータを復号するにはどうすればよいですか?

答え:データの暗号化に使用されたアルゴリズムと、使用された鍵を知る必要があります。それが分かれば、その鍵を使ってデータを復号できます。

近年では、ほぼすべてのクレジットカードデータが、特別な鍵管理方式によって取得された使い捨ての鍵を用いて暗号化されています。この方式は次のように呼ばれています。 DUKPT (Derived Unique Key Per Transactionの略)。DUKPTの世界では、すべてのトランザクションが独自の鍵を持つという点を理解することが重要です。鍵を他のトランザクションで再利用することはできないため、リプレイ攻撃は不可能です。

ここで疑問が生じます。特定のトランザクションを解錠できるDUKPT鍵を、どのようにして導出すればよいのでしょうか?一般的には、そのトランザクションのKey Serial Number (KSN)と、IPEK(クレジットカードリーダーに注入された初期鍵)と呼ばれる特別な値が必要です。IPEKは、BDK (Base Derivation Key)と呼ばれる極秘の鍵(こちらはカードリーダーに注入されることはありません)から導出されます。BDKと異なり、IPEKは特定の物理デバイス固有のものです(1つのBDKから多数のユニークなIPEKを生成できます)。お使いのデバイスのIPEKが分からない場合(IPEKはどこにも記録されないため、通常は分からないものです)、KSNとBase Derivation Keyから次に説明する手法でIPEKを導出できます。 本記事のパート I.

セッション鍵(ワーキング鍵、または単に「データ鍵」と呼ばれることもあります)の導出は、3ステップのプロセスとして捉えるのが最適です。手順は以下のとおりです:

1. BDKとKSNを使用してIPEKを導出します。( 本記事のパート I を参照してください。詳しい方法が記載されています。)

2. ANSI X9.24 (DUKPT)鍵導出アルゴリズムを使用し、KSNとIPEKから基底鍵、つまり初期の「導出鍵」を生成します。

3. ステップ2の導出鍵を、Data Key、PIN Key、またはMAC Keyのいずれかに変換します。(ほとんどのクレジットカードリーダーはトランザクションのセッション鍵としてDataバリアント鍵を使用するように設定されていますが、一部はPINバリアントを使用するように設定されている点に注意してください。)

それでは、「導出鍵」(ステップ2)を取得するために必要な作業を見ていきましょう。これは3ステップのプロセスの中で最も手間のかかる部分です。導出鍵が得られたら、それをData、PIN、MACの各バリアントへ変換する方法について説明します。こちらは比較的容易です。

以下では擬似コードを多用しますが、ご安心ください。以下のすべてのステップについて、完全に動作するソースコード(JavaScript)が、人気の高い当社の以下のツールで公開されています。 暗号化/復号ツール(まだ試していない方は、ぜひお試しください。最新のブラウザで動作する自己完結型のWebページです。)

鍵の導出

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ビット目から始めます。2回目のループでは20ビット目、次に19ビット目という具合です)。オンになっているビットを見つけるたびに、それをBaseKSNにORで結合し、次の関数を呼び出します。 generateKey() で更新します。 curKeyBaseKSNはループを経るごとにビットを蓄積し、curKeyの値はオンになっているカウンタービットを見つけるたびに更新されていきます。

それでは generateKey() は何をするのでしょうか?よくぞ聞いてくださいました!お使いのプログラミング言語がBigInteger演算をサポートしている場合、コードは次のようになります:

さて、ご覧のとおり、16バイトの鍵がマスクされ、その鍵を使って8バイトの ksn 値を暗号化することで、新しい鍵の左半分(左の8バイト)を取得しています。新しい鍵の右半分は、同じksnから生成された暗号文ですが、マスクされていない鍵を使用しています。

最後に、次の関数がどのようになっているかを知る必要があります。 encryptRegister() こちらがその内容です:

ここでの Cipher Block Chaining(暗号ブロック連鎖)は実質的に意味を持たない点に注意してください。なぜなら、暗号化対象は 8 バイトの値(データ 1 ブロック)であり、「連鎖」させる対象がないからです。コードに含まれているのは、単に暗号化ルーチンが連鎖の有無を示すパラメータを要求する仕様になっているためです。

また、暗号化に 8 バイトの鍵を用いている点にも注意してください。TDES は鍵長が 8 バイトしかない場合、シングル DES にデフォルトで切り替わります。これは、トリプル DES において 8 バイトの鍵を使うと、暗号化/復号/暗号化のサイクルが単一の暗号化と等価になってしまうためです。

平易な言葉で説明すると、このルーチンは 16 バイトの鍵の上位 8 バイトを使って、鍵の下位 8 バイトと(8 バイトの)ksn を XOR した特殊な値を暗号化します。その結果が ksn の一方向ハッシュとなります。

これらをすべて組み合わせると、上記のステップ 5 のループは次のような curKey 値を生成し、それが最終的に Data、PIN、または MAC のバリアントを派生させるベースとなる基底鍵になります。(ステップ 5 のループは、basis key である curKey を返す関数の一部であるはずです。)

それでは、この 3 つの「鍵バリアント」オプションについて、さらに詳しく見ていきましょう。

Data、PIN、MAC 鍵バリアントの生成

ANSI X9.24 では、DUKPT 鍵が最終的に取りうる形態として、MAC、PIN、Data と呼ばれる 3 種類のバリアントを認めています。これらの鍵タイプが何に使われるかについての議論は後回しにして、ここではその生成方法に集中して説明します。

いずれのバリアントの出発点も、DUKPT の基底鍵(前述のステップ 5 で curKey と呼んだ派生鍵)です。MAC バリアントを得るには、基底鍵(「派生鍵」)と特定の定数を XOR するだけです。

PIN バリアントも同様に作成されますが、異なる定数を使用します。

Data バリアントには、さらに別の定数が必要です。

MAC および PIN バリアントでは、XOR 演算が該当セッション鍵を生成する最終ステップとなります。Data バリアントでは、もう 1 ステップ、一方向ハッシュを伴う処理を行うのが通例です(Data 鍵から MAC 鍵への逆変換の可能性を排除するためです)。疑似コードで示すと次のようになります。

日本語で言うと、まず EDE3 拡張方式を使って派生鍵の 24 バイト版を取得します。(これは単に 16 バイト鍵の最初の 8 バイトを鍵の末尾にコピーして、最初と最後の 8 バイトが同一となる 24 バイト鍵を作るだけのことです。)この鍵を使って 16 バイト派生鍵の最初の 8 バイトを TDES 暗号化し、8 バイトの暗号文を得ます。これが最終的なデータ鍵の左半分になります。右半分を作るには、同じ 24 バイト鍵を使って derivedKey の下位 8 バイトを暗号化します。2 つの 8 バイト暗号文(左半分と右半分)を結合すれば完成です。

既知の正解値

ご自宅でこれを試そうとしている場合は、既知の正解値と照合して作業を確認すると良いでしょう。まず、16 バイトの BDK として 0123456789ABCDEFFEDCBA9876543210(16 進数)から始めます。これは誰もがテスト用に使う鍵値です。テスト用 KSN 値として 629949012C0000000003 を試してみてください。この 2 つの値から、D2943CCF80F42E88E23C12D1162FD547 という IPEK を導出できるはずです。(IPEK の導出方法を確認したい場合は、 本記事のパート I を参照してください。)

前述の IPEK から始めて「派生鍵」(または DUKPT 基底鍵)を導出すると、以下の値が得られるはずです。

KSN カウンタ・ループの「if」を最初に通過する際、 BaseKSN は 49012C0000000002 となり、 curKey の処理後に B58CDA5C7A1E9FF5E7335B988626D01A になります。 generateKey().

カウンタ・ループの「if」を 2 回目に通過する際には、カウンタの「ON」ビットを両方とも処理したことになるため、BaseKSN は 49012C0000000003 となり、結果として得られる curKey は 841AB7B94ED086EBC2B8A8385DA7DFCA になります。(念のため、カウンタのビットを MSB から順に BaseKSN へ OR していきます。カウンタが 0x0F で終わる場合、ビットを順に OR していくと BaseKSN は 49012C0000000008 → 49012C000000000C → 49012C000000000E → 49012C000000000F へと推移します。)

したがって、「派生鍵」は 841AB7B94ED086EBC2B8A8385DA7DFCA となります。

データ・バリアント用の定数を XOR した後、派生鍵は 841AB7B94E2F86EBC2B8A8385D58DFCA に変化します。

この値の上半分と下半分を、EDE3 拡張鍵 841AB7B94E2F86EBC2B8A8385D58DFCA841AB7B94E2F86EB で暗号化すると、最終的なデータ鍵 F739AEF595D3877F731782D28BB6AC4F が得られるはずです。すなわち、24 バイトの EDE3 鍵を用いて 841AB7B94E2F86EB を暗号化すると F739AEF595D3877F という暗号文になり、同じ鍵で C2B8A8385D58DFCA を暗号化すると 731782D28BB6AC4F という暗号文になります。これらの暗号文を連結すれば完成です。これで、KSN が 629949012C0000000003 だったトランザクションのデータを復号できる 16 バイトの鍵が得られたことになります。

サンプル・コード:ID TECH Encrypt/Decrypt Tool

ここで取り上げた DUKPT 鍵導出ルーチンのすべての完全なソースコードを確認するには、ぜひ HTML と JavaScript ベースの 暗号化/復号ツールをダウンロードし、ソースコードを精査してみてください。このツールは、IPEK の計算、3 種類すべての DUKPT 鍵バリアントの導出、TDES または AES によるデータの暗号化・復号など、多くの機能を備えています。Chrome の優れた開発者コンソール・ツール群を使えば、ツールのコードをリアルタイムでステップ実行し、変数値の変化を確認したり、ブレークポイントを設定したりできます。学習の助けとして非常に有用です。しかも無料です!ぜひ 暗号化/復号ツールをダウンロードし、触ってみて、お友達にも紹介してください。私の知る限り、これは Web 上で唯一のピュア JavaScript による DUKPT 実装です。