ホーム 主筆 その他ソフト その他情報 Syuhitu.org English

Windows関連

スクリーンセーバー作成法

半透明ウインドウの性能

bootfont.bin

キャビネット形式

ウインドウスタイルをいじる

Java製ソフトをServiceに登録する

イベントログにメッセージを出力する

コントロールパネルにアイコンを追加する

スクリプトによる拡張1

スクリプトによる拡張2

ガジェットの作成

大容量メモリ

メモリ搭載量の下限に挑む

スパースファイルにする

表示されるアイコンの種類を調べてみた

メモリマップIOとエラー処理

ファイルを作る順番と速度の関係

Cryptography API: Next Generationを使う

Windows 10のアクセントカラー

iSCSIディスクにバックアップを取る

サーバプロセスを分離して実装する

サーバプロセスを分離して実装する - F#

レジストリに大量に書き込む

Solaris関連

OpenGL

Solaris設定

ディレクトリの読み込み

主筆プラグイン開発

マルチスレッドでの開発

door

音を出す

Blade100の正しい虐め方

パッケージの作成

画像入出力

BMPファイル

ICOファイル

ANIファイル

JPEGファイル

減色アルゴリズム

減色アルゴリズムの並列化

その他アルゴリズムなど

自由軸回転

Base64

文字列操作

CPU利用率の取得

正規表現ライブラリ

メタボールを作る

メタボールを作る2

正規表現とNFA・DFA

C言語の構文解析

液晶ディスプレイを解体してみた

iSCSIの理論と実装

単一フォルダにファイルを沢山作る

USB-HUBのカスケード接続

SafeIntの性能

VHDファイルのフォーマット

USBメモリに書き込み続けてみた

CNG:AES GCMモードのプログラム例

2016年2月6日公開

Cryptography API: Next Generationを使い、AES GCMモードで暗号/復号を行うプログラム例を示す。

プログラム例

#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <assert.h>
#include <Windows.h>
#include <Bcrypt.h>

