1.共通鍵(AES)による簡単な暗号化

 Microsoft社は、次世代の暗号化API(Cryptography Next Genaration)をサポートしました。これまでのCrypto APIに替わるAPIですが、今後ともCryptoAPIのサポートは続けるようです。
 CNG(Cryptography Next Genaration)は、CryptoAPIに比して暗号化の詳細をコントロールできるようになりました。

C#(.NET Framework)での暗号化の手順は、サンプルと共に8.C#による簡単な暗号化で説明しています。

1.1 概要

 CNG(CNGCryptography Next Genaration) API を使った共通鍵による暗号化の概要をサンプルとともに解説します。(ここでは、AESブロック暗号による暗号化のサンプルを示します。)
 暗号の方式には、共通鍵による暗号方式(対称鍵暗号方式ともいう)と公開鍵による暗号方式(非対称鍵暗号方式ともいう)があります。ここでは、共通鍵による暗号化の方法を解説します。
 共通鍵による暗号化では、暗号化するときに利用する鍵と、それを復号するときに利用する鍵に同じものを使います。ちなみに、公開鍵方式では、2つの違った鍵が使われます。

 共通鍵による暗号化は、以下の関数を使います。

  • BCryptOpenAlgorithmProvider
  • BCryptGetProperty
  • BCryptSetProperty
  • BCryptGenerateSymmetricKey
  • BCryptDestroyKey
  • BCryptExportKey
  • BCryptImportKey
  • BCryptEncrypt / BCryptDecrypt
  • BCryptCloseAlgorithmProvider
大まかな手順は、以下のとおりです。
  • 暗号化のプロバイダを準備する。
  • 暗号化の鍵を生成する。
  • 生成した鍵を復号のために取り出す。
  • 初期化ベクタを生成する。
  • 目的のデータを暗号化する。
  • 復号の場合は、上記で取り出した鍵を取り込んでから
  • 暗号化されたデータを復号する。
  • プロバイダーを終了する。


1.2 暗号化のプロバイダを準備する

 CNG(Cryptography Next Genaration) API のプロバイダは、中間層としての論理インターフェースを介して利用します。ここの指定にしたがってプロバイダが読み込まれ利用できるようになります。
 以下に準備のコードを記します。
#define NT_SUCCESS(Status)  (((NTSTATUS)(Status)) >= 0)
#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)

BCRYPT_ALG_HANDLE   hAlg = NULL;
NTSTATUS            status = STATUS_UNSUCCESSFUL;

// Open an algorithm handle.
status = BCryptOpenAlgorithmProvider(
                    &hAlg,
                    BCRYPT_AES_ALGORITHM,
                    NULL,
                    0);
if(NT_SUCCESS(status)) {
    printf("OK\n");
} else {
    printf("*** Error 0x%x returned by BCryptOpenAlgorithmProvider\n", status);
    goto Error;
}
 CNG APIの関数からは、Windows Driver Kit(WDK) の NTSTATUS型でエラー情報が戻ります。また、BCryptで定義されているすべてのCNGオブジェクトは、BCRYPT_HANDLEで特定されます。しかし、上記のようにオブジェクトごとに違う型でコードを記述することもできます。

 BCryptOpenAlgorithmProvider関数の第1引数には、プロバイダオブジェクトのハンドルが戻ります。
 第2引数には、暗号化のアルゴリズムを指定します。
    以下のアルゴリズムを指定できます。
    BCRYPT_3DES_ALGORITHM       トリプルDES暗号化
    BCRYPT_3DES_112_ALGORITHM   112ビット トリプルDES暗号化
    BCRYPT_AES_ALGORITHM        AES暗号化
    BCRYPT_AES_GMAC_ALGORITHM   AES GMAC暗号化
    BCRYPT_DES_ALGORITHM        DES暗号化
    BCRYPT_DESX_ALGORITHM       拡張DES暗号化
    BCRYPT_RC2_ALGORITHM        RC2ブロック暗号化
    BCRYPT_RC4_ALGORITHM        RC4暗号化
 第3引数には、使用するアルゴリズム プロバイダをしていますが、NULLを渡すと第2引数で指定したアルゴリズム用の規定のプロバイダが読み込まれ利用できるようになります。  第4引数には、いくつかのオプションを指定できますが、BCryptの多くの関数では通常0(ゼロ)を指定します。

