暗号化されたファイル(*.enc)をメニューの「ファイル」「開く」で開くと
当然意味のない表示が現れます。「ファイル」「復号」を選択すると
パスワード入力欄が使用可能になるのでここに、パスワードを入力します。
OKボタンを押すと、復号されて意味のある表示となります。
この時復号された内容はテキストファイル(*.txt)として保存されます。
署名についても「ファイル」メニューから「署名を確認して開く」
「署名をして上書き保存」「名前を付けて署名保存」を選択できます。
キーファイルは*.key、署名ファイルは*.sigとなります。
では、プログラムを見てみましょう。
普通のメニューのリソース・スクリプトです。// hash04.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "新規作成(&N)...", IDM_NEW MENUITEM "開く(&O)...", IDM_OPEN_SIMPLE MENUITEM "上書き保存(&S)", IDM_SAVE_SIMPLE MENUITEM "名前を付けて保存(&A)...", IDM_SAVEAS_SIMPLE MENUITEM SEPARATOR MENUITEM "署名を確認して開く...", IDM_OPEN MENUITEM "署名をして上書き保存", IDM_SAVE MENUITEM "名前を付けて署名保存...", IDM_SAVEAS MENUITEM SEPARATOR MENUITEM "暗号化(&E)...", IDM_ENCRYPT MENUITEM "復号(&D)...", IDM_DECRYPT MENUITEM SEPARATOR MENUITEM "終了(&X)...", IDM_END END END
いつもとほとんど変わりません。// hash04.cpp #define _WIN32_WINNT 0x0500 #ifndef STRICT #define STRICT #endif #define ID_EDIT 100 #define ID_STATIC 101 #define ID_PASS 102 #define ID_BUTTON_CLR 103 #define ID_BUTTON_OK 104 #define STATIC_HEIGHT 35 #include <windows.h> #include <windowsx.h> #include <wincrypt.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyStaticProc(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); BOOL MyEncrypt(HWND); BOOL MyDecrypt(HWND hEdit); char szClassName[] = "hash04"; //ウィンドウクラス char szWinTitle[] = "猫でもわかる署名 [%s]"; HINSTANCE hInst; //元ファイル、プロッブファイル、署名ファイル char szOrgFile[MAX_PATH], szBlobFile[MAX_PATH], szSigFile[MAX_PATH]; //暗号化されたファイル、復号されたファイル char szEncFile[MAX_PATH], szDecFile[MAX_PATH]; //パスワード char szPass[64]; //署名検証をするかどうか BOOL bSig = TRUE; //元々のスタティックコントロールのプロシージャ WNDPROC OrgStaticProc; //スタティックコントロール上のエディットコントロール、ボタンのハンドル HWND hPass, hButOK, hButCLR; //表示用エディットコントロール HWND g_hEdit; //暗号化か復号か BOOL bEncrypt = 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 hStatic, hEdit; MINMAXINFO *lpmmi; switch (msg) { case WM_INITMENU: if (IsWindowEnabled(hPass)) { MessageBox(hWnd, "パスワードを入力してOKボタンを押してください", "注意", MB_OK); } break; case WM_GETMINMAXINFO: lpmmi = (MINMAXINFO *)lp; lpmmi->ptMinTrackSize.x = 340; lpmmi->ptMinTrackSize.y = 140; break; 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); g_hEdit = hEdit; hStatic = CreateWindow("Static", "", WS_CHILD | WS_VISIBLE | WS_BORDER, 0, 0, 0, 0, hWnd, (HMENU)ID_STATIC, hInst, NULL); //スタティックコントロールのサブクラス化 OrgStaticProc = (WNDPROC)GetWindowLong(hStatic, GWL_WNDPROC); SetWindowLong(hStatic, GWL_WNDPROC, (LONG)MyStaticProc); hPass = CreateWindow("Edit", "", WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | WS_BORDER | ES_PASSWORD, 5, 5, 200, 25, hStatic, (HMENU)ID_PASS, hInst, NULL); EnableWindow(hPass, FALSE); hButCLR = CreateWindow("Button", "clr", WS_CHILD | WS_VISIBLE, 210, 5, 50, 25, hStatic, (HMENU)ID_BUTTON_CLR, hInst, NULL); EnableWindow(hButCLR, FALSE); hButOK = CreateWindow("Button", "OK", WS_CHILD | WS_VISIBLE, 270, 5, 50, 25, hStatic, (HMENU)ID_BUTTON_OK, hInst, NULL); EnableWindow(hButOK, FALSE); SetFocus(hEdit); break; case WM_SIZE: MoveWindow(hStatic, 0, 0, LOWORD(lp), STATIC_HEIGHT, TRUE); MoveWindow(hEdit, 0, STATIC_HEIGHT, LOWORD(lp), HIWORD(lp) - STATIC_HEIGHT, 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: bSig = TRUE; MySaveAs(hEdit); break; case IDM_SAVEAS_SIMPLE: bSig = FALSE; MySaveAs(hEdit); break; case IDM_SAVE: bSig = TRUE; MySave(hEdit); break; case IDM_SAVE_SIMPLE: bSig = FALSE; MySave(hEdit); break; case IDM_OPEN: bSig = TRUE; MyOpen(hEdit); break; case IDM_OPEN_SIMPLE: bSig = FALSE; MyOpen(hEdit); break; case IDM_ENCRYPT: if (strcmp(szOrgFile, "") == 0) { MessageBox(hWnd, "保存してから暗号化してください", "注意", MB_OK); break; } EnableWindow(hPass, TRUE); EnableWindow(hButOK, TRUE); EnableWindow(hButCLR, TRUE); bEncrypt = TRUE; MessageBox(hEdit, "パスワードを記入してOKボタンを押してください", "パスワード", MB_OK); SetFocus(hPass); break; case IDM_DECRYPT: if (strcmp(szOrgFile, "") == 0) { MessageBox(hWnd, "暗号ファイルを開いてから復号してください", "注意", MB_OK); break; } EnableWindow(hPass, TRUE); EnableWindow(hButOK, TRUE); EnableWindow(hButCLR, TRUE); bEncrypt = FALSE; MessageBox(hEdit, "パスワードを記入してOKボタンを押してください", "パスワード", MB_OK); SetFocus(hPass); 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) { SetWindowLong(hStatic, GWL_WNDPROC, (LONG)OrgStaticProc); DestroyWindow(hStatic); DestroyWindow(hButCLR); DestroyWindow(hButOK); DestroyWindow(hPass); DestroyWindow(hEdit); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }
WM_INITMENUメッセージが来たら、パスワード入力窓が使用可能かどうかを 調べて、可能なら注意を促すメッセージを出します。どういうことかというと メニューの「暗号化」または「復号」を選択するとパスワード入力窓が入力可能に なります。しかし、その時点でパスワードを入れずにメニューから他の作業を 選択されると、ちょっとまずいことになります。それで、「暗号化」「復号」を選択した 場合は、パスワードを入力してOKボタンを押すまでメニューは使えない ようにしたわけです。
WM_GETMINMAXINFOメッセージが来たら、ウィンドウの最小のサイズを指定します。 ウィンドウをあまり小さくされて、ボタン等が隠れてしまわないようにする ためです。WM_GETMINMAXINFOメッセージについては 第208章を参照してください。
WM_CREATEメッセージか来たら、本文表示用エディットコントロール、ボタン類、 パスワード入力用エディットコントロール、ボタン類を乗せるスタティックコントロール を作ります。「OK」「CLR」はスタティックコントロールの子ウィンドウなので これらのボタンの通知メッセージはスタティックコントロールにやってきます。 それで、スタティックコントロールをサブクラス化して通知メッセージの処理を できるようにします。なお、サブクラス化については第36章 などを参照してください。
また、この時点ではパスワード入力窓、ボタンは使用不可にしておきます。
WM_SIZEメッセージが来たら、スタティックコントロール、本文表示用エディットコントロールの サイズを調整します。
メニューからいろいろな項目が選択されたら、それぞれの関数を呼び出します。
プログラム終了時にはサブクラス化を解除しておきます。
MySign, MyVerify, MySaveAs関数に変更はありません。
上書き保存をする関数です。メニューの「上書き保存」を選択したときは bSigがFALSEになっているので単なる上書き保存です。「署名をして上書き保存」 を選択した場合はbSigがTRUEになっているので署名をします。BOOL MySave(HWND hEdit) { int iLen; HGLOBAL hMem; char *lpszBuf, *p; int result; 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) { strcpy(szBlobFile, szOrgFile); p = strrchr(szBlobFile, '.'); result = p - szBlobFile; szBlobFile[result + 1] = '\0'; strcat(szBlobFile, "key"); strcpy(szSigFile, szOrgFile); p = strrchr(szSigFile, '.'); result = p - szSigFile; szSigFile[result + 1] = '\0'; strcat(szSigFile, "sig"); MySign(hEdit, lpszBuf); } CloseHandle(hFile); GlobalUnlock(hMem); GlobalFree(hMem); SendMessage(hEdit, EM_SETMODIFY, (WPARAM)FALSE, 0); return TRUE; }
さて、エディットコントロールから入力を行って、「署名をして上書き保存」を選択したときは ファイル名が何も決まっていないので、一番最初の段階で「ファイル名がついていません」 とメッセージボックスが出ます。しかし、「名前を付けて保存」をしておくとszOrgFile に元ファイル名が格納されます。そして、続けて「署名をして上書き保存」が選択されたとき 署名ファイルとキーファイルの名前がまだ、決まっていません。
そこで、szOrgFileから 署名ファイルとキーファイルの名前を付けます。元ファイルがabc.txtなら署名ファイルは abc.sig、キーファイルはabc.keyとします。拡張子を変えればよいのですが、これが 簡単そうでめんどうです。まずはピリオドの位置を検索しなければいけないことは わかると思いますが、abc.def.txtのように複数のピリオドが存在するファイル名も 許されているので話が複雑になります。実は文字列の最後からある文字を検索する 関数というのがあってこれを使えば簡単なプログラムになります。
(strchr関数と間違えないでください)char *strrchr( const char *string, int c );
文字列を走査し、指定された文字の最後の出現箇所を見つけます。 これにより最後にピリオドが出現した所を見つけます。戻り値が そこのポインタとなります。戻り値をpとするとstring[p-string]がピリオドです。 その次の文字をヌル文字にするとピリオドで終わる文字列となります。 後はstrcat関数で拡張子を付けてやれば、新しいファイル名ができあがります。
MyOpen, MyNew関数に変更はありません。IsSign関数はなくなりました
サブクラス化されたスタティックコントロールのプロシージャです。LRESULT CALLBACK MyStaticProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_COMMAND: switch (LOWORD(wp)) { case ID_BUTTON_OK: if (SendMessage(g_hEdit, EM_GETMODIFY, 0, 0)) { MessageBox(hWnd, "内容が変更されています。内容を保存してください", "保存", MB_OK); MySaveAs(g_hEdit); } Edit_GetText(hPass, szPass, sizeof(szPass)); if (strcmp(szPass, "") == 0) { MessageBox(hWnd, "パスワードが指定されていません", "パスワード", MB_OK | MB_ICONEXCLAMATION); break; } if (bEncrypt) MyEncrypt(g_hEdit); else MyDecrypt(g_hEdit); Edit_SetText(hPass, ""); EnableWindow(hPass, FALSE); EnableWindow(hButCLR, FALSE); EnableWindow(hButOK, FALSE); break; case ID_BUTTON_CLR: Edit_SetText(hPass, ""); break; } break; default: return CallWindowProc(OrgStaticProc, hWnd, msg, wp, lp); } return 0; }
OKボタンが押されたら暗号化または、復号をします。この時szOrgFileで示されているファイルの内容と エディットコントロールに表示されている内容に違いあってはまずいので、EM_GETMODIFYメッセージで 確認をしています。また、パスワードが何も記入されたいないときも困るので注意を促します。 復号、または暗号化が終わったらパスワード入力窓、ボタンは使用不可にしておきます。
第267章で作ったMyHash関数とほとんど同じです。今回は暗号化する内容は エディットコントロールに表示されていますが、かならずファイルから読み出して 暗号化します。BOOL MyEncrypt(HWND hEdit) { HCRYPTPROV hProv = 0; HCRYPTKEY hKey = 0; HCRYPTHASH hHash = 0; int nLen, result; HGLOBAL hMem; char *lpszBuf; DWORD dwLength; HANDLE hOrg, hDest; DWORD dwRead, dwWritten; char szBuffer[1024 + 64], *pdset; BOOL bEnd = FALSE, bReturnValue = FALSE; strcpy(szEncFile, szOrgFile); pdset = strrchr(szEncFile, '.'); result = pdset - szEncFile; szEncFile[result + 1] = '\0'; strcat(szEncFile, "enc"); nLen = GetWindowTextLength(hEdit); hMem = GlobalAlloc(GHND, nLen + 1); if (hMem == NULL) { MessageBox(hEdit, "メモリが確保できません", "Error", MB_OK); goto Error; } lpszBuf = (char *)GlobalLock(hMem); Edit_GetText(hEdit, lpszBuf, nLen + 1); if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) { MessageBox(hEdit, "デフォルトCSVのハンドルが取得できません", "Error", MB_OK); goto Error; } if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) { MessageBox(hEdit, "ハッシュオブジェクトの作成失敗", "Error", MB_OK); goto Error; } dwLength = strlen(szPass); if (!CryptHashData(hHash, (BYTE *)szPass, dwLength, 0)) { MessageBox(hEdit, "ハッシュオブジェクトにデータを追加できません", "Error", MB_OK); goto Error; } if (!CryptDeriveKey(hProv, CALG_RC2, hHash, CRYPT_EXPORTABLE, &hKey)) { MessageBox(hEdit, "キー生成に失敗しました", "Error", MB_OK); goto Error; } hOrg = CreateFile(szOrgFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hOrg == INVALID_HANDLE_VALUE) { MessageBox(hEdit, "元ファイルをオープンできません", "Error", MB_OK); goto Error; } hDest = CreateFile(szEncFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hDest == INVALID_HANDLE_VALUE) { MessageBox(hEdit, "出力先ファイルをオープンできません", "Error", MB_OK); goto Error; } while (1) { ReadFile(hOrg, szBuffer, 1024, &dwRead, NULL); if (dwRead < 1024) bEnd = TRUE; else bEnd = FALSE; CryptEncrypt(hKey, 0, bEnd, 0, (BYTE *)szBuffer, &dwRead, sizeof(szBuffer)); WriteFile(hDest, szBuffer, dwRead, &dwWritten, NULL); if (bEnd) break; } bReturnValue = TRUE; Error: if (hOrg) CloseHandle(hOrg); if (hDest) CloseHandle(hDest); if (hHash) CryptDestroyHash(hHash); if (hKey) CryptDestroyKey(hKey); if (hProv) CryptReleaseContext(hProv, 0); if (bReturnValue) { wsprintf(szBuffer, "暗号化ファイルは\n%s\nとして保存しました", szEncFile); MessageBox(hEdit, szBuffer, "完了", MB_OK); } if (hMem) GlobalFree(hMem); return bReturnValue; }
復号する関数です。これも第267章で作った MyDecrypt関数とほとんど同じです。ただ復号したものを*.txtで保存するので 暗号化ファイル名(*.enc)より*.txtのファイル名を作るところが追加されています。BOOL MyDecrypt(HWND hWnd) { HCRYPTPROV hProv = 0; HCRYPTKEY hKey = 0; HCRYPTHASH hHash = 0; char szBuffer[1024 + 64], *pdset, *lpszTxt, szTitle[64], szBarTitle[64]; DWORD dwLength, dwRead, dwWrite, dwSize; HANDLE hOrg, hDest; BOOL bEnd = FALSE; int result; HGLOBAL hMem; BOOL bReturnValue = FALSE; //暗号化ファイル名*.encから*.txtを作る strcpy(szDecFile, szOrgFile); pdset = strrchr(szDecFile, '.'); if (strcmp(pdset, ".enc") != 0) { MessageBox(hWnd, "現在開いているファイルは暗号化されたファイルではありません", "Error", MB_OK); return FALSE; } result = pdset - szDecFile; szDecFile[result + 1] = '\0'; strcat(szDecFile, "txt"); if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) { MessageBox(hWnd, "CryptAcquireContext Error", "Error", MB_OK); goto Error; } if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) { MessageBox(hWnd, "CryptCreateHash Error", "Error", MB_OK); goto Error; } dwLength = strlen(szPass); if (!CryptHashData(hHash, (BYTE *)szPass, dwLength, 0)) { MessageBox(hWnd, "CryptHashData Error", "Error", MB_OK); goto Error; } if (!CryptDeriveKey(hProv, CALG_RC2, hHash, CRYPT_EXPORTABLE, &hKey)) { MessageBox(hWnd, "CryptDeriveKey Error", "Error", MB_OK); goto Error; } 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); goto Error; } dwSize = GetFileSize(hOrg, NULL); hMem = GlobalAlloc(GHND, dwSize + 64); if (hMem == NULL) { MessageBox(hWnd, "メモリを確保できません", "Error", MB_OK); goto Error; } lpszTxt = (char *)GlobalLock(hMem); hDest = CreateFile(szDecFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hOrg == INVALID_HANDLE_VALUE) { MessageBox(hWnd, "復号化ファイルをオープンできません", "Error", MB_OK); goto Error; } while (1) { ReadFile(hOrg, szBuffer, 1024, &dwRead, NULL); if (dwRead < 1024) bEnd = TRUE; else bEnd = FALSE; CryptDecrypt(hKey, 0, bEnd, 0, (BYTE *)szBuffer, &dwRead); WriteFile(hDest, szBuffer, dwRead, &dwWrite, NULL); szBuffer[dwWrite] = '\0'; strcat(lpszTxt, szBuffer); if (bEnd) break; } lpszTxt[GetFileSize(hDest, NULL)] = '\0'; Edit_SetText(g_hEdit, lpszTxt); wsprintf(szBuffer, "復号化を完了して\n%s\nとして保存しました", szDecFile); MessageBox(hWnd, szBuffer, "完了", MB_OK); strcpy(szOrgFile, szDecFile); GetFileTitle(szOrgFile, szTitle, sizeof(szTitle)); wsprintf(szBarTitle, szWinTitle, szTitle); SetWindowText(GetParent(hWnd), szBarTitle); bReturnValue = TRUE; Error: if (hMem) { GlobalUnlock(hMem); GlobalFree(hMem); } if (hOrg) CloseHandle(hOrg); if (hDest) CloseHandle(hDest); if (hHash) CryptDestroyHash(hHash); if (hKey) CryptDestroyKey(hKey); if (hProv) CryptReleaseContext(hProv, 0); return bReturnValue; }
ファイルを復号する場合も暗号化ファイルを読み込んでエディットコントロールに 表示しますが暗号化ファイルはテキストファイルではないので、エディット校とロールの 内容が必ずしも暗号化ファイルの内容と一致しません。復号する場合も必ず 暗号化ファイルを読みだして、それを復号します。
今回のプログラムでいろいろ遊んでみてください。
Update 21/May/2000 By Y.Kumei