int _tmain(int argc, _TCHAR* argv[])
{
  // アルゴリズムプロバイダのハンドル
  BCRYPT_ALG_HANDLE hAlgorithm;
 
  // 対称鍵のハンドル
  BCRYPT_KEY_HANDLE hKey;
 
  // キーオブジェクト
  unsigned char *pKeyObject;
 
  // キーオブジェクトのバイト長
  unsigned long KeyObjectLength;
 
  // 暗号鍵のバイト長(シークレットのバイト長に等しい)
  BCRYPT_KEY_LENGTHS_STRUCT KeyLength;
 
  // イニシャルベクタ
  unsigned char *pIV;
 
  // イニシャルベクタのバイト長(ブロック長と等しい)
  unsigned long IVLength;
 
  // 対称鍵生成元となるシークレット
  unsigned char *pSecretData;
 
  // 平文のデータ
  char Hirabun1[64] = "abcdefghijklmnopqrstuvwxyz";
 
  // 暗号文
  char Angoubun[512];
 
  // 生成された暗号文の長さ
  unsigned long AngoubunSize;
 
  // 復号した平文
  char Hirabun2[512];
 
  // 復号された平文の長さ
  unsigned long Hirabun2Size;
 
  // 作業用
  unsigned long Result = 0;
 
  // 暗号利用モード
  TCHAR ChainModeBuf[64];
 
  // 認証情報
  BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO acmi;
 
  // Nonceのバッファ
  // (詳しく知らんが、Nonceは12バイトと決まっているらしい)
  unsigned char vNonce1[12];
  unsigned char vNonce2[12];
 
  // 認証タグのバッファ
  unsigned char vAuthTag[256];
 
  // 認証タグのサイズ
  BCRYPT_AUTH_TAG_LENGTHS_STRUCT AuthTagLen;
 
  // MACの保持用のバッファらしい
  unsigned char vMacBuf[256];
 
  // アルゴリズムプロバイダのハンドルを取得する
  NTSTATUS r = BCryptOpenAlgorithmProvider(
    &hAlgorithm, BCRYPT_AES_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0
  );
  if ( r != 0 )
    printf( "BCryptOpenAlgorithmProvider失敗\n" );
  else
    printf( "BCryptOpenAlgorithmProvider成功\n" );
 
  // 暗号利用モードを変える
  _tcscpy_s( ChainModeBuf, BCRYPT_CHAIN_MODE_GCM );
  r = BCryptSetProperty(
    hAlgorithm, BCRYPT_CHAINING_MODE, (unsigned char*)ChainModeBuf, sizeof( ChainModeBuf ), 0
  );
  if ( r != 0 )
    printf( "BCryptSetProperty失敗\n" );
  else
    printf( "BCryptSetProperty成功\n", ChainModeBuf );
 
  // キーオブジェクトのサイズを取得する
  r = BCryptGetProperty(
    hAlgorithm, BCRYPT_OBJECT_LENGTH, (unsigned char*)( &KeyObjectLength ), sizeof( KeyObjectLength ), &Result, 0
  );
  if ( r != 0 )
    printf( "BCryptGetProperty失敗\n" );
  else
    printf( "BCryptGetProperty成功 キーオブジェクトのバイト長=%d\n", KeyObjectLength );
 
  // イニシャルベクタのサイズを取得する
  r = BCryptGetProperty(
    hAlgorithm, BCRYPT_BLOCK_LENGTH, (unsigned char*)( &IVLength ), sizeof( IVLength ), &Result, 0
  );
  if ( r != 0 )
    printf( "BCryptGetProperty失敗\n" );
  else
    printf( "BCryptGetProperty成功 イニシャルベクタのバイト長=%d\n", IVLength );
 
  // キーのサイズを取得する
  r = BCryptGetProperty(
    hAlgorithm, BCRYPT_KEY_LENGTHS, (unsigned char*)( &KeyLength ), sizeof( KeyLength ), &Result, 0
  );
  if ( r != 0 )
    printf( "BCryptGetProperty失敗\n" );
  else {
    KeyLength.dwMaxLength /= 8;
    printf( "BCryptGetProperty成功 キーのバイト長=%d\n", KeyLength.dwMaxLength );
  }
 
  // 認証タグのサイズを取得する
  r = BCryptGetProperty(
    hAlgorithm, BCRYPT_AUTH_TAG_LENGTH, (unsigned char*)( &AuthTagLen ), sizeof( AuthTagLen ), &Result, 0
  );
  if ( r != 0 )
    printf( "BCryptGetProperty失敗\n" );
  else
    printf( "BCryptGetProperty成功 認証タグのバイト長=%d\n", AuthTagLen.dwMaxLength );
 
  // 一応確認する
  assert( sizeof( vAuthTag ) > AuthTagLen.dwMaxLength );
  assert( sizeof( vMacBuf ) > AuthTagLen.dwMaxLength );
 
  // 必要なメモリ領域を確保する
  pKeyObject = (unsigned char*)( malloc( KeyObjectLength ) );
  pIV = (unsigned char*)( malloc( IVLength ) );
  pSecretData = (unsigned char*)( malloc( KeyLength.dwMaxLength ) );
 
  // 対称鍵生成元となるシークレットを生成する
  for ( unsigned long i = 0; i < KeyLength.dwMaxLength; i++ )
    pSecretData[i] = rand();    // 本当は、乱数はrandを使ってはいけない
 
  // 対称鍵を生成する
  r = BCryptGenerateSymmetricKey(
    hAlgorithm, &hKey, pKeyObject, KeyObjectLength, pSecretData, KeyLength.dwMaxLength, 0
  );
  if ( r != 0 )
    printf( "BCryptGenerateSymmetricKey失敗\n" );
  else
    printf( "BCryptGenerateSymmetricKey成功\n" );
 
  printf( "--- 暗号化 -------------------------------------------\n" );
 
  // イニシャルベクタを生成する
  for ( unsigned long i = 0; i < IVLength; i++ )
    pIV[i] = (unsigned char)i;    // 本当は乱数にする
 
  // 認証情報を設定する
  for ( int i = 0; i < sizeof( vNonce1 ); i++ ) {
    vNonce1[i] = rand();          // 乱数である必要はない
    vNonce2[i] = vNonce1[i];      // 後で使うため
  }
  BCRYPT_INIT_AUTH_MODE_INFO( acmi );  // 初期化
  acmi.pbNonce = vNonce1;              // Nonceの情報が必要
  acmi.cbNonce = sizeof( vNonce1 );    // Nonceのサイズ
 
  // 暗号化時に生成される認証タグを取得するバッファを指定する
  acmi.pbTag = vAuthTag;
  acmi.cbTag = AuthTagLen.dwMaxLength;
 
  // 事前に暗号文のバイト数を取得する
  r = BCryptEncrypt(
    hKey,                                         // 鍵
    (unsigned char*)Hirabun1, sizeof( Hirabun1 ), // 平文
    &acmi,                                        // 認証情報
    pIV, IVLength,                                // イニシャルベクタ
    nullptr, 0,                                   // 暗号文
    &Result,                                      // 出力された暗号文のバイト長
    0                                             // フラグ
  );
  if ( r != 0 )
    printf( "BCryptEncrypt失敗\n" );
  else
    printf( "BCryptEncrypt成功 予測されるバイト数=%d\n", Result );
 
  // 一応確認しておく
  assert( sizeof( Angoubun ) > Result );
 
  // 暗号化する
  r = BCryptEncrypt(
    hKey,                                         // 鍵
    (unsigned char*)Hirabun1, sizeof( Hirabun1 ), // 平文
    &acmi,                                        // 認証情報
    pIV, IVLength,                                // イニシャルベクタ
    (unsigned char*)Angoubun, sizeof( Angoubun ), // 暗号文
    &Result,                                      // 出力された暗号文のバイト長
    0                                             // フラグ
  );
  if ( r != 0 )
    printf( "BCryptEncrypt失敗\n" );
  else {
    printf( "BCryptEncrypt成功 バイト数=%d, 暗号文=", Result );
    for ( unsigned int j = 0; j < Result; j++ )
      printf( "%02X", (unsigned char)Angoubun[j] );
    printf( "\n" );
	printf( "認証タグ=" );
    for ( unsigned int j = 0; j < AuthTagLen.dwMaxLength; j++ )
      printf( "%02X", (unsigned char)vAuthTag[j] );
    printf( "\n" );
  }
  AngoubunSize = Result;
 
  printf( "--- 復号 ---------------------------------------------\n" );
  // ※暗号文を複数に分けて復号する場合に特殊な処理が必要になるらしい。
  //   ここでは2回に分けれ復号することを考える
 
  // まず、暗号化時に使用したイニシャルベクタが必要になるため、
  // 復号に備えてイニシャルベクタを元に戻す
  for ( unsigned long i = 0; i < IVLength; i++ )
    pIV[i] = (unsigned char)i;    // 本当は乱数にする
 
  // 認証情報の初期化
  BCRYPT_INIT_AUTH_MODE_INFO( acmi );  // 初期化
  acmi.pbNonce = vNonce2;              // Nonceの情報が必要
  acmi.cbNonce = sizeof( vNonce2 );    // Nonceのサイズ
 
  // 暗号化時に取得された認証タグを指定する
  // (認証タグが一致しないとSTATUS_AUTH_TAG_MISMATCHで失敗する)
  acmi.pbTag = vAuthTag;
  acmi.cbTag = AuthTagLen.dwMaxLength;
 
  // 計算中のMACを一時的に保持しておく作業用バッファを指定する
  acmi.pbMacContext = vMacBuf;
  acmi.cbMacContext = AuthTagLen.dwMaxLength;
 
  // 復号処理が継続されることを示す
  acmi.dwFlags = BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG;
 
  // サイズの取得は省略して、とりあえず前半を復号する
  r = BCryptDecrypt(
    hKey,                                         // 鍵
    (unsigned char*)Angoubun,                     // 暗号文のバッファ
	AngoubunSize / 2,                             // 暗号文のサイズ(前半部分)
    &acmi,                                        // 認証情報
    pIV, IVLength,                                // イニシャルベクタ
    (unsigned char*)Hirabun2,                     // 平文
	sizeof( Hirabun2 ),                           // 平文用のバッファ長
    &Result,                                      // 出力された平文のバイト長
    0                                             // フラグ
  );
  if ( r != 0 )
    printf( "BCryptDecrypt失敗\n" );
  else
    printf( "BCryptDecrypt成功 復号されたバイト数=%d\n", Result );
  Hirabun2Size = Result;
 
  // 復号処理が継続されないことを示す
  acmi.dwFlags &= ~BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG;
 
  // 後半部分を復号する
  r = BCryptDecrypt(
    hKey,                                         // 鍵
    (unsigned char*)Angoubun + AngoubunSize / 2,  // 暗号文のバッファ(後半)
	AngoubunSize / 2,                             // 暗号文のサイズ(後半部分)
    &acmi,                                        // 認証情報
    pIV, IVLength,                                // イニシャルベクタ
    (unsigned char*)Hirabun2 + Hirabun2Size,      // 平文のバッファ(後半部分)
	sizeof( Hirabun2 ) - Hirabun2Size,            // 平文用のバッファサイズ(残りの部分)
    &Result,                                      // 出力された平文のバイト長
    0                                             // フラグ
  );

  if ( r != 0 )
    printf( "BCryptDecrypt失敗\n" );
  else {
    Hirabun2Size += Result;
    printf( "BCryptDecrypt成功 バイト数=%d, 平文=%s\n", Hirabun2Size, Hirabun2 );
  }
 
  // キーを破棄する
  r = BCryptDestroyKey( hKey );
  if ( r != 0 )
    printf( "BCryptDestroyKey失敗\n" );
  else
    printf( "BCryptDestroyKey成功\n" );
 
  // アルゴリズムプロバイダを破棄する
  r = BCryptCloseAlgorithmProvider( hAlgorithm, 0 );
  if ( r != 0 )
    printf( "BCryptCloseAlgorithmProvider失敗\n" );
  else
    printf( "BCryptCloseAlgorithmProvider成功\n" );
 
  // メモリ領域を解放する
  free( pKeyObject );
  free( pIV );
  free( pSecretData );
 
  return 0;
}

