ホーム 主筆 その他ソフト その他情報 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:鍵の保管

2016年2月13日公開

共通鍵方式にせよ、公開鍵方式にせよ、生成した鍵はどこか安全な場所に保管しなければならない。

必要性

こういう場合を考えてみる。

GUIを表示できる場合

ユーザデータを暗号化してファイルに保存するような場合を考える。

そういう場合は、保存や読み込みを行う際にパスワードの入力を求め、そのパスワードを元に鍵を生成すればいい。

例えば、パスワード付きのzipファイルのような方法である。

上記は7zでパスワード付きのzipファイルを生成する画面である。明らかにパスワードの入力を求められている。

展開する際には、同様にパスワードの入力を求められる。

こういった場合は、鍵をどこに保存するのかについては、ほとんど考慮すべきことはない。なぜならば、パスワードを適当に捻り回して暗号鍵を生成すればいいからである。

要は、鍵を生成する元ネタをコンピュータ内に保持しさえしなければ、セキュリティ性を確保できる。もう少し有体に言えば、プログラマはすべての責任をユーザに擦り付けることができるのである。

GUIがない場合

GUIを持たず、Windowsならサービスとして、Unix系であればデーモンとして実行されるようなものを考える。この場合、ユーザに「パスワードを入力してください」と言ってウインドウを表示することはできない。しかし、暗号や認証を行いたい場合にはどうすればいいのか?

この場合、ユーザがバカだったりプログラムが改ざんされていたり等といったパターンを考慮しておくと、完全な解は無いようにも思われる。

それでも、たぶん平文でファイルに保存するよりはまともな方法として、CNGではキーストレージプロバイダという機能を提供している。

使い方

関係するAPI関数を先に示す。

まず、NCryptOpenStorageProvider関数でキーストレージプロバイダのハンドルを取得する。

NTSTATUS r = NCryptOpenStorageProvider(
  &hKyStorage,                 // ハンドル
  MS_KEY_STORAGE_PROVIDER,     // プロバイダの種類を指定する
  0                            // 指定するものはない
);

MS_KEY_STORAGE_PROVIDERを指定した場合はディスク上のどこかに鍵が保存される。どこかというのは、多分以下である。

C:\Users\ユーザ名\AppData\Roaming\Microsoft\Crypto\Keys

後で実行例を示すが、鍵を作るとこんなファイルが生成される。

ファイルの中身はこんな感じである。

プロバイダの種類としてMS_SMART_CARD_KEY_STORAGE_PROVIDERを指定してやれば、鍵をICカードに保存することができるらしい。

キーストレージプロバイダのハンドルを取得したら、次は鍵を生成してやる。これにはNCryptCreatePersistedKey関数と、NCryptFinalizeKey関数を使用する。

// 鍵を生成する
r = NCryptCreatePersistedKey( 
  hKyStorage,           // ストレージプロバイダ
  &hKey,                // 鍵のハンドル
  BCRYPT_AES_ALGORITHM, // アルゴリズム
  L"TestKey",           // 鍵の名称
  0,                    // 鍵の種類
  0                     // フラグ
);
 
// 鍵の生成を終了する
r = NCryptFinalizeKey(
  hKey,                 // 鍵のハンドル
  0                     // フラグ
);

共通鍵暗号の例で示したような、詳細なパラメタを指定する方法が見当たらない。シークレットもイニシャルベクタも指定できない。全部お任せなのだろうか?

暗号化を行うにはNCryptEncrypt関数を使用する。

// 暗号化する
r = NCryptEncrypt(
  hKey,                                         // 鍵
  (unsigned char*)Hirabun1, sizeof( Hirabun1 ), // 平文
  nullptr,                                      // パディングの情報
  (unsigned char*)Angoubun, sizeof( Angoubun ), // 暗号文
  &AngoubunSize,                                // 出力された暗号文のバイト長
  0                                             // フラグ
);

復号はNCryptDecrypt関数である。

r = NCryptDecrypt(
  hKey,                                         // 鍵
  (unsigned char*)Angoubun, AngoubunSize,       // 暗号文
  nullptr,                                      // パディングの情報
  (unsigned char*)Hirabun1, sizeof( Hirabun1 ), // 平文
  &Result,                                      // 出力された平文のバイト長
  0                                             // フラグ
);

さて、上でイニシャルベクタが指定できないと書いたが、使われないわけでもないらしい。おそらく、内部的にイニシャルベクタの値を保持しているのだろう。

