NAME
Term::Sample - Finger printing of your keyboard typing
SYNOPSIS
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";
DESCRIPTION
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.
EXPORTS
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.
METHODS for Term::Sample
- new()
-
Package Constructor. Takes no arguments and returns a blessed refrence to an object which contains all the methods that are optionally exported. Below is a description of those methods in the order that they appear in the list above.
- sample( option_name => options_value )
-
This produces a raw keyboard sample using Win32::Console and Timer::HiRes.
Options Tags: 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.
- average(@samples)
- average(@analysis);
- average($sample1, $sample2, ... $sampleN);
- average($analysis1, $analysis2, ... $analysisN);
-
This averages together samples with samples or analysis' with anlysis' and returns a single, averaged sample or analysis, whichever was passed in to it. They can be passed via an array (not array ref), or via a variable-length argument list.
- save($sample, $file);
- save($analysis, $file);
-
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().
- load($file);
-
Loads a sample or analysis from file $file. Returns a refrence to the respective type of the file, containing the data in the file.
- analyze($sample);
-
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.
- diff($sample1, $sample2 [, $v]);
- diff($analysis2, $analysis2 [, $v]);
-
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.
- print_data($sample, type => $type);
-
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.
- print_data($analysis, type => $type);
-
This prints a overview or the complete highlights of the $analysis, depending on $type. If $type = 'overview', it will print out the averages for the analysis, as well as the first two highlights for key-press and inter-key times. If $type = 'analysis' or $type = 'details', it prints the complete hightlights list for both key-press and inter-ley times, as well as the averages for the analysis.
- to_string($sample);
-
This extracts the characters typed from the raw timing data in $sample and returns it as a scalar string.
- p($a,$b);
-
Returns the difference of $a-$b as a percentage of $a.
- intr($float);
-
Rounds a float to an integer and returns the integer.
- round($float, $places);
-
Rounds a floating point number to $places after the decimal and returns the float.
- plus($neg);
-
Makes a negative number positive. No effect on positive numbers. Returns the positive number.
-
This is for those of us that are lazy and don't wan't to type ``$set = Term::Sample::Set->new(tags)'' It is simply an alias for the 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? :-)
METHODS for Term::Sample::Set
-
Optional 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( %keys )
-
Stores a hash of data in the set. Example:
$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.
- $set->remove($key);
-
Removes the key $key from the set. Returns undef on errors, otherwise returns $set.
Returns data stored at $key. Returns undef on errors, otherwise returns data stored at key.
- $set->match($data [, $flag]);
-
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.
- $set->save($file);
-
Save the entire data set, keys and all, in file $file. Flat file format is used. Returns undef on errors, otherwise returns $set.
- $set->load($file)
-
Loads keys from file $file into the dataset. Note: It over writes any keys existing in the dataset if there is a conflicting key found in the file. Returns undef on errors, otherwise returns $set.
EXAMPLES
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__
NOTE
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.
SMALL DISCLAIMER
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!
BUGS
- Speed
-
The 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.
- Other
-
This is a beta release of
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>.
AUTHOR
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.
DOWNLOAD
You can always download the latest copy of Term::Sample from http://www.josiah.countystart.com/modules/get.pl?term-sample:pod
7 POD Errors
The following errors were encountered while parsing the POD:
- Around line 898:
'=item' outside of any '=over'
- Around line 1028:
You forgot a '=back' before '=head1'
- Around line 1030:
'=item' outside of any '=over'
- Around line 1072:
Unknown directive: =iutem
- Around line 1110:
You forgot a '=back' before '=head1'
- Around line 1273:
'=item' outside of any '=over'
- Around line 1292:
You forgot a '=back' before '=head1'