実行結果

BCryptOpenAlgorithmProvider成功
BCryptSetProperty成功
BCryptGetProperty成功 キーオブジェクトのバイト長=3206
BCryptGetProperty成功 イニシャルベクタのバイト長=16
BCryptGetProperty成功 キーのバイト長=32
BCryptGetProperty成功 認証タグのバイト長=16
BCryptGenerateSymmetricKey成功
--- 暗号化 -------------------------------------------
BCryptEncrypt成功 予測されるバイト数=64
BCryptEncrypt成功 バイト数=64, 暗号文=4E5BE5D2CCEDC589
55F66603FF487E855D1C173440696CA25A95BCE9FC058CBC8
6638C671EB7627CD6A3EFBC0A5EFADC56369F1CB0808FB59
16B5230A3026FFD
認証タグ=E44547FD31D93276049E4384272F1E85
--- 復号 ---------------------------------------------
BCryptDecrypt成功 復号されたバイト数=32
BCryptDecrypt成功 バイト数=64, 平文=abcdefghijklmnopqrstuvwxyz
BCryptDestroyKey成功
BCryptCloseAlgorithmProvider成功

概説

CBCモードに比べると大分複雑化している。あまり詳しくないが、多少の解説を試みる。

まず、もっとも異なる点はBCryptEncrypt関数とBCryptDecrypt関数に対して、BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO構造体のアドレスを渡していることである。

BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO構造体は下記のように定義されている。

typedef struct _BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO {
  ULONG     cbSize;
  ULONG     dwInfoVersion;
  PUCHAR    pbNonce;
  ULONG     cbNonce;
  PUCHAR    pbAuthData;
  ULONG     cbAuthData;
  PUCHAR    pbTag;
  ULONG     cbTag;
  PUCHAR    pbMacContext;
  ULONG     cbMacContext;
  ULONG     cbAAD;
  ULONGLONG cbData;
  ULONG     dwFlags;
} BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO, 
*PBCRYPT_AUTHENTICATED_CIPHER_MODE_INFO;

このうち興味があるのはpbNonce・cbNonceのNonce(ナンスとかノンスと読むのがいいらしい)の情報、pbTag・cbTagの認証タグ、pbMacContext、cbMacContextの計算中のMACを保持するバッファ、dwFlagsのフラグである。

Nonce

残念なことに、このあたりは一次情報として頼るべきMSDNには記載があまりない。有象無象の情報をかき集めると、とりあえず以下のような値を指定すればいいらしい。

  1. pbNonceには12バイトのバッファを指定する。当然、cbNonceの値は12になる。
  2. Nonceは同一の鍵で暗号化するすべてのメッセージで異なる値を指定する。ここでいう「メッセージ」の単位は、認証タグが1つ生成される単位、と解釈すればよさそうだ。
  3. Nonceは乱数である必要はない。ただし、必ずメッセージごとに異なる値であることと、暗号化するときと復号するときとで同じ値を指定しなければらない。
  4. メッセージごとに異なる値でありさえすれば予測できる値でもいいので、メッセージごとにインクリメントされるカウンタなんかでもいいらしい。