だから、NCryptEncrypt関数で暗号化した直後に、同じ鍵を使いまわしてNCryptEncrypt関数で復号しようとしたら失敗した。関数の実行自体は正常終了するのだが、正しく復号されない。NCryptEncrypt関数を呼び出す都度、鍵の内部状態が更新されるのだと思われる。

最後に、生成した鍵はNCryptDeleteKey関数で削除する。

r = NCryptDeleteKey( hKey, 0 );

NCryptDeleteKey関数が呼び出されたタイミングで、「C:\Users\ユーザ名\AppData\Roaming\Microsoft\Crypto\Keys」に作られた得体のしれないファイルが削除されるようだ。

実装例

アプリケーションで管理していない領域に鍵が保存されることを確認するため、プログラムを2本に分けて示す。

1つ目は鍵の生成と暗号化を行うプログラムである。

#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <time.h>
#include <assert.h>
#include <Windows.h>
 
// CNGのヘッダファイル
#include <Bcrypt.h>
#include <Ncrypt.h>
 
///////////////////////////////////////////////////////////////////////////////
// main関数
 
int _tmain(int argc, _TCHAR* argv[])
{
  NCRYPT_PROV_HANDLE hKyStorage;  // ストレージプロバイダのハンドル
  NCRYPT_KEY_HANDLE hKey;         // 鍵のハンドル
  char Hirabun1[64] = "abcdefghijklmnopqrstuvwxyz";  // 平文のデータ
  char Angoubun[512];             // 暗号文
  unsigned long AngoubunSize;     // 生成された暗号文の長さ
 
  // ストレージプロバイダのハンドルを取得する
  NTSTATUS r = NCryptOpenStorageProvider(
    &hKyStorage,                 // ハンドル
    MS_KEY_STORAGE_PROVIDER,     // プロバイダの種類を指定する
    0                            // 指定するものはない
  );
  if ( r != 0 )
    printf( "NCryptOpenStorageProvider失敗\n" );
  else
    printf( "NCryptOpenStorageProvider成功\n" );
 
  // 鍵を生成する
  r = NCryptCreatePersistedKey( 
    hKyStorage,           // ストレージプロバイダ
    &hKey,                // 鍵のハンドル
    BCRYPT_AES_ALGORITHM, // アルゴリズム
    L"TestKey",           // 鍵の名称
    0,                    // 鍵の種類
    0                     // フラグ
  );
  if ( r != 0 )
    printf( "NCryptCreatePersistedKey失敗\n" );
  else
    printf( "NCryptCreatePersistedKey成功\n" );
 
  // 鍵の生成を終了する
  r = NCryptFinalizeKey(
    hKey,                 // 鍵のハンドル
    0                     // フラグ
  );
  if ( r != 0 )
    printf( "NCryptFinalizeKey失敗\n" );
  else
    printf( "NCryptFinalizeKey成功\n" );
 
  // 暗号化する
  r = NCryptEncrypt(
    hKey,                                         // 鍵
    (unsigned char*)Hirabun1, sizeof( Hirabun1 ), // 平文
    nullptr,                                      // パディングの情報
    (unsigned char*)Angoubun, sizeof( Angoubun ), // 暗号文
    &AngoubunSize,                                // 出力された暗号文のバイト長
    0                                             // フラグ
  );
  if ( r != 0 )
    printf( "NCryptEncrypt失敗\n" );
  else {
    printf( "NCryptEncrypt成功 暗号文=\n" );
    for ( unsigned int i = 0; i < AngoubunSize; i++ )
      printf( "%02X", (unsigned char)Angoubun[i] );
    printf( "\n" );
  }
 
  // 鍵を破棄する
  r = NCryptFreeObject( hKey );
  if ( r != 0 )
	  printf( "NCryptFreeObject失敗\n" );
  else
	  printf( "NCryptFreeObject成功\n" );
 
  // ストレージプロバイダのハンドルを破棄する
  r = NCryptFreeObject( hKyStorage );
  if ( r != 0 )
    printf( "NCryptFreeObject失敗\n" );
  else
    printf( "NCryptFreeObject成功\n" );
 
 
  ///////////////////////////////////////////////////////////////
  // 以下は暗号文をファイルに出力する処理
 
  HANDLE h = CreateFile(
    L"EncriptData.txt", GENERIC_WRITE, 0, NULL,
    CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL
  );
  WriteFile( h, Angoubun, AngoubunSize, &AngoubunSize, NULL );
  CloseHandle( h );
 
  return 0;
}