1.3 暗号化鍵の生成

 暗号化鍵は、キーオブジェクトとして生成されます。
 まず、このオブジェクトを格納する領域を確保します。上記で指定したAES暗号化では初期化ベクタを使用します。この初期化ベクタ(Initialization Vector; IVと略します)は、ブロックごとの暗号化ごとに変更されますのでそのための領域を確保します。さらに、暗号化のオプションを指定した上で暗号化の鍵を生成します。
 以下に、鍵生成のコードを記します。
DWORD   cbKeyObj;
PBYTE   pbKeyObj;

// キーオブジェクトを格納する領域のサイズを取得
status = BCryptGetProperty(
                hAlg,
                BCRYPT_OBJECT_LENGTH,
                (PBYTE)&cbKeyObj,
                sizeof(DWORD),
                &cbData,
                0);
if(NT_SUCCESS(status)) {
    printf("The key object size is %d.\n", cbKeyObj);
} else {
    printf("*** Error 0x%x returned by "
           "BCryptGetProperty(BCRYPT_OBJECT_LENGTH)\n",
           status);
    goto Error;
}

// 領域を確保
pbKeyObj = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbKeyObj);
if(!pbKeyObj) {
    printf("*** memory allocation failed\n");
    goto Error;
}
 キーオブジェクトのサイズはBCryptGetPropertyにプロパティ名BCRYPT_OBJECT_LENGTHを指定して取得し、そのサイズの領域を確保します。
 BCryptGetProperty関数の第1引数には、アルゴリズム プロバイダのハンドルを渡します。
 第2引数には、BCRYPT_OBJECT_LENGTHを指定します。
 第3引数には、必要な領域のサイズが戻ります。
 第4引数には、第3引数に用意した領域のサイズを渡します。
 第5引数には、第3引数にコピーされたデータのバイト数が戻ります。
 第6引数には、0(ゼロ)を指定します。

 AES暗号化には、初期化ベクタを準備します。暗号化する際に似ているデータ(例えば、メールのように先頭に"from"という文字列が現れるようなデータなど)の場合に暗号鍵を類推しやすくなりますが、それを難しくするために初期化ベクタを指定して同じ鍵を使っても違った暗号化データが生成されるようにします。
 初期化ベクタのサイズを求め、その領域を確保し、初期化ベクタ値をセットします。初期化ベクタはブロック暗号化ごとに暗号化関数によって更新されます。
static const BYTE   rgbIV[] = {
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};

DWORD   cbData;
DWORD   cbBlockLen;
PBYTE   pbIV;

// 初期化ベクタ(initialization vector; IV)のサイズを取得
status = BCryptGetProperty(
                hAlg,
                BCRYPT_BLOCK_LENGTH,
                (PBYTE)&cbBlockLen,
                sizeof(DWORD),
                &cbData,
                0);
if(NT_SUCCESS(status)) {
    printf("The block length is %d.\n", cbBlockLen);
} else {
    printf("*** Error 0x%x returned by "
           "BCryptGetProperty(BCRYPT_BLOCK_LENGTH)\n",
           status);
    goto Error;
}

// 初期化ベクタの領域を確保
// (初期化ベクタは、暗号化・復号処理によって更新される)
pbIV = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbBlockLen);
if(!pbIV) {
    printf("*** memory allocation failed\n");
    goto Error;
}
memcpy(pbIV, rgbIV, cbBlockLen);
 初期化ベクタのサイズは、BcryptGetPropertyにBCRYPT_BLOCK_LENGTHを指定して取得します。
 BCryptGetPropertyの第1引数には、アルゴリズム プロバイダのハンドルを指定します。
 第2引数には、BCRYPT_BLOCK_LENGTHを指定します。
 第3引数には、初期化ベクタに必要な領域のサイズが戻ります。
 第4引数には、第3引数に用意した領域のサイズを渡します。
 第5引数には、第3引数にコピーされたデータのバイト数が戻ります。
 第6引数には、0(ゼロ)を指定します。

 暗号化のオプションを設定してから、鍵を生成します。
 以下にコードを記します。
static const BYTE   rgbAES128Key[] = {
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};