上記のプログラムでは、とりあえず2つのバッファに同じ乱数を設定しておいて、暗号時と復号時で同じ乱数を使用するようにしている。

認証タグ

BCryptEncrypt関数で暗号化した時に認証タグの値が生成される。BCryptDecrypt関数で復号する際に同じ認証タグの値を指定してやることによって、暗号文がおかしくなっていないか確認してくれるらしい。

復号する際に同じ値の認証タグが指定されないと、STATUS_AUTH_TAG_MISMATCHで復号が失敗する。

認証タグを格納するバッファのサイズは、BCryptGetProperty関数でBCRYPT_AUTH_TAG_LENGTHを指定することで取得することができる。

pbMacContext、フラグ

これは「1つのメッセージ」をBCryptEncrypt関数やBCryptDecrypt関数を複数回に分けて暗号/復号したい場合に使用する必要がある。

ここでいう「1つのメッセージ」とは、上記の認証タグが1つ生成される単位のことである。

上記のプログラム例では、暗号化する際にはBCryptEncrypt関数1回の呼び出しで全体を暗号化している。そのため、pbMacContextは指定せず(すなわちNULLのままで)、dwFlagsにも値を指定しないで(すなわち0)暗号化を行っている。

しかし、復号する際には32バイトごと2回に分けて復号している。そのため、1回目の呼び出しでpbMacContextに適当なサイズのバッファを指定して、dwFlagsにBCRYPT_AUTH_MODE_CHAIN_CALLS_FLAGを指定している。そうすることで、複数回呼び出されるBCryptDecrypt関数の間で、計算途中の認証タグの情報を引き継ぐことができるようになるということらしい。

なお、意外なことにBCryptDecrypt関数を呼び出すごとにdwFlagsの値が勝手に更新される。でもって、その更新された値を適当に変更すると2回目以降の復号が失敗する。

上記のプログラムで、2回目のBCryptDecrypt関数の呼び出しの前に、「acmi.dwFlags &= ~BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG;」という面倒なことをやっているのはそのためである。「acmi.dwFlags = 0;」では正しく動作しない。

イニシャルベクタ

実のところ、メッセージを1回で暗号/復号する場合はイニシャルベクタは無視されるらしい。

上記のプログラム例では、ほかのものを使いまわしている都合もありイニシャルベクタはそのまま指定しているが、鍵とNonceが同じであればイニシャルベクタの値を指定の有無によらず、同じ暗号文が返される。

しかし、メッセージを複数回に分けて暗号/復号する場合には、2回目以降のBCryptEncrypt関数やBCryptDecrypt関数の呼び出しに対して何らかの情報を引き継ぐために、イニシャルベクタのバッファが使用されるらしい。

要は、中身は使われないが、からのバッファを指定しろということらしい。

<< 「CNG:共通鍵暗号アルゴリズムの比較」に戻る

<< 「Cryptography API: Next Generationを使う」に戻る


連絡先 - サイトマップ - 更新履歴
Copyright (C)  2000 - 2016 nabiki_t All Rights Reserved.