This is an archived cached-text copy of the developerWorks article. Please consider viewing the original article at: IBM developerWorks



IBM®
Skip to main content
    Country/region [select]      Terms of use
 
 
    
     Home      Products      Services & industry solutions      Support & downloads      My IBM     
skip to main content

developerWorks  >  Open source  >

Expand your text entry options with keystroke dynamics

Use xev, Perl, and custom algorithms to measure characters and how they are typed

developerWorks
Document options

Document options requiring JavaScript are not displayed

Sample code


Rate this page

Help us improve this content


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.



Back to top


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.



Back to top


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.



Back to top


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.



Back to top


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.



Back to top


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.



Back to top


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.



Back to top


Conclusion and further examples

Share this...

digg Digg this story
del.icio.us Post to del.icio.us
Slashdot Slashdot it!

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.




Back to top


Download

DescriptionNameSizeDownload method
Sample codeos-keystrokeDynamics_0.1.zip2KBHTTP
Information about download methods


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

Nathan Harrington is a programmer at IBM currently working with Linux and resource-locating technologies.




Rate this page


Please take a moment to complete this form to help us better serve you.



YesNoDon't know
 


 


12345
Not
useful
Extremely
useful
 


Back to top



    About IBM Privacy Contact