はじめに
仕事をしているとパスワードを入力する場面が何度もあります。共用サーバーの接続スクリプトなど自組織内で作成したスクリプトやバッチファイルを使う人も多いかと思いますが,入力したパスワードが平文のまま表示されてしまうバッチファイルに遭遇すると
こ の バ ッ チ を 作 っ た の は 誰 だ ! ?
と作者を呼び出したくなります。とはいえ,Windows のバッチファイルで入力パスワードの隠匿化を行うのはとても難しいようです。
ということで自作することにしました。良いアイデアが思い浮かんだので。
最初に結論を書く
それはエスケープシーケンスを使うというものです。Windows10 の途中で可能になったテクニックです。要は文字の色を背景色と同じにしてしまえば入力した文字は見えなくなります。サンプルコードを以下に示します。注意事項としてはパスワード入力後に文字の色と背景色を元に戻す必要があります。この際に改行なしでエスケープシーケンスを出力する必要があるので,ちょっとしたテクニック1を使っています。
エスケープシーケンス概説
エスケープシーケンスについての詳しい情報は Microsoft 公式サイト2 を参照下さい。以下は上記のバッチファイルで使った機能に関する部分のみを抜粋したものです。
シーケンス | コード | 説明 | 動作 |
---|---|---|---|
ESC [ 0 m |
SGR | 既定値 | すべての属性を変更前の既定の状態に戻します |
ESC [ 30 m |
SGR | 前景白 | 非ボールドの明るい黒を前景に適用します |
ESC [ 40 m |
SGR | 背景白 | 非ボールドの明るい黒を背景に適用します |
実行例
実行例を以下に示します。入力した文字は見えません。カーソルだけ見えます。
人によってはコマンドプロンプトの背景色を変えている人がいるかもしれません。その場合は何をやっているのかバレバレです。
注意事項
注意事項としては,文字の色を背景色と同じにして見えなくしているだけですから,パスワードを入力した付近を選択してコピー&ペーストすれば入力した文字を復元できます。なので早めに CLS
コマンドを呼び出して画面クリアをしたほうが良いでしょう。
ちなみにパスワード入力中に Ctrl-C
などで中断すると,それ以降文字が見えなくなってしまうのではないかと危惧したのですが,実際試してみると何故か Ctrl-C
を二回連打しないと終了できなかったりと動作自体に謎は残るものの,既定の状態に戻すエスケープシーケンスが実行されて無事復帰できました。
補足)正統派な手段
おそらく一番正統派な解決手段3は PowerShell を使うというものです。PowerShell だと下記のように DOTNET の機能を使えるので,奇をてらったテクニックを使うこともなく普通に書けます。SecureString
オブジェクトになってしまうのは過剰包装的な感じもしますが。
$obj = Read-Host "Password" -AsSecureString
$str = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR(sobj)
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($str)
Write-Host "Your entered password is $password"
実行画面もこれぞあるべき姿という感じです。
c:\Qiita>.\test.ps1
Password: ********
Your entered password is password
PowerShell 7.1 以降だともっとスッキリ書けます。
$password = Read-Host "Password" -MaskInput
Write-Host "Your entered password is $password"
補足その2)超絶技巧
バッチファイルでも技巧を凝らせばできるという参考文献4のテクニックについても紹介しておきましょう。下記はオリジナルのエッセンスを自分なりに咀嚼して作り直したものです。特殊なテクニックを利用してパスワード文字列を一文字ずつ入力するほか,バックスペースによる修正にも対応しています。なおオリジナルは何も表示しませんが,アスタリスク *
を表示するよう筆者が手を加えています。なお,パスワードの文字数も貴重な情報ですから,アスタリスクを表示してしまうのは改悪かもしれません。
実行例を以下に示しますが,とてもいい感じです。
上記のプログラムの基本原理(文字入力方法)についても説明しておきましょう。これは xcopy
コマンドの /W
オプション,すなわち「コピーを開始する前に任意のキーを押すことを求めるメッセージを表示する」機能を用いたものです。このオプションを用いると Enter キーなしの1文字入力が可能で,入力した文字を取得することができます。ちなみに pause
コマンドも Enter キーなしの1文字入力待ちが可能ですが,入力した文字をエコーバックしないので,このような用途に用いることができません。
こうして出力された4行のうちの先頭行の末尾文字(下記の例では y
)が入力された文字となります。また3行目だけは標準エラー出力に出力されるので NUL
デバイスにリダイレクトして捨てる必要があります。
なお,xcopy
コマンドで指定するコピー元ファイルは確実に存在しなくてはなりません。存在しなければメッセージが表示される前にエラー終了となるからです。このためコピー元とコピー先のファイルに自分自身 %0
を指定しています。
他には replace
コマンドを使う方法もあります。replace
コマンドは「指定されたドライブやディレクトリから同じファイルを探して置き換える」というコマンドです5。/U
オプションは送り側ファイルよりも古いファイルのみを置き換えるというもの,/W
オプションはファイルの検索開始前に待機する(ユーザーのキー入力を待つ)というものです。カレントディレクトリに存在する名前が一文字のファイル(ワイルドカード ?
にマッチングするファイル名)をカレントディレクトリ .
にコピーしようとしているので仮にファイルが存在していてもタイムスタンプは必ず一致し,ファイルの置き換えは行われません。また一文字のファイルが存在しなくてもエラーにはなりません。
上記以外に驚いた点を以下述べます。
-
これまで
for
コマンドのループ変数は英字一文字(大文字・小文字は区別するので全52種)だと思っていたのですが,#
とか英字以外の文字も使えるんですね。筆者が確認したところ,これ以外に@
,$
,_
などが変数名に使えるようです。 -
オリジナルプログラムではバックスペース記号を取得するために
prompt
コマンドを用いています。prompt
コマンドではバックスペースを$H
という印字可能な文字列で指定できるからです。こうして一時的にプロンプトをバックスペース記号に置き換え,これを読み出すことでバックスペース記号を取得しています。本記事ではバッチファイル内に直接バックスペース記号を埋め込んでいるので,このテクニックを使っていません。
ちなみに通常コマンドプロンプト上ではエスケープキーを押すと,その行の入力がキャンセルされますが,残念ながら上記の TEST2.CMD
ではエスケープキーによる入力キャンセルに対応していません。これは xcopy
コマンドを用いた文字入力方法ではエスケープキーの入力を検知できないからです。
おまけ
今回の記事で作成したバッチファイルです。
20250310_Password.zip (google-drive)