File Coverage

blib/lib/Auth/GoogleAuthenticator.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package Auth::GoogleAuthenticator;
2 3     3   69386 use strict;
  3         6  
  3         105  
3 3     3   173916 use Authen::OATH;
  0            
  0            
4             use Convert::Base32;
5             use Math::Random::MT 'rand'; # to generate good passcodes
6             use URI::Escape;
7              
8             use vars qw($VERSION);
9             $VERSION= '0.03';
10              
11             sub new {
12             my ($class, %args) = @_;
13             if( $args{ secret_base32 }) {
14             $args{ secret } = decode_base32( delete $args{ secret_base32 });
15             };
16            
17             $args{ auth } ||= Authen::OATH->new();
18             bless \%args => $class;
19             }
20              
21             sub auth { $_[0]->{auth} };
22              
23             sub registration_qr_code {
24             my ($self, $label, $type) = @_;
25             # if we have an OTP, dislay the QRCode to the user
26             require Imager::QRCode;
27             my $qrcode = Imager::QRCode->new(
28             size => 4,
29             margin => 4,
30             version => 1,
31             level => 'M',
32             casesensitive => 1,
33             );
34             my $img = $qrcode->plot($self->registration_url($label, $type));
35             $img->write( data => \my $res, type => 'png' );
36             $res
37             }
38              
39             sub registration_key {
40             return encode_base32( $_[0]->{secret} );
41             }
42              
43             sub totp {
44             my ($self, $ts) = @_;
45             $self->auth->totp( $self->{secret}, $ts )
46             };
47              
48             sub registration_url {
49             my ($self, $label, $type) = @_;
50             $type ||= 'totp';
51             $label= uri_escape($label);
52             return "otpauth://$type/$label?secret=" . $self->registration_key
53             }
54              
55             sub verify {
56             my ($self, $code, $ts) = @_;
57             return ($code and
58             $self->totp( $ts ) == $code);
59             }
60              
61             1;
62              
63             =head1 WORKFLOW
64              
65             =over 4
66              
67             =item *
68              
69             Install Google Authenticator
70              
71             =item *
72              
73             Visit the "Install Two Factor Authentication" page
74              
75             =item *
76              
77             Display the secret key there
78              
79             ->registration_qr_code
80             ->registration_key
81              
82             Display the "Panic" OTPs there so that the user can print them out
83             on paper and store them in a secure location:
84              
85             my @recovery_passwords = generate_recovery_strings( 3 );
86             for my $pass ( @recovery_passwords ) {
87             print $pass, "\n";
88             };
89              
90             =item *
91              
92             Photograph the QR code
93              
94             or
95              
96             Manually enter the key into the Authenticator
97              
98             =item *
99              
100             On the Login page enter the password
101             and the OTP code from the Authenticator
102             or on the Recovery page, enter one of the panic keys.
103              
104             =back
105              
106             =head1 PASSWORD STORAGE
107              
108             The password should be stored as a hash.
109              
110             The shared authenticator secret needs to be stored as plaintext.
111              
112             =head1 RECOVERY
113              
114             As phones tend to get lost, the recovery passphrases become
115             important. They also are password equivalent. So, my recommendation
116             is to store the recovery passphrases only as hashes, just
117             like you store passwords.
118              
119             =head1 COMPATIBILITY
120              
121             At least on iDevices, using C<< < >> or C<< > >> made registering
122             the generated accounts through QRcodes fail. The QRcodes work
123             with Android devices.
124              
125             =head1 SEE ALSO
126              
127             TOTP: Time-Based One-Time Password Algorithm
128              
129             L<http://tools.ietf.org/html/rfc6238>
130              
131             =cut