File Coverage

blib/lib/Authen/OATH.pm
Criterion Covered Total %
statement 54 55 98.1
branch 7 14 50.0
condition 1 3 33.3
subroutine 9 9 100.0
pod 2 2 100.0
total 73 83 87.9


line stmt bran cond sub pod time code
1             package Authen::OATH;
2             $Authen::OATH::VERSION = '2.0.0';
3 2     2   13266 use warnings;
  2         2  
  2         47  
4 2     2   7 use strict;
  2         2  
  2         26  
5              
6 2     2   709 use Digest::HMAC;
  2         759  
  2         65  
7 2     2   1690 use Math::BigInt;
  2         30662  
  2         6  
8 2     2   19603 use Moo 2.002004;
  2         19118  
  2         9  
9 2     2   2818 use Types::Standard qw( Int Str );
  2         87384  
  2         15  
10              
11             has digits => (
12             is => 'rw',
13             isa => Int,
14             default => 6,
15             );
16              
17             has digest => (
18             is => 'rw',
19             isa => Str,
20             default => 'Digest::SHA',
21             );
22              
23             has timestep => (
24             is => 'rw',
25             isa => Int,
26             default => 30,
27             );
28              
29              
30             sub totp {
31 5     5 1 3575 my ( $self, $secret, $manual_time ) = @_;
32 5 50       11 $secret = join( "", map chr( hex() ), $secret =~ /(..)/g )
33             if $secret =~ /^[a-fA-F0-9]{32,}$/;
34 5         101 my $mod = $self->digest;
35 5 50       205 if ( eval "require $mod" ) {
36 5         68 $mod->import();
37             }
38 5   33     14 my $time = $manual_time || time();
39 5         76 my $T = Math::BigInt->new( int( $time / $self->timestep ) );
40 5 50       248 die "Must request at least 6 digits" if $self->digits < 6;
41 5         29 ( my $hex = $T->as_hex ) =~ s/^0x(.*)/"0"x(16 - length $1) . $1/e;
  5         87  
42 5         44 my $bin_code = join( "", map chr hex, $hex =~ /(..)/g );
43 5         14 my $otp = $self->_process( $secret, $bin_code );
44 5         527 return $otp;
45             }
46              
47              
48             sub hotp {
49 10     10 1 24 my ( $self, $secret, $c ) = @_;
50 10 50       22 $secret = join( "", map chr( hex() ), $secret =~ /(..)/g )
51             if $secret =~ /^[a-fA-F0-9]{32,}$/;
52 10         186 my $mod = $self->digest;
53 10 50       416 if ( eval "require $mod" ) {
54 10         129 $mod->import();
55             }
56 10         32 $c = Math::BigInt->new($c);
57 10 50       397 die "Must request at least 6 digits" if $self->digits < 6;
58 10         52 ( my $hex = $c->as_hex ) =~ s/^0x(.*)/"0"x(16 - length $1) . $1/e;
  10         153  
59 10         85 my $bin_code = join( "", map chr hex, $hex =~ /(..)/g );
60 10         22 my $otp = $self->_process( $secret, $bin_code );
61 10         922 return $otp;
62             }
63              
64              
65             sub _process {
66 15     15   15 my ( $self, $secret, $bin_code ) = @_;
67 15         210 my $hmac = Digest::HMAC->new( $secret, $self->digest );
68 15         312 $hmac->add($bin_code);
69 15         58 my $hash = $hmac->digest();
70 15         224 my $offset = hex substr unpack( "H*" => $hash ), -1;
71 15         22 my $dt = unpack "N" => substr $hash, $offset, 4;
72 15         16 $dt &= 0x7fffffff;
73 15         28 $dt = Math::BigInt->new($dt);
74 15         510 my $modulus = 10**$self->digits;
75              
76 15 50       211 if ( $self->digits < 10 ) {
77 15         84 return sprintf( "%0$self->{ 'digits' }d", $dt->bmod($modulus) );
78             }
79             else {
80 0           return $dt->bmod($modulus);
81             }
82              
83             }
84              
85              
86             1; # End of Authen::OATH
87              
88             =pod
89              
90             =encoding UTF-8
91              
92             =head1 NAME
93              
94             Authen::OATH - OATH One Time Passwords
95              
96             =head1 VERSION
97              
98             version 2.0.0
99              
100             =head1 SYNOPSIS
101              
102             use Authen::OATH;
103              
104             my $oath = Authen::OATH->new();
105             my $totp = $oath->totp( 'MySecretPassword' );
106             my $hotp = $oath->hotp( 'MyOtherSecretPassword' );
107              
108             Parameters may be overridden when creating the new object:
109              
110             my $oath = Authen::OATH->new( digits => 8 );
111              
112             The three parameters are "digits", "digest", and "timestep."
113             Timestep only applies to the totp() function.
114              
115             While strictly speaking this is outside the specifications of
116             HOTP and TOTP, you can specify digests other than SHA1. For example:
117              
118             my $oath = Authen::OATH->new(
119             digits => 10,
120             digest => 'Digest::MD6',
121             );
122              
123             If you are using Google Authenticator, you'll want to decode your secret
124             *before* passing it to the C method:
125              
126             use Convert::Base32 qw( decode_base32 );
127              
128             my $oath = Authen::OATH->new;
129             my $secret = 'mySecret';
130             my $otp = $oath->totp( decode_base32( $secret ) );
131              
132             =head1 DESCRIPTION
133              
134             Implementation of the HOTP and TOTP One Time Password algorithms
135             as defined by OATH (http://www.openauthentication.org)
136              
137             All necessary parameters are set by default, though these can be
138             overridden. Both totp() and htop() have passed all of the test
139             vectors defined in the RFC documents for TOTP and HOTP.
140              
141             totp() and hotp() both default to returning 6 digits and using SHA1.
142             As such, both can be called by passing only the secret key and a
143             valid OTP will be returned.
144              
145             =head1 SUBROUTINES/METHODS
146              
147             =head2 totp
148              
149             my $otp = $oath->totp( $secret [, $manual_time ] );
150              
151             Manual time is an optional parameter. If it is not passed, the current
152             time is used. This is useful for testing purposes.
153              
154             =head2 hotp
155              
156             my $opt = $oath->hotp( $secret, $counter );
157              
158             Both parameters are required.
159              
160             =head2 _process
161              
162             This is an internal routine and is never called directly.
163              
164             =head1 CAVEATS
165              
166             Please see the SYNOPSIS for how interaction with Google Authenticator.
167              
168             =head1 AUTHOR
169              
170             Kurt Kincaid
171              
172             =head1 COPYRIGHT AND LICENSE
173              
174             This software is copyright (c) 2010 by Kurt Kincaid.
175              
176             This is free software; you can redistribute it and/or modify it under
177             the same terms as the Perl 5 programming language system itself.
178              
179             =cut
180              
181             __END__