#!/usr/bin/perl -T -w use strict; use warnings; use Digest::HMAC_SHA1 qw/ hmac_sha1_hex /; ## Set up usernames to secrets. Minimum length, 16 chars. my %users = ( "username" => "ABCDEFGHIJKLMNOP", ); ## Catch Ctrl+C and Ctrl+Z... $SIG{'INT'} = sub { fail(); }; $SIG{'TSTP'} = sub { fail(); }; sub decodeBase32 { my ($val) = @_; # turn into binary characters $val =~ tr|A-Z2-7|\0-\37|; # unpack into binary $val = unpack('B*', $val); # cut off the 000 prefix $val =~ s/000(.....)/$1/g; # trim off some characters if not 8 character aligned my $len = length($val); $val = substr($val, 0, $len & ~7) if $len & 7; # pack back up $val = pack('B*', $val); return $val; } sub totp_token { my $secret = shift; my $key = unpack("H*", decodeBase32($secret)); my $lpad_time = sprintf("%016x", int(time()/30)); my $hmac = hmac_sha1_hex_string($lpad_time, $key); my $offset = sprintf("%d", hex(substr($hmac, -1))); my $part1 = 0 + sprintf("%d", hex(substr($hmac, $offset*2, 8))); my $part2 = 0 + sprintf("%d", hex("7fffffff")); my $token = substr("".($part1 & $part2), -6); return $token; } sub hmac_sha1_hex_string { my ($data, $key) = map pack('H*', $_), @_; hmac_sha1_hex($data, $key); } sub fail { kill( -1, 0 ); exit 1; } my $username = $ENV{"LOGNAME"}; if ( !defined $users{$username} ) { print "No token configured. Bye.\n"; fail(); } print "Enter Code: "; my $input_code = ; chomp($input_code); my $calculated_code = totp_token($users{$username}); if ( $calculated_code ne $input_code ) { fail(); } ## Auth passed. exit 0;