File Coverage

blib/lib/Crypt/URandom/Token.pm
Criterion Covered Total %
statement 45 46 97.8
branch 13 14 92.8
condition 10 13 76.9
subroutine 10 10 100.0
pod 3 3 100.0
total 81 86 94.1


line stmt bran cond sub pod time code
1             package Crypt::URandom::Token;
2              
3 3     3   1037877 use strict;
  3         8  
  3         122  
4 3     3   15 use warnings;
  3         5  
  3         204  
5 3     3   43 use v5.20;
  3         12  
6              
7 3     3   1860 use Crypt::URandom qw(urandom);
  3         14770  
  3         232  
8 3     3   26 use Carp qw(croak);
  3         6  
  3         175  
9 3     3   59 use Exporter qw(import);
  3         7  
  3         2000  
10              
11             our @EXPORT_OK = qw(urandom_token);
12              
13             our $VERSION = "0.005";
14              
15             =head1 NAME
16              
17             Crypt::URandom::Token - Generate secure strings for passwords, secrets and similar
18              
19             =head1 SYNOPSIS
20              
21             use Crypt::URandom::Token qw(urandom_token);
22              
23             # generates a 44-character alphanumeric token (default)
24             my $token = urandom_token();
25              
26             # generate a 19 character lowercase alphanumeric password
27             my $password = urandom_token(19, [a..z, 0..9]);
28              
29             # generate a 6 digit numeric pin
30             my $pin = urandom_token(6, "0123456789");
31              
32             # Object usage:
33             my $obj = Crypt::URandom::Token->new(
34             length => 44,
35             alphabet => [ A..Z, a..z, 0..9 ],
36             );
37             my $token = $obj->get;
38              
39             =head1 DESCRIPTION
40              
41             This module provides a secure way to generate a random token for passwords and
42             similar using L as the source of random bits.
43              
44             By default, it generates a 44 character alphanumeric token with more than 256
45             bits of entropy. A custom alphabet with between 2 and 256 elements can be
46             provided.
47              
48             Modulo reduction and rejection sampling is used to prevent modulus bias. Keep in
49             mind that bias will be introduced if duplicate elements are provided in the
50             alphabet.
51              
52             =head1 FUNCTIONS
53              
54             =head2 urandom_token
55              
56             my $token = urandom_token($length, $alphabet);
57              
58             Returns a string of C<$length> random characters from C<$alphabet>.
59              
60             If C<$length> is not provided, it defaults to 44.
61              
62             If C<$alphabet> is not provided, it defaults to uppercase letters, lowercase
63             letters, and digits. You can provide either a string of characters or an
64             arrayref.
65              
66             =head1 METHODS
67              
68             =head2 new
69              
70             Creates a new token generator object. Accepts a hash or hashref with these
71             parameters:
72              
73             =over 4
74              
75             =item * C - desired token length (defaults to 44)
76              
77             =item * C - the set of characters to use. Can be a string of characters or an array reference. Defaults to C<[ A..Z, a..z, 0..9 ]>
78              
79             =back
80              
81             =head2 get
82              
83             Generates and returns a random token as a token, using the object attributes for
84             length and alphabet.
85              
86             =head1 AUTHOR
87              
88             Stig Palmquist
89              
90             =head1 LICENSE
91              
92             This library is free software; you can redistribute it and/or modify it under
93             the same terms as Perl itself.
94              
95             =cut
96              
97             sub new {
98 6     6 1 961 my ($class, @args) = @_;
99 6 100 66     34 if (@args == 1 && ref $args[0] eq 'HASH') {
100 1         2 @args = %{ $args[0] };
  1         5  
101             }
102 6         20 my %args = @args;
103 6         24 return bless \%args, $class;
104             }
105              
106             sub get {
107 6     6 1 2721 my $self = shift;
108 6         31 return urandom_token($self->{length}, $self->{alphabet});
109             }
110              
111             sub _alphabet {
112 18     18   38 my $in = shift;
113              
114 18         27 my @alphabet;
115 18 100 66     96 if ( ref $in eq 'ARRAY' ) {
    100          
116 5         42 @alphabet = @$in;
117             } elsif (defined $in && !ref $in) {
118 7   50     115 @alphabet = split("", ($in // ""));
119             } else {
120 6         114 @alphabet = ("A" .. "Z", "a" .. "z", "0" .. "9");
121             }
122              
123 18 100 100     96 unless (@alphabet >= 2 && @alphabet <= 256) {
124 2         80 croak "alphabet size must be between 2 and 256 elements";
125             }
126              
127 16         305 return @alphabet;
128             }
129              
130             sub urandom_token {
131 18   100 18 1 1737 my $length = shift || 44;
132 18         56 my @alphabet = _alphabet(shift);
133              
134 16 50       51 unless ($length > 0) {
135 0         0 croak "length must be a positive integer";
136             }
137              
138 16         36 my $bias_lim = 256 % @alphabet;
139              
140 16         31 my (@bytes, @token);
141 16         49 while (@token < $length) {
142 2069842 100       3809661 @bytes = split "", urandom(32) unless @bytes;
143 2069842         5074351 my $num = ord(shift @bytes);
144 2069842 100       3891519 next if $num < $bias_lim;
145 2004688         4469384 push @token, $alphabet[$num % @alphabet];
146             }
147 16         865088 return join "", @token;
148             }
149              
150             1;