// 暗号化のオプションを設定
BCRYPT_KEY_HANDLE   hKey = NULL;
status = BCryptSetProperty(
                hAlg,
                BCRYPT_CHAINING_MODE,
                (PBYTE)BCRYPT_CHAIN_MODE_CBC,
                sizeof(BCRYPT_CHAIN_MODE_CBC),
                0);
if(NT_SUCCESS(status)) {
    printf("OK\n");
} else {
    printf("*** Error 0x%x returned by "
           "BCryptSetProperty(BCRYPT_CHAINING_MODE)\n",
           status);
    goto Error;
}

// 暗号化用のキーオブジェクトを生成
status = BCryptGenerateSymmetricKey(
                hAlg,
                &hKey,
                pbKeyObject,
                cbKeyObject,
                (PBYTE)rgbAES128Key,
                sizeof(rgbAES128Key),
                0);
 暗号化のアルゴリズムによっていくつかのオプションを選択できます。AES暗号化アルゴリズムでは、Chining Mode(連鎖方式)が選択できますのでまずそれを設定します。
 BCryptSetProperty関数の第1引数には、アルゴリズムプロバイダを指定します。
 第2引数には、BCRYPT_CHAINING_MODEを指定します。  第3引数には、指定の連鎖モードを指定します。以下のモードが定義されています。
    BCRYPT_CHAIN_MODE_NA
    BCRYPT_CHAIN_MODE_CBC
    BCRYPT_CHAIN_MODE_EBC
    BCRYPT_CHAIN_MODE_CFB
 第4引数には、第3引数のバイトサイズを渡します。ここでは、文字列を渡していますので、そのバイト数になります。
 第5引数には、0(ゼロ)を指定します。

 キーオブジェクトは、BCryptGenerateSymmetricKeyで生成します。なお、キーオブジェクトもBCRYPT_HANDLEで特定されますが、BCRYPT_KEY_HANDLEを使うことができます。
 BCryptGenerateSymmetricKey関数の第1引数には、アルゴリズム プロバイダを渡します
 第2引数には、キーオブジェクトのハンドルが戻ります。
 第3引数には、キーオブジェクトの領域を指定します。
 第4引数には、第3引数に指定した領域のバイトサイズを指定します。
 第5引数には、キーオブジェクトのための鍵のポインタを指定します。
 第6引数には、第5引数のバイトサイズを指定します。
 第7引数には、0(ゼロ)を指定します。

1.4 復号用に生成した鍵の取り出し

 生成されたキーオブジェクトから、復号用の鍵情報を取り出します。この情報は、復号の際にインポートされ、アルゴリズム プロバイダで使用されます。
 鍵情報を取り出すために関数を2回コールします。1回目でデータのサイズを受け取り、その領域を確保してから2回目でデータを取得します。
 以下にコードを記します。
DWORD   cbBlob;
PBYTE   pbBlob;

// 鍵データのコピーを作成
// まずデータのサイズを得る
status = BCryptExportKey(
                hKey,
                NULL,
                BCRYPT_OPAQUE_KEY_BLOB,
                NULL,
                0,
                &cbBlob,
                0);
if(NT_SUCCESS(status)) {
    printf("The Key size is %d.\n", cbBlob);
} else {
    printf("*** Error 0x%x returned by "
           "BCryptExportKey(BCRYPT_OPAQUE_KEY_BLOB)\n",
           status);
    goto Error;
}

// データ領域を確保
pbBlob = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbBlob);
if(!pbBlob) {
    printf("*** memory allocation failed\n");
    goto Error;
}

// 鍵情報を取得
status = BCryptExportKey(
                hKey,
                NULL,
                BCRYPT_OPAQUE_KEY_BLOB,
                pbBlob,
                cbBlob,
                &cbBlob,
                0);
