| Level: Intermediate Nathan Harrington (harrington.nathan@gmail.com), Programmer, IBM
04 Dec 2007 Measure the total time of entry and verify the time between keystrokes to help
authenticate a user regardless of the data being entered. Require nonprintable
characters, such as backspace and break, in the password to enable new levels of password
obfuscation. Learn how to apply the open source tools xev and Perl in keystroke
dynamics to measure the more-subtle characteristics of human-computer interaction.
The whorls and ridges on your fingertips can identify you based on what you touch. How
you touch and especially type can often be just as unique an activity. Keystroke
dynamics is a relatively new field that enables identification of individuals through
statistical analysis of their typing patterns. Many commercial products analyze the
dynamics of password entry, as well as continuous typing monitoring for enhanced
security. This article uses example code to demonstrate keystroke dynamics for enhancing the
security of your applications in authentication and continuous data entry contexts.
Requirements
Software
Linux® is required with a functioning X Window System setup, such as Gnome or
KDE. Any distribution equipped with a graphical desktop released after 1999 should
provide an already-configured environment with which to get started. Effective capture
and processing of X Window events is a task handled best by a program like xev — the X event viewer. Check Resources to download xev. You
need to download the source code for xev even if it is already installed or available
as a package for your distribution. And you need Perl, which is all but ubiquitous in Linux distributions today.
Hardware
You need hardware with sufficient RAM and CPU horsepower to display X Window System and
run Linux smoothly. We need physical access to a computer with a keyboard hooked
directly to it. Thin clients, Virtual Network Computing (VNC) connections, and virtualized clients will work in
theory, but the various layers of processing and network overhead may cause
unpredictable latency, which will affect the system's behavior.
Modifying and compiling xev
The general approach used in this article to demonstrate the use of keystroke
dynamics is to capture all events with xev and pipe them into a Perl program custom-made
for password creation and confirmation.
To install xev from scratch, download xorg-x11-6.8.1.tar.gz and unpack it. Change to the directory
xc/programs/xev and type xmkmf . The Imakefile file is used by
the xmkmf program to generate a makefile. Build the xev program by typing make . The make commands should complete without error, but if you
have a problem, please consult the X.org documentation. If you can build the xev
program correctly, you'll need to make one simple change to print out the keystroke
events with more precise timings. Listing 1 shows the xev.c file with the fflush(stdout); statement added on line 164.
Listing 1. Modified xev.c
...
printf (" XFilterEvent returns: %s\n",
XFilterEvent (eventp, e->window) ? "True" : "False");
}
fflush(stdout);
}
static void
do_KeyRelease (XEvent *eventp)
{
...
|
Save the updated file and rerun make to build the new
version of xev. Copy xev to your current working directory for use by the password-entry
program. To test your xev setup, open an xterm window and type the command xwininfo . Click on the current xterm window and look at the output.
You should see a line similar to "xwininfo: Window id: 0x32000012
nathan@kinakuta:~." Grab the hex code signifying the Window ID of your xterm and
run the command ./xev -id 0x32000012 . All events within the
xterm window will now be printed as they occur. Try pressing different keys or moving
the mouse, then press Ctrl+C to exit.
Processing keyboard events for total time of entry
Many software developers enter their most commonly used passwords at nearly their
regular typing speeds. If someone else is entering your password, though, chances are
good they will be considerably slower. The first section of code below
shows how to set up a simple password entry and confirmation program that takes into
account the full timing of password entry. Other measurements show that your password
entry is usually done as fast as you can type or with a steady rhythm. Listing 2
introduces the xevKeyDyn.pl program through its main logic loop.
Listing 2. main xevKeyDyn.pl program logic
#!/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;
|
The main program logic first turns buffered output off. This will be used later during
the nonprintable character management. After checking to make sure an X Window ID has
been specified, some variables are set up for times between the total password entry
and the between key event entry. These times are in milliseconds, which should provide
enough precision for all but the most erratic typists. The keysOne and keysTwo
variables hold the passwords as entered, and the nonPrintable array contains the most significant portions of the
various nonprintable characters supported. The program will loop continuously until a
valid password in character matching and total time of entry is entered. Listing 3
shows the readPassword subroutine.
Listing 3. readPassword subroutine
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
|
As you may recall from the xev testing performed above, an individual key press or
key-release event causes four or five lines to be printed by the xev program. readPassword listens for a key related event, then extracts the
individual key name (like J or Ctrl+L). The key name, its time, and type
are then added to the current password array for later processing. When the
Enter key is pressed, the subroutine exits if at least one character has been
entered. Listing 4 shows the printPassword subroutine, which
is used to print out the password keys and timings for reference.
Listing 4. printPassword subroutine
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
|
With the main program code and two subroutines listed above, xevKeyDyn.pl can read and
print out two passwords. To ensure that the passwords match in characters and character
order, we'll add the charactersMatch subroutine, as shown in
Listing 5.
Listing 5. charactersMatch subroutine
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
|
The first check performed ensures that a key press and key-release event is recorded
for a given entry. As you experiment with the xevKeyDyn.pl program, you may notice that
certain keystrokes tend to overlap, especially those that exist within a two-hand
required word. That is, as your left hand types a s d f, and your right pinkie
flies in to crush the Enter key, you'll often depress Enter
moments before releasing the f key. This first check ensures that a press and
release is recorded for every key stroke you intended.
After a simple check to ensure the two passwords are of equal length, each key is then
matched in the entire password — ensuring that the fifth character is a
zero in both passwords, for example. If the passwords collected all of the required key
press and key releases, are the same length, and contain all the same characters, a
match was found and the subroutine exits successfully. Listing 6 adds subroutines for
the first basic keystroke dynamics-related check: total time of password entry.
Listing 6. totalTime
and getTotalTime check subroutine
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
|
Subroutine getTotalTime simply subtracts the time of the
last keystroke (in milliseconds) from the time of the first keystroke. The totalTimeOK subroutine considers a password and its confirmation
valid if their total time discrepancy is less than maxTimeDiff (500 milliseconds).
Run the program with the command perl xevKeyDyn.pl
0x32000012 . Try typing your various passwords here and notice how the overall
time to enter them is recorded. Develop a steady rhythm and you can reduce the maxTimeDiff variable to one-tenth of a second or less for advanced users.
Requiring nonprintables
Hiding in plain sight is a common tactic for adding obfuscation to a security scheme.
Requiring nonprintable characters, such as backspace or pause, is a simple way to add
another layer of obfuscation to your password. Using the xev program as the event
grabber allows for a seamless addition of the requirement for nonprintables. With this
function enabled, you can tell your friends the characters in your password and they
still won't be able to use it. Listing 7 shows the hasNonPrintable subroutine used to check a password for one of the
expected prefixes in the @nonPrintable array.
Listing 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
|
Enable the hasNonPrintable check by adding the line shown in
Listing 8 to the main program loop.
Listing 8. hasNonPrintable addition to main program loop
next unless( charactersMatch() );
next unless( totalTimeOK() );
next unless( hasNonPrintable() ); # add at line 35
|
Catching and requiring nonprintables is one way to hide password attributes in plain
sight. Even if monitored, a casual attacker will be stymied by the presence of
backspace and other characters that apparently alter the presence or order of characters in the password string.
Key-hold times
A complement to the obfuscation and total timing measurements is a precision between
keystrokes requirement. If you type the first part of your password rapidly, but the
second part slowly due to numbers or case shifts, the consistency will be measured.
Moreover, if a typist with different pacing enters the letters slowly, but rapidly
enters the numbers (perhaps due to the use of a keypad), the individualKeyTimingsOK subroutine will detect these differences.
Listing 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
|
Listing 9 shows the individualKeyTimingsOK subroutine. For
this example, only the times between a keystroke and the keystroke immediately
following it are measured. Timings are considered a match if the differences between
the keystrokes across passwords are less than the maxKeyDiff
threshold (100 milliseconds). Lower the threshold to a value less than one-tenth of a
second to require certain matches specific to a typist. The author used to be in the
habit of choosing passwords that could be typed with one hand at different positions on
the keyboard. Running the xevKeyDyn.pl program shows that these passwords have a very
short overall time of entry, as well as certain key-press events within 20 milliseconds
of each other. Repeatable precision of 20 one-thousandths of a second for specific
keystrokes is difficult for even the fastest typists to mimic, much less guess even
when the correct password characters are known.
Enable the individualKeyTimingsOK check by adding the line
shown in Listing 10 to the main program loop.
Listing 10. individualKeyTimingsOK addition to main program loop
next unless( charactersMatch() );
next unless( totalTimeOK() );
next unless( hasNonPrintable() );
next unless( individualKeyTimingsOK() ); # add at line 36
|
With the nonprintable requirement and individual timings between keys check in place,
run the program with perl xevKeyDyn.pl 0x32000012 . Try
various combinations of characters and nonprintables. Vary your speed between keys or
across sections of your password to experiment with your new options for text entry.
Example implementations
Consider the example password shown in Listing 11. The characters entered were 's-<backspace>-e-c-r-3-t with a new twist: The s key was
held down until after the last key in the password was pressed and released. The
simplistic yet powerful method that xev records keyboard events enables a seamless
introduction of this new feature. Overlapping key-press/release events is a facet of
human computer interaction that is familiar to anyone who has constructed simple games
or real-time user interfaces. Most often this feature is ignored, and password text
boxes simply record key-press events and ignore key-release events. Measuring the
press and release times of keystrokes is an important step to adding more robust
identification features in this application designed to monitor keystroke dynamics.
Listing 11. Example password for "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
|
Many users find the requirement for a specific duration of password to be a
nonburdensome addition to their authentication process. With the appropriate
persuasion, increasing the security of your authentication schemes can be as easy as
applying these control algorithms to your current password entry code.
Remote access and implementation notes
Remote authentication schemes, such as Ajax login pages or thin clients running
server-hosted X sessions, require special consideration. Key event-timing accuracy can
vary greatly between entry sessions and even individual keystrokes as network latencies
and machine processing times are subject to a wide variety of influences. Take care
that the implementation of your keystroke-measuring systems do not become an
authentication barrier during unpredicted machine states, such as high network load or failed disk drives consuming extra resources.
Also note that implementing keystroke dynamics requires the user to have character and
keystroke accuracy. If the techniques described in this article are implemented, the
release time and the initial key-press time have to be accurate. You can modify the
code presented here to support only key-press events. However, the loss of information
for key press and hold while other keys are pressed will occur.
Conclusion and further examples
You can use the techniques described in this article to enhance the authentication options of your existing Perl applications or modify any program based on these concepts. In addition, keystroke dynamics is one of the few fields where biometrics can be used for continuous authentication. Consider modifying your applications to use some of the techniques described in this article to detect and track common typographical errors, keys pressed closely together and other typing patterns that are unique to your application's users. Try advancing the captcha arms race through measured keystroke press and release times for your web authentication applications.
Download Description | Name | Size | Download method |
---|
Sample code | os-keystrokeDynamics_0.1.zip | 2KB | HTTP |
---|
Resources Learn
Get products and technologies
-
One of the easiest ways to get xev is through a
full X11 source code download.
-
Grab Perl from the source at Perl.org.
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
-
Download IBM product evaluation versions, and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
Discuss
About the author | | | Nathan Harrington is a programmer at IBM currently working with Linux and resource-locating technologies. |
Rate this page
| |