2つ目は、既存の鍵を取得し、暗号文を復号して、鍵を破棄している。

#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <time.h>
#include <assert.h>
#include <Windows.h>
 
// CNGのヘッダファイル
#include <Bcrypt.h>
#include <Ncrypt.h>

int _tmain(int argc, _TCHAR* argv[])
{
  NCRYPT_PROV_HANDLE hKyStorage;  // ストレージプロバイダのハンドル
  NCRYPT_KEY_HANDLE hKey;         // 鍵のハンドル
  char Hirabun1[64];              // 平文のデータ
  unsigned long Result;           // 平文のサイズ
  char Angoubun[512];             // 暗号文
  unsigned long AngoubunSize;     // 暗号文の長さ
 
  ///////////////////////////////////////////////////////////////
  // 先に暗号文をファイルから取得する
  HANDLE h = CreateFile(
    L"EncriptData.txt", GENERIC_READ, 0, NULL,
    OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL
  );
  AngoubunSize = GetFileSize( h, NULL );
  ReadFile( h, Angoubun, AngoubunSize, &AngoubunSize, NULL );
  CloseHandle( h );
 
  ///////////////////////////////////////////////////////////////
  // 復号する
 
  // ストレージプロバイダのハンドルを取得する
  NTSTATUS r = NCryptOpenStorageProvider(
    &hKyStorage,                  // ハンドル
    MS_KEY_STORAGE_PROVIDER,      // プロバイダの種類を指定する
    0                             // 指定するものはない
  );
  if ( r != 0 )
    printf( "NCryptOpenStorageProvider失敗\n" );
  else
    printf( "NCryptOpenStorageProvider成功\n" );
 
  // 既存の鍵を開く
  r = NCryptOpenKey(
    hKyStorage,                   // ストレージプロバイダ
    &hKey,                        // 鍵のハンドル
    L"TestKey",                   // 鍵の名称
    0,                            // 鍵の種類
    0                             // フラグ
  );
  if ( r != 0 )
    printf( "NCryptOpenKey失敗\n" );
  else
    printf( "NCryptOpenKey成功\n" );
 
  // 復号する
  r = NCryptDecrypt(
    hKey,                                         // 鍵
    (unsigned char*)Angoubun, AngoubunSize,       // 暗号文
    nullptr,                                      // パディングの情報
    (unsigned char*)Hirabun1, sizeof( Hirabun1 ), // 平文
    &Result,                                      // 出力された平文のバイト長
    0                                             // フラグ
  );
  if ( r != 0 )
    printf( "NCryptDecrypt失敗\n" );
  else
    printf( "NCryptDecrypt成功 平文=%s\n", Hirabun1 );
 
  // 鍵を削除する
  r = NCryptDeleteKey( hKey, 0 );
  if ( r != 0 )
	  printf( "NCryptDeleteKey失敗\n" );
  else
	  printf( "NCryptDeleteKey成功\n" );
 
  r = NCryptFreeObject( hKyStorage );
  if ( r != 0 )
	  printf( "NCryptFreeObject失敗\n" );
  else
	  printf( "NCryptFreeObject成功\n" );
  return 0;
}

実行例

面白味はないが、上記のプログラムを実行した結果を示す。

1つ目

NCryptOpenStorageProvider成功
NCryptCreatePersistedKey成功
NCryptFinalizeKey成功
NCryptEncrypt成功 暗号文=
35DBAE8CE4CCD0F02A6AC66F2DA8538F
521BA76D720B8ED22D883F418730711281
1B86937606ABAA9BAAC72221A45803AC4
B50FEACAB00003AA415631239606B

NCryptFreeObject成功
NCryptFreeObject成功

上に書いたことの繰り返しになるが、1つ目のプログラムを実行した時点で、下記のように鍵が生成されている。

2つ目

NCryptOpenStorageProvider成功
NCryptOpenKey成功
NCryptDecrypt成功 平文=abcdefghijklmnopqrstuvwxyz
NCryptDeleteKey成功
NCryptFreeObject成功

正しく復号されていることがわかる。また、プログラムの最後で鍵を削除しているため、生成された鍵のファイルが無くなっていることが確認できる。

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


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