メモ帳風のエディタで文書を作って、これを保存するときに 本文ファイルの他に、キーファイル、署名ファイルも同時に作るようにします。 また、他の人から送られてきた本文、キー、署名ファイルを同じディレクトリに 置いておくと、本文を開くときに署名が正しいかどうかを検証します。
さて、AさんからBさんに、いつも文書ファイルを送るとします。
この場合Aさんはあらかじめ自分の公開鍵をBさんに渡しておきます。
Aさんは、文書ファイルと署名ファイルをBさんに送るだけで、
Bさんはその文書が正しいかどうか判定することができます。
しかし、ここでは文書を作るときに常に3つのファイルを作るようにしました。 本文のファイルがabc.txtならば、キーファイルはabc.key、署名ファイルは abc.sigとしました。 (公開鍵のファイルは名前が違うだけでいつも同じものができます。)このへんは、プログラムを 工夫してみてください。
では、プログラムを見てみましょう。
メニューのリソース・スクリプトです。「署名作成・検証」のメニューアイテムには 初期状態でチェック(CHECKED)がついていることに注意してください。// hash03.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "新規作成(&N)...", IDM_NEW MENUITEM "開く(&O)...", IDM_OPEN MENUITEM "上書き保存(&S)", IDM_SAVE MENUITEM "名前を付けて保存(&A)...", IDM_SAVEAS MENUITEM SEPARATOR MENUITEM "終了(&X)...", IDM_END END POPUP "オプション(&O)" BEGIN MENUITEM "署名作成・検証", IDM_SIGN, CHECKED END END
親ウィンドウのクライアント領域にエディットコントロールを作るため エディットコントロールのIDをデファインしています。// hash03.cpp #define _WIN32_WINNT 0x0500 #ifndef STRICT #define STRICT #endif #define ID_EDIT 100 #include <windows.h> #include <windowsx.h> #include <wincrypt.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); BOOL MySign(HWND, char *); BOOL MyVerify(HWND, char *); BOOL MySaveAs(HWND); BOOL MySave(HWND); BOOL MyOpen(HWND); void MyNew(HWND); void IsSign(HWND hWnd); char szClassName[] = "hash03"; //ウィンドウクラス char szWinTitle[] = "猫でもわかる署名 [%s]"; HINSTANCE hInst; //元ファイル、プロッブファイル、署名ファイル char szOrgFile[MAX_PATH], szBlobFile[MAX_PATH], szSigFile[MAX_PATH]; //署名検証をするかどうか BOOL bSig = TRUE;
ダイアログボックスのプロシージャはなくなりました。
MySign, MyVerify関数の引数が増えました。
名前を付けて保存、上書き保存、ファイルを開くための関数が増えました。 IsSign関数はオプションで、署名作成等をしないで普通のエディタとして 使うかどうかをメニューに表示する関数です。
グローバル変数のbSigがTRUEであれば、ファイル書き込み時に署名、キー ファイルも作り、読み込み時には署名の正当性を検証します。
前章とほとんど同じですが、メインウィンドウの初期サイズを 少し大きくしてあります。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座標 420, //幅 260, //高さ 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; static HWND hEdit; switch (msg) { case WM_CREATE: hEdit = CreateWindow("Edit", "", WS_VISIBLE | WS_CHILD | ES_WANTRETURN | ES_MULTILINE | ES_AUTOHSCROLL | ES_AUTOVSCROLL | WS_VSCROLL | WS_HSCROLL, 0, 0, 0, 0, hWnd, (HMENU)ID_EDIT, hInst, NULL); SetFocus(hEdit); break; case WM_SIZE: MoveWindow(hEdit, 0, 0, LOWORD(lp), HIWORD(lp), TRUE); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_NEW: MyNew(hEdit); break; case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_SAVEAS: MySaveAs(hEdit); break; case IDM_SAVE: MySave(hEdit); break; case IDM_OPEN: MyOpen(hEdit); break; case IDM_SIGN: IsSign(hWnd); break; } break; case WM_CLOSE: if (SendMessage(hEdit, EM_GETMODIFY, 0, 0)) { id =MessageBox(hWnd, "内容が変更されています\n変更を保存しますか", "注意", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) MySaveAs(hWnd); } id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hEdit); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }
WM_CREATEメッセージが来たらエディットコントロールを作って、フォーカスをこれに合わせます。
WM_SIZEメッセージが来たらエディットコントロールの大きさを調整します。
メニューからの選択があったらそれぞれの関数を呼び出します。
終了時にエディットコントロールの内容が変更されていたら保存するかどうかを確認します。
終了時にはエディットコントロールを破棄します。(必ずしも必要ではありません)
署名をする関数です。前章と異なり本文はすでに読み込まれているので 引数からポインタをもらいます。本文をファイルから読み出す操作がなくなりました。BOOL MySign(HWND hWnd, char *lpszDescription) { HCRYPTPROV hProv = 0; HCRYPTKEY hKey = 0; HCRYPTHASH hHash = 0; char szBlob[1024]; DWORD dwRead, dwWrite, dwSigLen, dwBlobLen; BYTE pbSig[512]; HANDLE hKeyBlob, hSig; BOOL bEnd = FALSE; if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) { MessageBox(hWnd, "CryptAcquire Error", "Error", MB_OK); return FALSE; } if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) { MessageBox(hWnd, "CryptCreateHash Error", "Error", MB_OK); CryptReleaseContext(hProv, 0); return FALSE; } if (!CryptHashData(hHash, (BYTE *)lpszDescription, strlen(lpszDescription), 0)) { MessageBox(hWnd, "CryptHashData Error", "Error", MB_OK); 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), 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); CryptDestroyHash(hHash); CryptDestroyKey(hKey); CryptReleaseContext(hProv, 0); return TRUE; }
署名の正当性を検証する関数です。これも、前章では本文をファイルから読み出していましたが 引数からポインタをもらっています。BOOL MyVerify(HWND hWnd, char *lpszDescription) { HCRYPTPROV hProv = 0; HCRYPTKEY hKey = 0; HCRYPTHASH hHash = 0; char szBlob[1024], szSig[1024]; DWORD dwRead, dwBlobLen, dwSigLen, dwError; HANDLE hBlob, hSig; BOOL bEnd = FALSE; if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) { MessageBox(hWnd, "CryptAcquireContext Error", "Error", MB_OK); 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); 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(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(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(hBlob); CloseHandle(hSig); CryptReleaseContext(hProv, 0); return FALSE; } if (!CryptHashData(hHash, (BYTE *)lpszDescription, strlen(lpszDescription), 0)) { MessageBox(hWnd, "Hash Data Error", "Error", MB_OK); CloseHandle(hBlob); CloseHandle(hSig); CryptReleaseContext(hProv, 0); return FALSE; } if (CryptVerifySignature(hHash, (BYTE *)szSig, dwSigLen, hKey, NULL, 0)) MessageBox(hWnd, "正しいファイルです", "署名検証済", 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(hBlob); CloseHandle(hSig); CryptDestroyHash(hHash); CryptDestroyKey(hKey); CryptReleaseContext(hProv, 0); return TRUE; }
名前を付けて保存する関数です。bSigがTRUEならば署名ファイル、キーファイルの名前を 決めてMySign関数を呼びます。GetSaveFileName関数が戻ってくるとOPENFILENAME構造体の nFileExtensionには、パス付きファイル名の最初から何バイト目に拡張子が来ているかが 格納されます(最初が0バイト目)。これを利用してキーファイルや署名ファイるの 名前を作っています。BOOL MySaveAs(HWND hEdit) { OPENFILENAME of; char szOrgFileTitle[64], *lpszBuf, szTitleBar[64]; HANDLE hFile; int iTextLen; HGLOBAL hMem; DWORD dwWritten; HWND hParent; memset(&of, 0, sizeof(OPENFILENAME)); of.lStructSize = sizeof(OPENFILENAME); of.lpstrFilter = "テキスト(*.txt)\0*.txt\0すべて(*.*)\0*.*\0\0"; of.lpstrFile = szOrgFile; of.nMaxFile = MAX_PATH; of.lpstrFileTitle = szOrgFileTitle; of.nMaxFileTitle = 64; of.Flags = OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY; of.lpstrTitle = "名前を付けて保存"; of.lpstrDefExt = "txt"; if (!GetSaveFileName(&of)) return FALSE; hFile = CreateFile(szOrgFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { MessageBox(hEdit, "ファイをオープンできません", "Error", MB_OK); return FALSE; } iTextLen = GetWindowTextLength(hEdit); hMem = GlobalAlloc(GHND, iTextLen + 64);//多めにメモリを確保 lpszBuf = (char *)GlobalLock(hMem); Edit_GetText(hEdit, lpszBuf, iTextLen + 64); //最後のヌル文字はファイルに書き込まない WriteFile(hFile, lpszBuf, lstrlen(lpszBuf), &dwWritten, NULL); if (bSig) { //署名ファイル、キーファイルの名前を自動的に決める //元ファイルがabc.txtならばキーファイルはabc.key, 署名ファイルはabc.sig strcpy(szBlobFile, szOrgFile); szBlobFile[of.nFileExtension] = '\0'; strcat(szBlobFile, "key"); strcpy(szSigFile, szOrgFile); szSigFile[of.nFileExtension] = '\0'; strcat(szSigFile, "sig"); //署名する MySign(hEdit, lpszBuf); } //ハンドル類の後始末 CloseHandle(hFile); GlobalUnlock(hMem); GlobalFree(hMem); //タイトルバーに表示 hParent = GetParent(hEdit); wsprintf(szTitleBar, szWinTitle, szOrgFileTitle); SetWindowText(hParent, szTitleBar); SendMessage(hEdit, EM_SETMODIFY, (WPARAM)FALSE, 0); return TRUE; }
上書き保存の関数です。BOOL MySave(HWND hEdit) { int iLen; HGLOBAL hMem; char *lpszBuf; HANDLE hFile; DWORD dwWritten; if (strcmp(szOrgFile, "") == 0) { MessageBox(hEdit, "ファイル名がついていません", "Error", MB_OK); return FALSE; } iLen = GetWindowTextLength(hEdit); hMem = GlobalAlloc(GHND, iLen + 64); lpszBuf = (char *)GlobalLock(hMem); Edit_GetText(hEdit, lpszBuf, iLen + 64); hFile = CreateFile(szOrgFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { MessageBox(hEdit, "ファイをオープンできません", "Error", MB_OK); return FALSE; } WriteFile(hFile, lpszBuf, strlen(lpszBuf), &dwWritten, NULL); if (bSig) MySign(hEdit, lpszBuf); CloseHandle(hFile); GlobalUnlock(hMem); GlobalFree(hMem); SendMessage(hEdit, EM_SETMODIFY, (WPARAM)FALSE, 0); return TRUE; }
ファイルを開く関数です。bSigがTRUEならばMyVerify関数を呼んで署名確認をします。BOOL MyOpen(HWND hEdit) { int id; OPENFILENAME of; HANDLE hFile; DWORD dwLength, dwRead; HGLOBAL hMem; char *lpszBuf, szTitleBar[64], szOrgFileTitle[64]; HWND hParent; if (SendMessage(hEdit, EM_GETMODIFY, 0, 0)) { id = MessageBox(hEdit, "内容が変更されています。\n内容を保存しますか", "注意", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) MySaveAs(hEdit); } memset(&of, 0, sizeof(OPENFILENAME)); of.lStructSize = sizeof(OPENFILENAME); of.lpstrFilter = "テキスト(*.txt)\0*.txt\0すべて(*.*)\0*.*\0\0"; of.lpstrFile = szOrgFile; of.nMaxFile = MAX_PATH; of.lpstrFileTitle = szOrgFileTitle; of.nMaxFileTitle = 64; of.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; of.lpstrTitle = "ファイルのオープン"; of.lpstrDefExt = "txt"; if (!GetOpenFileName(&of)) return FALSE; hFile = CreateFile(szOrgFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { MessageBox(hEdit, "ファイをオープンできません", "Error", MB_OK); return FALSE; } dwLength = GetFileSize(hFile, NULL); hMem = GlobalAlloc(GHND, dwLength + 1); lpszBuf = (char *)GlobalLock(hMem); //最後にヌル文字を付ける ReadFile(hFile, lpszBuf, dwLength + 1, &dwRead, NULL); Edit_SetText(hEdit, lpszBuf); if (bSig) { //元ファイル名からキーファイル、署名ファイルの名前を決める strcpy(szBlobFile, szOrgFile); szBlobFile[of.nFileExtension] = '\0'; strcat(szBlobFile, "key"); strcpy(szSigFile, szOrgFile); szSigFile[of.nFileExtension] = '\0'; strcat(szSigFile, "sig"); //署名確認 MyVerify(hEdit, lpszBuf); } CloseHandle(hFile); GlobalUnlock(hMem); GlobalFree(hMem); wsprintf(szTitleBar, szWinTitle, szOrgFileTitle); hParent = GetParent(hEdit); SetWindowText(hParent, szTitleBar); return TRUE; }
新規作成の関数です。void MyNew(HWND hEdit) { int id; HWND hParent; if (SendMessage(hEdit, EM_GETMODIFY, 0, 0)) { id = MessageBox(hEdit, "内容が変更されています\n変更を保存しますか", "注意", MB_YESNO); if (id == IDYES) MySaveAs(hEdit); } Edit_SetText(hEdit, ""); strcpy(szOrgFile, ""); strcpy(szBlobFile, ""); strcpy(szSigFile, ""); hParent = GetParent(hEdit); SetWindowText(hParent, "猫でもわかる署名"); return; }
メニューの「署名作成・検証」項目にチェックを付けたりはずしたり する関数です。同時にbSigをTRUEにしたりFALSEにしたりします。void IsSign(HWND hWnd) { HMENU hMenu; MENUITEMINFO mi; memset(&mi, 0, sizeof(MENUITEMINFO)); mi.cbSize = sizeof(MENUITEMINFO); mi.fMask = MIIM_STATE; hMenu = GetMenu(hWnd); if (bSig) { mi.fState = MFS_UNCHECKED; bSig = FALSE; } else { mi.fState = MFS_CHECKED; bSig = TRUE; } SetMenuItemInfo(hMenu, IDM_SIGN, FALSE, &mi); return; }
メニュー項目にチェックを付けたりはずしたりするプログラムは 忘れやすいので復習しておいてください (第253章の真ん中頃にはやわかり表があります)。
Update 09/May/2000 By Y.Kumei