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.1';
3 3     3   21720 use warnings;
  3         5  
  3         97  
4 3     3   38 use strict;
  3         4  
  3         74  
5              
6 3     3   1393 use Digest::HMAC;
  3         1675  
  3         130  
7 3     3   2920 use Math::BigInt;
  3         70753  
  3         14  
8 3     3   42221 use Moo 2.002004;
  3         29800  
  3         15  
9 3     3   4283 use Types::Standard qw( Int Str );
  3         220750  
  3         33  
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 10     10 1 6853 my ( $self, $secret, $manual_time ) = @_;
32 10 50       25 $secret = join( "", map chr( hex($_) ), $secret =~ /(..)/g )
33             if $secret =~ /^[a-fA-F0-9]{32,}$/;
34 10         184 my $mod = $self->digest;
35 10 50       481 if ( eval "require $mod" ) {
36 10         165 $mod->import();
37             }
38 10   33     40 my $time = $manual_time || time();
39 10         154 my $T = Math::BigInt->new( int( $time / $self->timestep ) );
40 10 50       564 die "Must request at least 6 digits" if $self->digits < 6;
41 10         63 ( my $hex = $T->as_hex ) =~ s/^0x(.*)/"0"x(16 - length $1) . $1/e;
  10         191  
42 10         99 my $bin_code = join( "", map chr( hex($_) ), $hex =~ /(..)/g );
43 10         33 my $otp = $self->_process( $secret, $bin_code );
44 10         1417 return $otp;
45             }
46              
47              
48             sub hotp {
49 20     20 1 62 my ( $self, $secret, $c ) = @_;
50 20 50       42 $secret = join( "", map chr( hex($_) ), $secret =~ /(..)/g )
51             if $secret =~ /^[a-fA-F0-9]{32,}$/;
52 20         398 my $mod = $self->digest;
53 20 50       911 if ( eval "require $mod" ) {
54 20         281 $mod->import();
55             }
56 20         79 $c = Math::BigInt->new($c);
57 20 50       930 die "Must request at least 6 digits" if $self->digits < 6;
58 20         113 ( my $hex = $c->as_hex ) =~ s/^0x(.*)/"0"x(16 - length $1) . $1/e;
  20         364  
59 20         200 my $bin_code = join( "", map chr( hex($_) ), $hex =~ /(..)/g );
60 20         58 my $otp = $self->_process( $secret, $bin_code );
61 20         2373 return $otp;
62             }
63              
64              
65             sub _process {
66 30     30   35 my ( $self, $secret, $bin_code ) = @_;
67 30         466 my $hmac = Digest::HMAC->new( $secret, $self->digest );
68 30         751 $hmac->add($bin_code);
69 30         124 my $hash = $hmac->digest();
70 30         519 my $offset = hex substr unpack( "H*" => $hash ), -1;
71 30         68 my $dt = unpack "N" => substr $hash, $offset, 4;
72 30         33 $dt &= 0x7fffffff;
73 30         68 $dt = Math::BigInt->new($dt);
74 30         1389 my $modulus = 10**$self->digits;
75              
76 30 50       509 if ( $self->digits < 10 ) {
77 30         192 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.1
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-2017 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__