if(NT_SUCCESS(status)) {
    printf("The Key size is %d.\n", cbBlob);
} else {
    printf("*** Error 0x%x returned by "
           "BCryptExportKey(BCRYPT_OPAQUE_KEY_BLOB)\n",
           status);
    goto Error;
}
 BCryptExportKeyにBCRYPT_OPAQUE_KEY_BLOBを指定して同一のCSPを利用するための鍵情報を取り出します。ここで取り出された鍵情報は、それを作成したCSPと同じCSPにそにその情報をインポートするための情報です。

 BCryptExportKey関数の第1引数には、キーオブジェクトのハンドルを指定します。
 第2引数は、現在使われていません。NULLを指定します。
 第3引数には、エクスポートするデータのタイプを指定します。
 第4引数には、鍵情報を格納するアドレスを指定します。
 第5引数には、鍵情報のための領域のバイトサイズを渡します。
 第6引数には、データが指定された領域にコピーされたバイト数が戻ります。
 第7引数には、0(ゼロ)を指定します。

1.5 データの暗号化

 準備が整いましたので、暗号化を実施します。サンプルでは、暗号化したデータのサイズを取得してから、その領域を確保して、暗号化データを得ます。
 以下に暗号化のコードを記します。
const BYTE rgbPlainText[] = {
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};

DWORD   cbCipherText;
PBYTE   pbCipherText;

// 暗号化データの取得
status = BCryptEncrypt(
                hKey,
                (PBYTE)rgbPlainText,
                sizeof(rgbPlainText),
                NULL,
                pbIV,
                cbBlockLen,
                NULL,
                0,
                &cbCipherText,
                BCRYPT_BLOCK_PADDING);
if(NT_SUCCESS(status)) {
    printf("The buffer size is %d.\n", cbCipherText);
} else {
    printf("*** Error 0x%x returned by BCryptEncrypt()\n", status);
    goto Error;
}

// 暗号化データ用の領域確保
pbCipherText = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbCipherText);
if(!pbCipherText) {
    printf("*** memory allocation failed\n");
    goto Error;
}

// 暗号化データの取得
status = BCryptEncrypt(
                hKey,
                (PBYTE)rgbPlainText,
                sizeof(rgbPlainText),
                NULL,
                pbIV,
                cbBlockLen,
                pbCipherText,
                cbCipherText,
                &cbData,
                BCRYPT_BLOCK_PADDING);
if(!NT_SUCCESS(status)) {
    printf("*** Error 0x%x returned by BCryptEncrypt()\n", status);
    goto Error;
}
 ブロック暗号化を実施する場合は、プレーンテキスト(暗号化されるデータ)のサイズが暗号化ブロックの整数倍でなけばなりません。あらかじめプレーンテキストのサイズを必要なブロックサイズに丸めておく(パディングする)か、サンプルのようにBCRYPT_BLOCK_PADDINGを指定する必要があります。

 BCryptEncrypt 関数の第1引数には、キーオブジェクトのハンドルを指定します。
 第2引数には、暗号化するデータが格納されたポインタを指定します。
 第3引数には、暗号化するデータのバイトサイズを渡します。
 第4引数には、共通鍵暗号の場合は利用しませんのでNULLを指定します。
 第5引数には、初期化ベクタのアドレスを指定します。
 第6引数には、初期化ベクタのサイズを渡します。
 第7引数には、暗号化データのバッファポインタを指定します。
 第8引数には、暗号化データ用バッファのバイトサイズを渡します。
 第9引数には、暗号化データのバッファにコピーされたデータのバイトサイズぎ戻ります。
 第10引数には、共通鍵暗号の場合において、0(ゼロ)または、BCRYPT_BLOCK_PADDINGを指定ます。

1.6 暗号化したデータを復号する

 暗号化したデータを復号します。
 サンプルでは、暗号化で使用したキーオブジェクトを終了してから、鍵情報をクリアし、その後に、格納しておいた鍵情報をインポートして復号します。
 しかしながら、実際のアプリケーションでは引渡しできる共通暗号鍵を暗号化と同じ手順で生成してから復号しますので、ここで示す手順とは異なってくるでしょう。
 以下に復号のサンプルコードを記します。
// キーオブジェクトを終了
status = BCryptDestroyKey(hKey);
if(!NT_SUCCESS(status)) {
    printf("*** Error 0x%x returned by BCryptDestroy()\n", status);
    goto Error;
}
hKey = 0;

// キーオブジェクト領域を再利用
// 内容をクリア
memset(pbKeyObject, 0, cbKeyObject);

// 初期化ベクタを初期化
// (BCryptEncryptによって内容が変化しているため)
memcpy(pbIV, rgbIV, cbBlockLen);

