Term::Sample - Finger printing of your keyboard typing
Term::Sample - Finger printing of your keyboard typing
use Term::Sample qw(sample average analyze intr); use strict; my $set = Term::Sample::Set->new(); my $sample_string = 'green eggs and ham'; if(!$set->load("test3.set")) { my @samples; print "Person: Person #1\n"; my $top = 3; for (0..$top) { print "[ Sample $_ of $top ] Please type \"$sample_string\": "; $samples[$_] = sample(); } $set->store( 'Person #1' => average(@samples) ); print "Person: Person #2\n"; my $top = 3; for (0..$top) { print "[ Sample $_ of $top ] Please type \"$sample_string\": "; # This has the same effect as saving all the samples in an array # then calling store on the average() output, as shown above. $set->store( 'Person #2' => sample() ); } $set->save("test3.set"); } print "Now to test it out...\n"; print "[ Anybody ] Please type \"$sample_string\": "; my $sample = sample(); my ($key, $diff) = $set->match($sample); print "I am sure (about ", intr(100-$diff), "% sure) that your signiture matched the key `$key'.\n";
Term::Sample implements simple typing analysis to find the ``personality'' in your typing. It uses Timer::HiRes and Win32::Console for best results. If it is not run on a Win32 system, it defaults to Term::ReadKey instead of Win32::Console. I'm not sure how well it works with ReadKey, as I have not had a chance to test it out yet.
In this module we deal with three basic items: samples, analysis', and sets. Samples are what
you get from the sample()
function and are raw keyboard data. Samples can be averaged
together to produce master samples, or analyzed to produce unique sample analysis'. Analysis'
are produced by alanlyze()-ing samples from sample()
or samples averaged together(). You
can store samples (averaged or analyzed) and analysis' in sets according to unique,
user-defined keys. You can then match new samples against the samples in the set and find
out which key it matched in the set, as well as the percentage of error.
This module uses Timer::HiRes to time both the key-press time (time between the key-down signal
and the key-up signal) and the key-interveal (time between key-up of previous key and key-down
of next key). This creates what I call a keyboard sample, or just a ``sample.'' This is created
by a custom prompt function, sample()
which returns an array ref. This is the raw keyboard
sample data. It can be averaged together with multiple sample to create a master sample
to be used as a signiture, or it can be individually saved with save(). Aditionally, you can
get a dump of the raw sample data with print_data($sample, type => 'basic') or
print_data($sample, type => 'average').
This creates a unique 'print', or analysis from a sample, or samples averaged together with
analyze(). analyze()
uses several factors to make the unique analysis. First, it calculates
average ASCII key codes, as well as the average total key-press and inter-key times. Then
it loops through the sample and picks out the fastest key-press times and inter-key times,
and taking a three-key average around that high-point to create a sample highlight. It creats
highlights from every key in the sample, fastest to slowest, and then sorts the hightlights by
key-press times and inter-key times, storing both lists in a final ``analysis'' object, along
with the averaged times created at the start. This gives a final, hopefully unique, sample
analysis.
Once you have gotten some master samples (I usually consider a master sample to be a single averaged sample of three to five samples of the same string, averaged with average(). see SYNOPSIS), you can store them in a Set. Included is a handy module for just that purpose.
Term::Sample::Set provides a way of managing master samples and matching test samples against
the master samples. After creating a new Term::Sample::Set object, you simply add samples
to it, stored by key, with the $set->store(key => $sample) method. You can then gather
additional unique samples to match against the samples contained in the set by calling
match($sample). match()
returns a two-element list with the first element being the key that
it matched. The keys are provided by the user when calling store(). The second element is
the ammount of differenece between the sample passed to match()
and the sample that is stored
at $key. Therefore you can get the percenentage of confidence in the match with intr(100-$diff).
(intr() is an optional export of Term::Sample). Additionally, sets can be saved and loaded with
save()
and load(). It stores data in a simple flat text file, so the data should be fairly
portable.
Try saving the SYNOPSIS above in a file and running it (you can find a copy of it in the 'examples' directory in this distribution, 'synopsis.pl'). It should run fine right out of the POD (pun intended :-) as-is. Get another person to type for Person #1, and you type for Person #2. Then either of you type at the ``Test it out'' prompt and see who it matches against. It will display the percentage of confidence in the match. It automatically stores the initial three samples from each person in a single set, so you can run the script again without having to re-type the initial samples.
Term::Sample can be used with a blessed refrence from the new()
constructor or via
exported functions. No functions are exported by default, but below are the OK ones
to import to your script.
sample average save load analyze diff print_data to_string p intr round plus new_Set new_set
A simple "use Term::Sample qw(sample average load save analyze diff print_data p intr round plus new_Set)" will get you all the functions in to your script.
new()
echo => $echo_type newline => $newline_flag
$echo_type can be one of three values: 'key', 'none', or any character to echo. If the echo tag is not included, it defaults to 'key'. A 'key' value echos every key typed to STDIO with a simple print call. A 'none' value does just that: It doesn't print anything. Any other character passed in the echo tag is echoed in place of every character typed. Good for using '*' in place of characters, that sort of thing.
$newline_flag is 1 by default, unless specified otherwise. If $newline_flag is 1, it prints a newline character (\n) to STDOUT after finishing with the sample, otherwise printing nothing.
sample()
returns an array ref to be used in other functions in this module.
save()
saves a sample or analysis to disk under $file name. It uses a flat file format and
the respective type (sample or analysis) will be restored by load().
This simply creates a unique analysis from a sample, or samples averaged together with
analyze(). analyze()
uses several factors to make the unique analysis. First, it calculates
average ASCII key codes, as well as the average total key-press and inter-key times. Then
it loops through the sample and picks out the fastest key-press times and inter-key times,
and taking a three-key average around that high-point to create a sample highlight. It creats
highlights from every key in the sample, fastest to slowest, and then sorts the hightlights by
key-press times and inter-key times, storing both lists in a final ``analysis'' object, along
with the averaged times created at the start. This gives a final, hopefully unique, sample
analysis.
This returns a hash refrence to an analysis data structure.
This compares the samples or analysis' and returns the percentage of difference between the two samples as an integer 0 and 100.
$v is an optional parameter to turn on verbose difference summary. If $v is not included, it defaults to 0, turing verbose off. If $v is 1, it includes a brief summary as it calculates. If $v is 2 it includes full verbose output.
This prints a summary or the raw data of the sample, depending on $type. If $type = 'average', it prints the average summary for the sample. If $type = 'basic', it prints out the complete, raw sample data.
new()
method of Term::Sample::Set, below.
I included one with set capitalized and one not. I think the capitalized Set would be more propoer, as that is the package name, but I am sure nobody will remember to always capitalize Set, so I made an alias for both. Aren't I nice? :-)
new(tags)
type => $type silent => $silent_flag
Creates and returns a blessed refrence to a Term::Sample::Set object. $type is optional. If $type is included, it is expected to be either 'sample' or 'analysis'. If $type is not included it defaults to 'sample.' $type tells the object what data it is expected to store in the set, wether raw sample data or analysis data from analyze().
If $silent is not specified, it defaults to 0. If $silent_flag is true, then all the methods of the set object will NOT return any errors. If it is 0 (default) then it will always print errors.
$set->store( 'Josiah's Sample' => $josiah, 'Larry's Sample' => $larry, 'Joe's Sample' => $joe );
store() expects the key values ($josiah, $larry, and $joe) to be an array ref as returned by sample() or an average() of samples, UNLESS the Set object was concstucted with the 'analysis' parameter. In that case, it expeccts the key values to be a hash refrence as returned by analyze().
Additionally, if your attempt to store()
to a key that already exists, then store()
will
average the data you are trying to store with the data already in the Set, storing the final
average data back in the set at the same key.
Returns undef on errors, otherwise returns $set.
Returns data stored at $key. Returns undef on errors, otherwise returns data stored at key.
match()
expects $data to be an array ref as returned by sample()
or an average()
of samples,
UNLESS the Set object was concstucted with the 'analysis' parameter. In that case, it expeccts
the key values to be a hash refrence as returned by analyze().
match()
returns a two-element list. The first element is the key that $data matched with
the least ammount of error. The second element is the percentage difference between $data
and the data in the key matched.
$flag is an optional paramater. If $flag is true, it will print out the percentage differences according to their keys to STDOUT. $flag defaults to false.
Returns undef on errors, otherwise returns $set.
See SYNOPSIS for example usage.
load($file)
This example helps you to create a master sample file, as for the sample password checking example below. It prompts you for the file to store the sample in, and the number of samples to take. I have found that the samples match better with longer strongs. I.e. instead of a password of "sue goo", try "blue sue ate the green goo". It also is a good idea to get around 5 - 10 samples. This allows it to average a good sampling of your typing together to create one master sample. Be sure to use the same string for each sample.
# File : examples/sample.pl # Author : Josiah Bryan, jdb@wcoil.com, 2000/9/16 use Term::Sample qw(sample average load save print_data); print "\nSample Creation Script\n\n"; print "Please enter a file to save the final sample in: "; chomp(my $file = <>); print "Number of samples to take: "; chomp(my $num = <>); my @samples; for my $x (1..$num) { print "[$x of $num] Enter sample string: "; $samples[++$#samples] = sample(); } print "Combining and saving samples..."; save(average(@samples), $file); print "Done!\n"; __END__
Here is a simple password checker. It assumes you have used the above sample maker to make a password file called ``password.sample'' with the correct password in it. This will ask the user for the password, with only an astrisk (*) as echo. It will first compare the text the user types to see if the password match. If they do, then it analyzes the input and the password sample and gets the difference between the two. It then converts the difference to a confidence percentage (100-diff), and displays the result.
# File : examples/passwd.pl # Author : Josiah Bryan, jdb@wcoil.com, 2000/9/16 use Term::Sample qw(sample analyze load intr to_string diff plus); my $password = load("password.sample"); print "Enter password: "; my $input = sample( echo => '*' ); my $diff; if(to_string($input) ne to_string($password)) { print "Error: Passwords don't match. Penalty of 100%\n"; $diff = 100; } $diff = intr(100 - (diff(analyze($input), analyze($password))+$diff)); print "I am $diff% sure you are ",(($diff>50)?"real.":"a fake!"),"\n"; __END__
This is a simple set builder. It modifies the sample creation script to prompt you for a key name and a Set file name. Then it goes thru the sample sampling process as before. Only instead of averaging and storing in a file, it averages and stores in a set, then saves the set to disk.
# File : examples/set.pl # Author : Josiah Bryan, jdb@wcoil.com, 2000/9/16 use Term::Sample qw(sample average print_data new_Set); print "\nSet Creation Script\n\n"; print "Please enter a file to save the final sample in: "; chomp(my $file = <>); print "Please enter a key for this sample in the set: "; chomp(my $key = <>); print "Number of samples to take: "; chomp(my $num = <>); my @samples; for my $x (1..$num) { print "[$x of $num] Enter sample string: "; $samples[++$#samples] = sample(); } print "Combining and saving samples..."; # Since most of the set methods return the blessed object, # (except match()) you can chain methods together new_Set(silent=>1) ->load($file) ->store($key => average(@samples)) ->save($file); print "Done!\n"; __END__
The same password example as the password script above. The difference is that this one asks for a username and draws the password from a Set file. If a key by that username doesnt exist in the Set file, it prints an error and exists. It then checks the validity and analysis' of the two samples, and prints the results.
# File : examples/spasswd.pl # Author : Josiah Bryan, jdb@wcoil.com, 2000/9/16 use Term::Sample qw(sample analyze intr to_string diff plus new_Set); my $set = new_Set(silent=>1); $set->load("password.set"); print "Enter username: "; chomp(my $key = <>); my $password = $set->get($key); if(!$password) { print "Error: No user by name `$key' in database. Exiting.\n"; exit -1; } print "Enter password: "; my $input = sample( echo => '*' ); print "got:",to_string($input)," needed:",to_string($password),"\n"; my $diff; if(to_string($input) ne to_string($password)) { print "Error: Passwords don't match. Penalty of 100%\n"; $diff = 100; } $diff = intr(100 - (diff(analyze($input), analyze($password))+$diff)); print "I am $diff% sure you are ",(($diff>50)?"real.":"a fake!"),"\n"; __END__
I have not tested this on a non-Windows system. I am not sure how well this will work,
as I did not see anything in Term::ReadKey docs about a facility for detecting
key-down and key-up. From what I see, it just returns the key on key-up. I have written
around this in the sample()
function. Therefore if it detects a non-Win32 system, it will
NOT measure the key-press times, only the inter-key delay times.
If someone knows of a way to do detect key up and key down with a more portable solution other than Win32::Console, PLEASE email me (jdb@wcoil.com) and let me know. Thankyou very much.
I make no claims to the accuracy or reliablility of this module. I simply started to write it as a fun experiment after creating Term::Getch the other day. It seems to work with some measure of accuracy with the testing I have done with several people here. I would greatly appreciate it if any of you that use it would email me and let me know how well it works for you. (jdb@wcoil.com) Thankyou very much!
sample()
function seems to have problems with fast typers (like me) who like to hold
down one key and not release the first key before pressing the second. This seems to confuse
it with the key-up and key-down signals. I might be able to fix that with some kind of
internal hash-table lookup or something, but for now I'll leave it be. I'll try to have
it fixed by the next version. If anyone fixes it by themselves, or gets part of it fixed, please
let me know so I don't reinvent any wheels that I don't really need to.
Term::Sample
, and that holding true, I am sure
there are probably bugs in here which I just have not found yet. If you find bugs in this module, I would
appreciate it greatly if you could report them to me at <jdb@wcoil.com>,
or, even better, try to patch them yourself and figure out why the bug is being buggy, and
send me the patched code, again at <jdb@wcoil.com>.
Josiah Bryan <jdb@wcoil.com>
Copyright (c) 2000 Josiah Bryan. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
The Term::Sample
and related modules are free software. THEY COME WITHOUT WARRANTY OF ANY KIND.
You can always download the latest copy of Term::Sample from http://www.josiah.countystart.com/modules/get.pl?term-sample:pod