 | レベル: 中級 Nathan Harrington (harrington.nathan@gmail.com), Programmer, IBM
2007年 12月 04日 入力の合計時間を測定してキーストロークの間隔を検証し、どんなデータが入力されたとしてもユーザーを認証できるようにしてください。さらに、パスワードにバックスペースや改行などの印字不能文字を含めるようにして、新しいレベルのパスワード難読化を実現してください。この記事ではオープンソースのツール、xev と Perl をキーストローク・ダイナミックスの分野に適用して、人間とコンピューターとの相互作用での人によって微妙に異なる特性を測定する方法を説明します。
人が指先で何かに触れると、そこに残された指紋によって、その本人を識別することができます。キー操作の場合、キータッチの仕方、特にタイピングがそれぞれの個人に特有であることも珍しくありません。キーストローク・ダイナミックスとは、タイピング・パターンの統計的分析によって個人の識別を可能にする比較的新しい分野です。市販用の製品の多くは、パスワード入力のダイナミックス (動的性) を分析するとともに、連続入力をモニターしてセキュリティーを強化しています。この記事ではサンプル・コードを例に、認証とデータの連続入力に関連して、アプリケーションのセキュリティーを強化するキーストローク・ダイナミックスについて説明します。
要件
ソフトウェア
Linux® で X Window System (Gnome または KDE など) がセットアップされて機能している必要があります。1999年以降にリリースされたグラフィカル・デスクトップを備えたディストリビューションであれば、構成済みの環境としてすぐに作業を開始できます。X Window イベントを効率的に取得して処理するというタスクは、xev (X イベント・ビューアー) などのプログラムで操作するのが最適です。xev をダウンロードするには「参考文献」を参照してください。xev のソース・コードがインストール済みであるか、またはディストリビューションにパッケージとして用意されている場合でもダウンロードする必要があります。さらに Perl も必要ですが、これは最近のほとんどの Linux ディストリビューションに含まれています。
ハードウェア
円滑な X Window System の表示と Linux の実行には、十分な RAM 容量と CPU 処理能力を備えたハードウェアが必要です。コンピューターへの物理アクセスとしてはキーボードを直接コンピューターに接続します。シン・クライアント、VNC (Virtual Network Computing) 接続、仮想クライアントは理論上動作しますが、処理とネットワーク・オーバーヘッドのさまざまなレイヤーによって予期しない遅延が発生し、システムの振る舞いに影響を与える可能性があります。
xev の変更とコンパイル
この記事ではキーストローク・ダイナミックの使い方を説明する一般的方法として、すべてのイベントを xev で取得し、パスワードの作成と確認用にカスタマイズした Perl プログラムに送ります。
xev をはじめからインストールするには、まず xorg-x11-6.8.1.tar.gz をダウンロードして解凍し、それから xc/programs/xev ディレクトリーに移って xmkmf と入力します。xmkmf プログラムが Imakefile ファイルを使用して makefile を生成するので、make と入力して xev プログラムをビルドします。make コマンドはエラーなしで完了するはずですが、問題がある場合には X.org のドキュメントを調べてください。xev プログラムを正しくビルドできたら、次にキーストローク・イベントをより正確なタイミングで出力するための単純な変更を行います。リスト 1 は、行 164 に fflush(stdout); という文が追加された状態の xev.c ファイルです。
リスト 1. 変更後の xev.c
...
printf (" XFilterEvent returns: %s\n",
XFilterEvent (eventp, e->window) ? "True" : "False");
}
fflush(stdout);
}
static void
do_KeyRelease (XEvent *eventp)
{
...
|
更新したファイルを保存したら、make を再度実行して新しいバージョンの xev をビルドします。xev を現行の作業ディレクトリーにコピーして、パスワード入力プログラムが使用できるようにしてください。xev のセットアップをテストするには、xterm ウィンドウを開いて xwininfo コマンドを入力し、xterm ウィンドウをクリックして出力を調べます。「xwininfo: Window id: 0x32000012 nathan@kinakuta:~」のような行が表示されるはずです。xterm の Window ID を表す 16 進コードを使って ./xev -id 0x32000012 コマンドを実行すると、すべてのイベントが発生と同時に xterm ウィンドウ内に出力されます。さまざまなキーを押したりマウスを動かしてから、Ctrl+C を押して終了してください。
入力時間合計のためのキーボード・イベントの処理
たいていのソフトウェア開発者は頻繁に使う自分のパスワードをほぼ決まった速さで入力します。他人がそのパスワードを入力した場合、入力速度はかなり落ちることが予想されます。以下に記載するコードでは、最初のセクションで単純なパスワード入力を行えるようにしており、確認プログラムでは個人を識別するための要素としてパスワード入力全体のタイミングを取り入れています。通常、自分のパスワードの入力は、キー入力する際の最高速度もしくは一定のリズムで行われるということをある測定結果が示しているからです。リスト 2 に、xevKeyDyn.pl プログラムのメイン・ロジック・ループを記載します。
リスト 2. xevKeyDyn.pl のメイン・プログラム・ロジック
#!/usr/bin/perl -w
# xevKeyDyn.pl keystroke dynamics password demonstrator using xev
use strict;
$|=1; #to ensure timely display of non-printable equivalent strings
die "specify a window id to read xev events from" if( @ARGV != 1 );
my $cmd = "./xev -id $ARGV[0]";
my $maxTimeDiff = 500; # one half second
my $maxKeyDiff = 100; # one tenth second
my $passwordOK = 0;
my @keysOne = (); # first password array of "key event time" items
my @keysTwo = (); # confim password array
my @nonPrintable = (
'Control_','Alt_','Shift_','Up','Left','Right','Down','BackSpace','Print',
'_Lock','Prior','Next','Home','End','Insert','Delete','Pause','Break' );
# main program loop
while( $passwordOK == 0 )
{
readPassword( \@keysOne, "Enter a");
printPassword( \@keysOne );
readPassword( \@keysTwo, "Confirm the");
printPassword( \@keysTwo );
my $catch = <STDIN>; #initial password catch
$catch = <STDIN>; #confirmation password catch
next unless( charactersMatch() );
next unless( totalTimeOK() );
$passwordOK = 1;
}#while passwordOk =0;
|
メイン・プログラム・ロジックはまず、バッファー出力をオフにします。これは、後で印字不能文字を管理するときに使用することになります。次に、X Window ID が指定されていることを確認した後、全パスワードを入力するまでの時間とキー・イベントの入力間隔に対する変数を設定しています。これらの時間の単位はミリ秒なので、入力が特に一定しない人を除けば十分な精度となるはずです。keysOne 変数と keysTwo 変数は入力されたパスワードを保持し、nonPrintable 配列にはサポートされる各種の印字不能文字からなるもっとも重要な部分が含まれます。プログラムは、文字照合と文字の入力合計時間において有効なパスワードが入力されるまでループを続けます。続いてリスト 3 に readPassword サブルーチンを記載します。
リスト 3. readPassword サブルーチン
sub readPassword
{
my $arrRef = $_[0]; # looks less like line noise
@{$arrRef} = (); # clear the hash
shift;
print "@_ password:\n";
open( XEV, " $cmd |" ) or die "can't open xev";
while( my $inLine = <XEV> )
{
if( $inLine =~ /KeyPress event/ || $inLine =~ /KeyRelease event/ )
{
my $keyType = substr($inLine, 0, index($inLine,","));
# get the time entry
my $currTime = <XEV>
$currTime = substr( $currTime, index($currTime,"time ")+5);
$currTime = substr( $currTime, 0, index($currTime,","));
# get the key name
my $currKey = <XEV>
$currKey = substr( $currKey, index($currKey,"keysym ")+7);
$currKey = substr( $currKey, 0, index($currKey,"),"));
$currKey = substr( $currKey, index($currKey, ", ")+2);
# echo the non-printable key names to the screen on key release only
if( $keyType =~ /KeyRelease/ )
{
map { print "<$currKey>" if( $currKey =~ /$_/ ) } @nonPrintable;
}
# continue to read if return pressed and no keys read, exit loop if return pressed
# after keys have been entered
next if( $currKey =~ /Return/ && @{$arrRef} == 0 );
last if( $currKey =~ /Return/ && @{$arrRef} != 0 );
# add the "time key type" to the array
push @{$arrRef}, "$currTime $currKey $keyType";
}#if a key press or release
}#while xev in
close(XEV);
}#readPassword
|
上記で実行した xev のテストで覚えているかもしれませんが、キー押下またはキー・リリースのイベントが発生するたびに、xev プログラムによって 4 行または 5 行の出力がされます。readPassword はキー関連のイベントをリッスンしてから、それぞれのキーの名前 (J、Ctrl+L など) を抽出します。するとキーの名前とそのイベントが発生したタイミング、そしてタイプが後で処理できるように現行のパスワード配列に追加されます。Enter キーが押された時点ですでに 1 文字以上入力されていれば、このサブルーチンは終了します。参照用にパスワードのキーとタイミングを出力するために使用される printPassword サブルーチンについては、リスト 4 のとおりです。
リスト 4. printPassword サブルーチン
sub printPassword
{
my $arrRef = $_[0];
for my $keyStr ( @{$arrRef} )
{
my( $time, $key, $type ) = split " ", $keyStr;
print "Key: $key at $time Type: $type\n";
}
print "Total time: ", getTotalTime( $arrRef );
print "\n\n";
}#printPassword
|
以上のメイン・プログラム・コードと 2 つのサブルーチンによって、xevKeyDyn.pl は 2 つのパスワードを読み取って出力します。2 つのパスワードが文字数と文字の並びの点で一致することを確実にするため、さらにリスト 5 に記載する charactersMatch サブルーチンを追加します。
リスト 5. charactersMatch サブルーチン
sub charactersMatch
{
# the hold a key and press enter check, or you type so fast you press enter
# before releasing your previous key
if( ($#keysOne+1) % 2 != 0 || ($#keysTwo+1) % 2 != 0 )
{
print "Key event missed, please try again.\n";
return(0);
}
if( $#keysOne != $#keysTwo )
{
print "Passwords are not equal length\n";
return(0);
}
my $count = 0;
while( $count < $#keysOne )
{
my( undef, $key1, undef ) = split " ", $keysOne[$count];
my( undef, $key2, undef ) = split " ", $keysTwo[$count];
last if( $key1 ne $key2 );
$count++;
}#while each key entry
return(1) if( $count == $#keysOne );
print "Password keys do not match. \n";
return(0);
}#charactersMatch
|
最初に実行されるチェックは、キー押下とキーのリリース・イベントが所定の入力に対して記録されることを確実にするためのものです。xevKeyDyn.pl プログラムを試していると、特定のキーストロークはオーバーラップしがちであることに気付くかもしれません。それが顕著なのは、両手で入力しなければならない語に含まれるキーストロークです。つまり、左手で a s d f を入力してから右手の小指で Enter キーを押すときに、f キーを離す一瞬前に Enter キーを押してしまうことがよくあります。そのため、この最初のチェックで、意図したすべてのキーストロークに対してキーの押下とリリースが記録されることを確実にするわけです。
2 つのパスワードの長さが同じであることを確かめる単純なチェックの後、それぞれのキーがパスワード全体で照合され、例えば両方のパスワードで 5 番目の文字が同じくゼロになっていることなどが確認されます。必要なすべてのキー押下とキー・リリースから収集された 2 つのパスワードが同じ長さで、それを構成する文字がすべて同じであれば一致という結果となり、サブルーチンが正常に終了します。次に追加するリスト 6 のサブルーチンで、キーストローク・ダイナミックスに関連する最初の基本的なチェック、つまりパスワード入力の合計時間をチェックします。
リスト 6. totalTime および getTotalTime チェックのサブルーチン
sub totalTimeOK
{
return(1) if( abs(getTotalTime(\@keysOne) - getTotalTime(\@keysTwo)) < $maxTimeDiff);
print "Total length 1 is: ", getTotalTime( \@keysOne ), "\n";
print "Total length 2 is: ", getTotalTime( \@keysTwo ), "\n";
print "Passwords are too far apart in total length \n";
return(0);
}#totalTimeOK
sub getTotalTime
{
# get the first and last times, and return the difference
my $arrRef = $_[0];
my ($strTime, undef, undef ) = split " ", ${$arrRef}[0]; # first array item
my ($endTime, undef, undef ) = split " ", ${$arrRef}[$#{$arrRef}]; # last array item
return( $endTime - $strTime );
}#getTotalTime
|
getTotalTime サブルーチンは単純に、最初のキーストロークから最後のキーストロークまでの時間 (ミリ秒単位) を引いて求めます。もう一方の totalTimeOK サブルーチンは、パスワードと確認パスワードの合計入力時間の差が maxTimeDiff の値 (500 ミリ秒) 未満であれば、この 2 つのパスワードを有効と見なします。
ここで、コマンド perl xevKeyDyn.pl 0x32000012 でプログラムを実行します。さまざまなパスワードを入力して、記録されるパスワードの合計入力時間に注目してください。一定のリズムで入力できるようになると、maxTimeDiff 変数を 10 分の 1 秒まで短縮できるはずです。上級ユーザーであれば、それ以上短縮できるかもしれません。
印刷不能文字の要求
ありふれた外観を盾にすることは、セキュリティー機構に難読化を備えさせる上での一般的な戦術です。パスワードを難読化するためのレイヤーをさらに追加する単純な方法としては、バックスペースやポーズなどの印刷不能文字を要求するという手段があります。xev プログラムでイベントを取得することによって、印刷不能文字の要求をシームレスに追加することができます。この機能が有効であれば、友人たちにパスワードの文字を教えたとしても、彼らがそれを使用することはできません。リスト 7 に hasNonPrintable サブルーチンを記載します。このサブルーチンを使って、@nonPrintable 配列内の期待されるプレフィックスのいずれかがパスワードに含まれるかどうかをチェックします。
リスト 7. hasNonPrintable
sub hasNonPrintable
{
for my $keyStr ( @keysOne )
{
my( undef, $key, undef ) = split " ", $keyStr;
map { return(1) if( $key =~ /$_/ ) } @nonPrintable;
}
print "A non-printable character is required.\n";
return(0);
}#hasNonPrintable
|
この hasNonPrintable チェックを有効にするには、リスト 8 のコードをメイン・プログラム・ループに追加します。
リスト 8. メイン・プログラム・ループへの hasNonPrintable 追加
next unless( charactersMatch() );
next unless( totalTimeOK() );
next unless( hasNonPrintable() ); # add at line 35
|
印刷不能文字を受け付け、要求することは、ありふれた外観にパスワードの属性を隠す 1 つの方法です。パスワードがモニターされているとしても、パスワード文字列にはバックスペース、そして文字の有無や並びに明らかに作用する文字が存在するため、通常のアタッカーからの攻撃が阻止されます。
キーの押下時間
難読化対策と合計入力時間に関する対策を補うのは、正確なキーストローク間隔という要件です。この要件では、パスワードの最初の部分は素早く入力する一方、2 番目の部分では数字や大文字入力があるために入力速度が落ちる場合、その一貫性が測定されることになります。さらに、入力のペースが異なるユーザーが文字はゆっくり入力する一方、数字は素早く入力する (おそらくキーバッドを使用するため) という場合には、individualKeyTimingsOK サブルーチンによって入力速度の違いが検出されます。
リスト 9. individualKeyTimingsOK
sub individualKeyTimingsOK
{
my $count = 0;
while( $count < ($#keysOne-1) )
{
my( $time1, undef, undef) = split " ", $keysOne[$count];
my( $time2, undef, undef) = split " ", $keysOne[$count+1];
my $timeDiff_0 = $time2 - $time1;
( $time1, undef, undef) = split " ", $keysTwo[$count];
( $time2, undef, undef) = split " ", $keysTwo[$count+1];
my $timeDiff_1 = $time2 - $time1;
if( abs($timeDiff_0 - $timeDiff_1) > $maxKeyDiff )
{
print "Intra-key timings invalid at position [$count] for " .
"$timeDiff_0 and $timeDiff_1, please try again.\n";
return(0);
}
$count++;
}#for each character
return(1);
}#individualKeyTimingsOK
|
上記のリスト 9 に記載しているのが、individualKeyTimingsOK サブルーチンです。このサンプル・コードでは、キーストロークと、そのすぐ後のキーストロークとの時間間隔だけを測定しています。タイミングについては、パスワード全体でのキーストローク間隔の差が maxKeyDiff しきい値 (100 ミリ秒) 未満であれば一致と見なされます。入力するユーザー固有のタイミングにのみ一致させる必要がある場合は、このしきい値を 10 分の 1 秒より小さくしてください。このサブルーチンの作成者は以前、キーボードのさまざまな位置で片手だけを使って入力できるパスワードを選ぶという習慣がありました。xevKeyDyn.pl プログラムを実行すると、そのようなパスワードを完全に入力する時間は非常に短く、しかも特定のキー押下イベントはそれぞれ 20 ミリ秒以内で発生することがわかります。一定のキーストロークで 1000 分の 1 秒の精度を 20 回繰り返すのは、入力が速いユーザーでも難しく、パスワードの正しい文字がわかっているとしても考えにくいものです。
individualKeyTimingsOK チェックを有効にするには、リスト 10 のコードをメイン・プログラム・ループに追加します。
リスト 10. メイン・プログラム・ループへの individualKeyTimingsOK 追加
next unless( charactersMatch() );
next unless( totalTimeOK() );
next unless( hasNonPrintable() );
next unless( individualKeyTimingsOK() ); # add at line 36
|
印刷不能文字の要件とキー入力間隔それぞれのタイミングのチェックが用意できたら、コマンド perl xevKeyDyn.pl 0x32000012 でプログラムを実行します。文字と印刷不能文字のさまざまな組み合わせを試してみてください。また、キーの入力間隔を変えたり、パスワードの部分ごとに速度を変えて、新しいテキスト入力オプションを実験してください。
実装例
リスト 11 に記載するパスワードの例を見てください。入力された文字は 's-<backspace>-e-c-r-3-t で、ここでは新しい仕掛けとして s キーがパスワードの最後のキーが押されてリリースされるまで押し下げられた状態になっています。この新しい機能は、xev がキーボード・イベントを記録する極めて単純ながらも強力な方法によってシームレスに導入することができます。キーの押下イベントとリリース・イベントを組み合わせるのは人間とコンピューターとの相互作用の一面であり、簡単なゲームやリアルタイムのユーザー・インターフェースを作成したことがある人にはお馴染みの方法です。しかしほとんどの場合、この機能は無視され、パスワードのテキスト・ボックスはキー押下イベントを記録するだけで、キーのリリース・イベントは無視します。その一方で、キーストロークの押下とリリースのタイミングを測定することが、キーストローク・ダイナミックをモニターするように設計されたこのアプリケーションに、より堅牢な識別機能を加えるための重要なステップとなります。
リスト 11. "secr3t" のパスワード例
nathan:$ perl xevKeyDyn.pl 0x32000012
Enter a password:
s<BackSpace>cr3t # note that the 'e' has been erased
Key: s at 9813585 Type: KeyPress # note no immediate release for 's' key
Key: e at 9813832 Type: KeyPress
Key: e at 9813951 Type: KeyRelease
Key: BackSpace at 9814160 Type: KeyPress
Key: BackSpace at 9814264 Type: KeyRelease
Key: c at 9814483 Type: KeyPress
Key: c at 9814586 Type: KeyRelease
Key: r at 9814809 Type: KeyPress
Key: r at 9814880 Type: KeyRelease
Key: 3 at 9815078 Type: KeyPress
Key: 3 at 9815221 Type: KeyRelease
Key: t at 9815356 Type: KeyPress
Key: t at 9815451 Type: KeyRelease
Key: s at 9815649 Type: KeyRelease # 's' key release
|
ユーザーの多くは、パスワード入力の所要時間に対する要件が認証プロセスに追加されても苦にはならないという感想を持っています。したがってその気があれば、これらの制御アルゴリズムを現行のパスワード入力コードに適用するだけで、簡単に認証スキームのセキュリティーを強化できるということです。
リモート・アクセスと実装に関する注意事項
サーバーがホストする X セッションを実行する Ajax ログイン・ページやシン・クライアントなどのリモート認証スキームには、特別な配慮が必要です。なぜならネットワーク遅延とマシンの処理時間は多種多様な影響を受けるため、キー・イベント・タイミングの精度は入力セッションごと、さらには個々のキーストロークごとに大幅に変わってくるからです。予期しないマシン状態 (ネットワーク負荷の増加、ディスク・ドライブの障害によるリソースの過剰消費など) になったときに、キーストローク測定システムの実装が認証の妨げとならないように注意してください。
さらに注意しなければならない点として、キーストローク・ダイナミックスを実装するには、ユーザーの特性とキーストローク精度が必要となります。この記事で説明した手法を実装する場合には、リリース時間と最初のキー押下時間が正確でなければなりません。ここに記載したコードを変更してキー押下イベントだけをサポートするようにもできますが、他のキーが押されている間にキーの押下と押し下げた状態に関する情報が失われることになります。
まとめとその他の例
この記事で説明した手法を使えば、既存の Perl アプリケーションの認証オプションを強化することも、あるいはこれらの概念に基づいてあらゆるプログラムを変更することも可能です。さらにキーストローク・ダイナミックスは、連続認証にバイオメトリクスを適用することができる数少ない分野の 1 つでもあります。ここで紹介した手法を使ってアプリケーションを変更し、よくある入力ミスやほとんど同時に押されたキー、そしてその他にもアプリケーションのユーザーに固有の入力パターンを検出して追跡することを検討してみてください。また Web 認証アプリケーションで、CAPTCHA への攻撃対策としてキーストロークの押下とリリースのタイミング測定を利用して CAPTCHA 技術を高度なものにしてみてください。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Sample code | os-keystrokeDynamics_0.1.zip | 2KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
- xev を入手するのにもっとも手軽な方法の 1 つは、完全な full X11 ソース・コードをダウンロードすることです。
- Perl.org から Perl のソースを入手してください。
-
IBM ソフトウェアの試用版を使用して、次のオープンソース開発プロジェクトを革新してください。ダウンロード、あるいは DVD で入手できます。
-
IBM 製品の評価版をダウンロードして、DB2®、Lotus®、Rational®、Tivoli®、および WebSphere® のアプリケーション開発ツールとミドルウェア製品を使ってみてください。
議論するために
著者について  | 
|  | Nathan Harrington は IBM のプログラマーで、現在は Linux とリソース探索技術に取り組んでいます。 |
記事の評価
|  |