データが署名されたときから改竄されていないかどうかを調べるには 署名されたときと同じ条件でハッシュ値を求めます。そしてこのハッシュ値が 署名者の公開鍵を使って署名と矛盾しないかを検証します。
ハッシュ値の長さはハッシュアルゴリズムによって決まります。元データの
大きさとは関係有りません。同じデータを同じアルゴリズムでハッシュすると
同じハッシュ値が得られることが保証されています。しかし、ハッシュ値から
元データを知ることはできません。
署名するには
署名が正しいかどうか検証するには1.CryptCreateHash関数で空のハッシュオブジェクトを作る。 2.CryptHashData関数でハッシュオブジェクトにデータを追加する。 3.CryptSignHash関数でハッシュに署名する。 4.CryptDestroyHash関数でハッシュオブジェクトを破壊する。
こう書くと簡単そうに見えますが、実際にプログラムを書くといろいろ問題が 出てきます。1.CryptCreateHash関数で空のハッシュオブジェクトを作る。 2.CryptHashData関数でデータを追加します。(署名したときと同じ条件)。 3.CryptVerifySignature関数で検証します。(署名者の公開鍵が必要)。 4.CryptDestroyHash関数で後始末をします。
今回は署名をするときに、公開鍵を鍵ファイルのキープロッブに納め、署名を 署名ファイルに納めます。元データもファイルで計3つのファイルが必要となります。 これら3つのファイルは誰でも見ることができます。
検証はこれらの3つのファイルの組に対して行います。どの1つを改竄しても 検出することができます。
hHashはハッシュオブジェクトのハンドルです。BOOL WINAPI CryptSignHash( HCRYPTHASH hHash, // in DWORD dwKeySpec, // in LPCTSTR sDescription, // in DWORD dwFlags, // in BYTE *pbSignature, // out DWORD *pdwSigLen // in/out );
dwKeySpecは、ハッシュに署名するときのキーの種類を指定します。
AT_KEYEXCHANGEは、交換鍵のプライベートキー、
AT_SIGNATUREは、署名鍵のプライベートキーを使うときに指定します。
sDescriptionは、現在は使われません。NULLにします。しかし 互換性のためまだサポートされています。
dwFlagsは、予約済みで0を指定します。
pbSignatureは、署名データを受け取るバッファのポインタです。 これをNULLにするとこの情報のサイズを知ることができます。
pdwSigLenは、pdwSigLenのサイズのポインタです。関数が戻るときは ばっふあに蓄えられたバイト数へのポインタです。
成功したらTRUE, 失敗したらFALSEを返します。
hHashは、ハッシュハンドルを指定します。BOOL WINAPI CryptVerifySignature( HCRYPTHASH hHash, // in BYTE *pbSignature, // in DWORD dwSigLen, // in HCRYPTKEY hPubKey, // in LPCTSTR sDescription, // in DWORD dwFlags // in );
pbSignatureは、署名データのアドレスです。
dwSigLenは、pbSignatureのバイト数です。
hPubKeyは、公開鍵のハンドルです。これは、署名したときに 使われたオリジナルのキーペアに属していなくてはなりません。
sDescriptionは、もはや使われません。NULLを指定します。しかし、互換性のため まだ、サポートされています。
dwFlagsは、予約済みで0を指定します。
成功するとTRUE, 失敗したらFALSEを返します。
さて、上の両関数でsDescriptionをNULLに指定しないと大変面倒なことがおこります。 Win98同士では署名の検証がうまくいくのですが、Win2000とWin98ではうまくいきません。 これがわかるまでに大変な時間を費やしてしまいました。
では、プログラムを見てみましょう。
メニューとダイアログボックスのリソース・スクリプトです。 特に目新しいものはないです。// hash02.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)", IDM_END END POPUP "署名(&N)" BEGIN MENUITEM "署名する(&S)", IDM_SIGN MENUITEM "署名確認(&V)", IDM_VERIFY END END ///////////////////////////////////////////////////////////////////////////// // // Dialog // MYFNAME DIALOG DISCARDABLE 0, 0, 213, 89 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "ファイル名" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT1,91,7,115,15,ES_AUTOHSCROLL EDITTEXT IDC_EDIT2,91,29,115,15,ES_AUTOHSCROLL EDITTEXT IDC_EDIT3,91,49,115,15,ES_AUTOHSCROLL DEFPUSHBUTTON "OK",IDOK,7,68,50,14 PUSHBUTTON "キャンセル",IDCANCEL,156,68,50,14 LTEXT "オリジナルファイル",IDC_STATIC1,7,13,73,8 LTEXT "署名ファイル",IDC_STATIC2,7,33,73,8 LTEXT "キーファイル",IDC_STATIC3,7,51,73,8 END
インスタンスハンドル、ファイル名等がグローバル変数になっています。// hash02.cpp #define _WIN32_WINNT 0x0500 #ifndef STRICT #define STRICT #endif #include <windows.h> #include <windowsx.h> #include <wincrypt.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyFNameProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); BOOL MySign(HWND); BOOL MyVerify(HWND); char szClassName[] = "hash02"; //ウィンドウクラス HINSTANCE hInst; //元ファイル、プロッブファイル、署名ファイル char szOrgFile[MAX_PATH], szBlobFile[MAX_PATH], szSigFile[MAX_PATH];
毎度同じです。親ウィンドウの初期状態でサイズを少し小さめにしてあります。int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; hInst = hCurInst; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //ウィンドウ・クラスの登録 ATOM InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst;//インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかる署名", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 220, //幅 130, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }
親ウィンドウのプロシージャです。//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; switch (msg) { case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_SIGN: MySign(hWnd); break; case IDM_VERIFY: MyVerify(hWnd); break; } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }
メニューからIDM_SIGNが選択されたら MySign関数を呼んで署名を行います。
IDM_VERIFYが選択されたらMyVerify関数を呼んで署名の正当性を 検証します。
ちょっと長いですが、署名を行う関数です。 わかりづらい時は、エラー時の処理を省略して眺めてみるとよくわかります。BOOL MySign(HWND hWnd) { HCRYPTPROV hProv = 0; HCRYPTKEY hKey = 0; HCRYPTHASH hHash = 0; char szDescription[1024 * 20], szBlob[1024]; DWORD dwLength, dwRead, dwWrite, dwSigLen, dwBlobLen; BYTE pbSig[512]; HANDLE hOrg, hKeyBlob, hSig; BOOL bEnd = FALSE; int id; id = DialogBox(hInst, "MYFNAME", hWnd, (DLGPROC)MyFNameProc); if (id == IDCANCEL) return FALSE; hOrg = CreateFile(szOrgFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hOrg == INVALID_HANDLE_VALUE) { MessageBox(hWnd, "元のファイルをオープンできません", "Error", MB_OK); return FALSE; } dwLength = GetFileSize(hOrg, NULL); if (dwLength > 1024 * 20) { MessageBox(hWnd, "ファイルが大きすぎます", "Error", MB_OK); CloseHandle(hOrg); return FALSE; } ReadFile(hOrg, (LPVOID)szDescription, dwLength, &dwRead, NULL); if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) { MessageBox(hWnd, "CryptAcquire Error", "Error", MB_OK); CloseHandle(hOrg); return FALSE; } if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) { MessageBox(hWnd, "CryptCreateHash Error", "Error", MB_OK); CloseHandle(hOrg); CryptReleaseContext(hProv, 0); return FALSE; } if (!CryptHashData(hHash, (BYTE *)szDescription, dwLength, 0)) { MessageBox(hWnd, "CryptHashData Error", "Error", MB_OK); CloseHandle(hOrg); CryptDestroyHash(hHash); CryptReleaseContext(hProv, 0); return FALSE; } CryptSignHash(hHash, AT_SIGNATURE, NULL, 0, NULL, &dwSigLen); CryptSignHash(hHash, AT_SIGNATURE, NULL, 0, pbSig, &dwSigLen); CryptGetUserKey(hProv, AT_SIGNATURE, &hKey); CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, NULL, &dwBlobLen); if (!CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, (BYTE *)szBlob, &dwBlobLen)) MessageBox(hWnd, "CryptExportKey Error", "Error", MB_OK); hKeyBlob = CreateFile(szBlobFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); WriteFile(hKeyBlob, szBlob, dwBlobLen, &dwWrite, NULL); hSig = CreateFile(szSigFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); WriteFile(hSig, pbSig, dwSigLen, &dwWrite, NULL); //確認のためファイルから読み出してVerifyしてみる ReadFile(hSig, pbSig, dwSigLen, &dwRead, NULL); ReadFile(hKeyBlob, szBlob, dwBlobLen, &dwRead, NULL); if(!CryptImportKey(hProv, (BYTE *)szBlob, strlen(szBlob)+1, 0,0, &hKey)) MessageBox(hWnd, "Error Import Key", "Error", MB_OK); if (CryptVerifySignature(hHash, pbSig, dwSigLen, hKey, NULL, 0)) MessageBox(hWnd, "正常に署名されました", "OK", MB_OK); else MessageBox(hWnd, "署名失敗です", "Error", MB_OK); CloseHandle(hSig); CloseHandle(hKeyBlob); CloseHandle(hOrg); CryptDestroyHash(hHash); CryptDestroyKey(hKey); CryptReleaseContext(hProv, 0); return TRUE; }
プライベートキーをファイルに保存するとき本来はキーサイズをファイルの先頭に 書いておき、その後からプロッブに書き出すようにするのが正しい方法と思われますが ちょっと手抜きをしています。
署名後ファイルから署名とキーを読み出して、正しく署名されたか調べています。 この操作はなくてもかまいません。
署名の正当性を検証する関数です。BOOL MyVerify(HWND hWnd) { HCRYPTPROV hProv = 0; HCRYPTKEY hKey = 0; HCRYPTHASH hHash = 0; char szDescription[1024 * 20], szBlob[1024], szSig[1024]; DWORD dwLength, dwRead, dwBlobLen, dwSigLen, dwError; HANDLE hOrg, hBlob, hSig; BOOL bEnd = FALSE; int id, yn; yn = MessageBox(hWnd, "ファイル名入力が必要ですか", "ファイル名入力の必要性", MB_YESNO | MB_ICONQUESTION); if (yn == IDYES || strcmp(szOrgFile, "") == 0) { id = DialogBox(hInst, "MYFNAME", hWnd, (DLGPROC)MyFNameProc); if (id == IDCANCEL) return FALSE; } hOrg = CreateFile(szOrgFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hOrg == INVALID_HANDLE_VALUE) { MessageBox(hWnd, "元ファイルがオープンできません", "Error", MB_OK); return FALSE; } dwLength = GetFileSize(hOrg, NULL); if (dwLength > 1024 * 20) { MessageBox(hWnd, "ファイルが大きすぎます", "Error", MB_OK); CloseHandle(hOrg); return FALSE; } ReadFile(hOrg, szDescription, dwLength, &dwRead, NULL); if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) { MessageBox(hWnd, "CryptAcquireContext Error", "Error", MB_OK); CloseHandle(hOrg); return FALSE; } //キープロップからキーを読み出してインポートする hBlob = CreateFile(szBlobFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hBlob == INVALID_HANDLE_VALUE) { MessageBox(hWnd, "キーファイルがオープンできません", "Error", MB_OK); CloseHandle(hOrg); CryptReleaseContext(hProv, 0); return FALSE; } dwBlobLen = GetFileSize(hBlob, NULL); ReadFile(hBlob, szBlob, dwBlobLen, &dwRead, NULL); if (!CryptImportKey(hProv, (BYTE *)szBlob, dwBlobLen, 0, 0,&hKey)) { MessageBox(hWnd, "Import Key Error", "Error", MB_OK); CloseHandle(hOrg); CloseHandle(hBlob); CryptReleaseContext(hProv, 0); return FALSE; } //署名を読み出す hSig = CreateFile(szSigFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hSig == INVALID_HANDLE_VALUE) { MessageBox(hWnd, "署名ファイルをオープンできません", "Error", MB_OK); CloseHandle(hOrg); CloseHandle(hBlob); CryptReleaseContext(hProv, 0); return FALSE; } dwSigLen = GetFileSize(hSig, NULL); ReadFile(hSig, szSig, dwSigLen, &dwRead, NULL); if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) { MessageBox(hWnd, "CryptCreateHash Error", "Error", MB_OK); CloseHandle(hOrg); CloseHandle(hBlob); CloseHandle(hSig); CryptReleaseContext(hProv, 0); return FALSE; } if (!CryptHashData(hHash, (BYTE *)szDescription, dwLength, 0)) { MessageBox(hWnd, "Hash Data Error", "Error", MB_OK); CloseHandle(hOrg); CloseHandle(hBlob); CloseHandle(hSig); CryptReleaseContext(hProv, 0); return FALSE; } if (CryptVerifySignature(hHash, (BYTE *)szSig, dwSigLen, hKey, NULL, 0)) MessageBox(hWnd, "正常なファイルです", "OK", MB_OK); else { dwError = GetLastError(); if (dwError == ERROR_INVALID_HANDLE) MessageBox(hWnd, "INVALID_HANDLE", "Error", MB_OK); if (dwError == ERROR_INVALID_PARAMETER) MessageBox(hWnd, "INVALID_PARAMETER", "Error", MB_OK); if (dwError == NTE_BAD_FLAGS) MessageBox(hWnd, "BAD_FLAGS", "Error", MB_OK); if (dwError == NTE_NO_MEMORY) MessageBox(hWnd, "NO_MEMORY", "Error", MB_OK); if (dwError == NTE_BAD_HASH) MessageBox(hWnd, "BAD_HASH", "Error", MB_OK); if (dwError == NTE_BAD_KEY) MessageBox(hWnd, "BAD_KEY", "Error", MB_OK); if (dwError == NTE_BAD_UID) MessageBox(hWnd, "BAD_UID", "Error", MB_OK); MessageBox(hWnd, "異常です", "Error", MB_OK); } CloseHandle(hOrg); CloseHandle(hBlob); CloseHandle(hSig); CryptDestroyHash(hHash); CryptDestroyKey(hKey); CryptReleaseContext(hProv, 0); return TRUE; }
最初の方で「ファイル名入力が必要ですか」とメッセージボックスで聞いているのは、 署名後引き続き、署名の検証を行うときいちいちファイル名を入力するのが面倒なので 便宜的に作ったものです。
ダイアログボックスのプロシージャです。LRESULT CALLBACK MyFNameProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { static HWND hEdit1, hEdit2, hEdit3; HWND hStatic1, hStatic2; switch (msg) { case WM_INITDIALOG: hEdit1 = GetDlgItem(hDlg, IDC_EDIT1); hEdit2 = GetDlgItem(hDlg, IDC_EDIT2); hEdit3 = GetDlgItem(hDlg, IDC_EDIT3); hStatic1 = GetDlgItem(hDlg, IDC_STATIC1); hStatic2 = GetDlgItem(hDlg, IDC_STATIC2); return TRUE; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: Edit_GetText(hEdit1, szOrgFile, sizeof(szOrgFile)); Edit_GetText(hEdit2, szSigFile, sizeof(szSigFile)); Edit_GetText(hEdit3, szBlobFile, sizeof(szBlobFile)); EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; } return FALSE; } return FALSE; }
特に説明も不要でしょう。
さて、今回のサンプルプログラムはVC++の統合環境から実行する場合は ファイル名は必ずフルパスで指定してください。そうしないと検証時みなエラーとなって しまう可能性があります。
Update 05/May/2000 By Y.Kumei