問題編
仕事でGnuPGを使用している。Windows上で使うこともあり、今までは古いWinPT*1を使用していた。鍵の管理はWinPT経由で行い、メールの暗号化/復号化にはEnigmailを使用し、ファイルの暗号化/復号化は内製のツールを使用していた。
古いWinPTを使っていた為、これまでGnuPGは1.2系だった。これがEnigmail 1.4よりGnuPG 1.4系でないと動作しないようになってしまったので、GnuPGのバージョンを上げることになった。
で、ここでWinPTの後継であるGnuPTにするか別アプリのGpg4winにするか迷ったのだけど、思い切ってGnuPG本体のみ*2に切り替えてしまうことにした。幸いにもEnigmailも内製のファイル暗号化/復号化ツールもGnuPG 1.4系で動作するので、鍵の管理はコンソールで行うことになるもののそれ以外の作業は従来通りに行える。
ただ1点だけ困ったことがある。ごく稀に二重に暗号化されたメールが届くことがある。Enigmailで復号化してもGnuPGで暗号化された文章が表示されるだけなので、更に復号化しなくてはならない。今まではここで暗号化された文章をクリップボードにコピーし、WinPTの「クリップボードの内容の復号化」の機能を使用して復号化していた。WinPTを使わなくなるので、代替案を考えなくてはならない。
解決編:構想
「クリップボードを使う」という方法自体は便利なので、そのアイデアは生かしたい。gpg.exeは標準入力から読み込んだデータを復号化できるので、クリップボードからテキストを取り出して標準出力に垂れ流すツールを作ってしまえば何とかなりそうだ。
ただクリップボードを経由すると何処かで文字コードの変換が発生するように思うのだが*3、暗号化されたメールの中身を見る限りASCIIの範囲の文字を使っていそうなので*4、CF_TEXTかCF_OEMTEXTでテキストを取得すればセーフだろう。
厄介なのは復号化したテキストの表示で、というのも復号後の文字コードはISO-2022-JPかもしれないしUTF-8かもしれないしShift_JISかもしれないのだ。ここは全くの他力本願で、「復号化したデータを一時ファイルに書き出し、適当なテキストエディタ/ビューアで表示する」という方法で誤魔化すことにした。
一旦は「復号化したデータをクリップボードにコピーする」というのも考えたのだけど、文字コードの判定と変換が必要そうだったので止めた。
解決編:実装
クリップボードからテキストを取り出すツールをC言語で書いた。Windows APIを叩くコンソールアプリだ。
/* ********************************************************************** */ /** * @brief rtcb; Read Text from ClipBoard * @author eel3 @ TRASH BOX * @date 2012/05/05 * * @par 動作確認済み環境: * - Microsoft Windows XP Professional (32bit) SP3 * * @par 確認済みコンパイラ: * - Microsoft(R) Visual Studio 2005 SP1 * - Microsoft(R) Visual Studio 2010 * - TDM-GCC 4.5.1 */ /* ********************************************************************** */ #ifndef __MINGW32__ #define _CRT_SECURE_NO_WARNINGS #pragma comment(lib, "User32.lib") #endif #include <assert.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <io.h> #include <tchar.h> #include <windows.h> #ifndef STDIN_FILENO #define STDIN_FILENO 0 #endif #ifndef STDOUT_FILENO #define STDOUT_FILENO 1 #endif #ifdef __MINGW32__ #ifndef _tperror #ifdef UNICODE #define _tperror _wperror #else #define _tperror perror #endif #endif typedef DWORD GS_SIZE_T; #else typedef SIZE_T GS_SIZE_T; #endif /* ---------------------------------------------------------------------- */ /* 関数の定義 */ /* ---------------------------------------------------------------------- */ /* ====================================================================== */ /** * @brief エラー出力して終了する * * @param[in] s エラー出力のヘッダとなる文字列 * @param[in] last_error エラーコード */ /* ====================================================================== */ static void error_exit(const LPCTSTR s, const DWORD last_error) { LPTSTR buf; assert(s != NULL); FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, NULL, last_error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &buf, 0, NULL); _ftprintf(stderr, _T("%s: %s"), s, buf); LocalFree((HLOCAL) buf); exit(EXIT_FAILURE); } /* ********************************************************************** */ /** * @brief メインルーチン * * @retval EXIT_SUCCESS 正常終了 * @retval EXIT_FAILURE 異常終了 */ /* ********************************************************************** */ int main(void) { #define ERREXIT(s) \ DWORD err = GetLastError(); \ (void) CloseClipboard(); \ error_exit(_T(s), err); HANDLE hMem; LPVOID p; GS_SIZE_T size; errno = 0; if (_setmode(STDOUT_FILENO, O_BINARY) == -1) { _tperror(_T("_setmode")); return EXIT_FAILURE; } if (!OpenClipboard(NULL)) { error_exit(_T("OpenClipboard"), GetLastError()); } if (!IsClipboardFormatAvailable(CF_OEMTEXT)) { ERREXIT("IsClipboardFormatAvailable"); } if ((hMem = GetClipboardData(CF_OEMTEXT)) == NULL) { ERREXIT("GetClipboardData"); } if ((p = GlobalLock(hMem)) == NULL) { ERREXIT("GlobalLock"); } if ((size = GlobalSize(hMem)) > 1) { /* ヌル文字は出力しない */ (void) fwrite(p, size - 1, 1, stdout); } (void) GlobalUnlock(hMem); (void) CloseClipboard(); return EXIT_SUCCESS; #undef ERREXIT }
MinGW(TDM-GCC)とVisual Studio 2005 or 2010でビルドできる。不要かもしれないが、念の為_setmode()を使用してバイナリデータでも問題なく標準出力に流し込めるようにしている。
このアプリをrtcb.exeという名前でビルドした上で、次のようなバッチファイルでラッピングした。
@echo off setlocal for /f "usebackq tokens=1-7 delims=/:." %%A in (`echo %DATE%/%TIME: =0%`) do ( set TMPNAM="C:\tmp\__rtcb_tmp_file_%%A%%B%%C%%D%%E%%F%%G.dat" ) %~dp0rtcb.exe | gpg.exe -d > %TMPNAM% ttpage %TMPNAM% del %TMPNAM% endlocal
このバッチファイルは以下の環境を想定している。
- このバッチファイルをrtcb.exeと同じフォルダに配置している。
- 一時作業用フォルダとしてC:\tmpを使用している。
- 環境変数PATHにgpg.exeの置いてあるフォルダが登録されている。
- テキストビューアとしてttpageが使用可能で、且つ環境変数PATHに登録済み。
一時ファイルの名前のつけ方は適当。安全な方法ではない。
あとクリップボードにテキストが無かった場合や暗号化データ以外のテキストだった場合の処理を全く考慮していない。あくまで自分用のツールなので運用でカバーする腹積もりだ。