// 鍵情報をインポート
status = BCryptImportKey(
                hAlg,
                NULL,
                BCRYPT_OPAQUE_KEY_BLOB,
                &hKey,
                pbKeyObject,
                cbKeyObject,
                pbBlob,
                cbBlob,
                0);
if(!NT_SUCCESS(status)) {
    printf("*** Error 0x%x returned by BCryptImportKey()\n", status);
    goto Error;
}
 サンプルでは、暗号化・復号の動作を検証するためにキーオブジェクトなどのデータをクリアしてから、先に格納してあった鍵情報を復号のためにインポートします。なお、キーオブジェクトデータの開放などの操作は、キーオブジェクトのハンドルを閉じてから行わなければなりません。

 BCrypyImportKey関数の第1引数には、アルゴリズムプロバイダを指定します。
 第2引数は、使用していません。NULLを指定します。
 第3引数には、インポートするデータのタイプを指定します。
 第4引数には、キーオブジェクトのハンドルが戻ります。
 第5引数には、キーオブジェクトのバッファポインタを指定します。
 第6引数には、キーオブジェクトバッファのバイトサイズを渡します。
 第7引数には、インポートする鍵情報が格納されたポインタを指定します。  第8引数には、インポートする鍵情報のバイトサイズを渡します。
 第9引数には、0(ゼロ)を指定します。

 復号用の鍵が準備できましたので、上記で得られた暗号化データを復号します。暗号化と同様に復号したデータのサイズを取得しその領域を確保してから、復号されたデータを受け取ります。
 以下に復号のコードを記します。
DWORD   cbPlainText;
PBYTE   pbPlainText;

// サイズを取得
status = BCryptDecrypt(
                hKey,
                pbCipherText,
                cbCipherText,
                NULL,
                pbIV,
                cbBlockLen,
                NULL,
                0,
                &cbPlainText,
                BCRYPT_BLOCK_PADDING);
if(NT_SUCCESS(status)) {
    printf("The output buffer size is %d.\n", cbPlainText);
} else {
    printf("*** Error 0x%x returned by BCryptDecrypt()\n", status);
    goto Error;
}

// 領域を確保
pbPlainText = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbPlainText);
if(!pbPlainText) {
    printf("*** memory allocation failed\n");
    goto Error;
}

// 暗号化データを復号
status = BCryptDecrypt(
                hKey,
                pbCipherText,
                cbCipherText,
                NULL,
                pbIV,
                cbBlockLen,
                pbPlainText,
                cbPlainText,
                &cbPlainText,
                BCRYPT_BLOCK_PADDING);
if(!NT_SUCCESS(status)) {
    printf("*** Error 0x%x returned by BCryptDecrypt()\n", status);
    goto Error;
}
 BCryptDecrypt関数の第1引数には、キーオブジェクトのハンドルを指定します。
 第2引数には、暗号化データのポインタを渡します。
 第3引数には、暗号化データのバイト数を渡します。
 第4引数は、共通鍵暗号の場合はNULLを指定します。
 第5引数には、初期化ベクタを渡します。
 第6引数には、初期化ベクタのバイトサイズを渡します。
 第7引数には、復号されたデータが格納されるバッファポインタを指定します。
 第8引数には、復号されたデータが格納されるバッファのバイトサイズを渡します。
 第9引数には、復号用データバッファにコピーされたデータのバイト数が戻ります。
 第10引数には、共通鍵暗号化の場合は、パディングの指定BCRYPT_BLOCK_PADDINGを渡します。

Javaで暗号化したデータを復号する方法、またはその逆の方法は、こちらです。

1.7 サンプル

 Visual Studio 2005 C++ プロジェクトです。
 商業利用および転載を禁止します。


(記載の会社名および製品名は、各社の登録商標および商標です。)

1. 共通鍵による簡単な暗号化
1.1 概要
1.2 暗号化のプロバイダを準備
1.3 暗号化鍵の生成
1.4 復号用に生成した鍵の取出
1.5 データの暗号化
1.6 暗号化したデータを復号
1.7 サンプル
株)トラスト・ソフトウェア・システム
暗号化・電子署名・タイムスタンプ ライブラリ作成します。
お問い合わせください。
会員用ログイン
ID:
パスワード:
ログインすると、一般公開していないページを閲覧できます。