File Coverage

blib/lib/Data/Password/Meter.pm
Criterion Covered Total %
statement 67 70 95.7
branch 49 58 84.4
condition 12 24 50.0
subroutine 8 8 100.0
pod 6 6 100.0
total 142 166 85.5


line stmt bran cond sub pod time code
1             package Data::Password::Meter;
2 1     1   15373 use strict;
  1         2  
  1         24  
3 1     1   3 use warnings;
  1         1  
  1         650  
4              
5             # Todo:
6             # - see: https://en.wikipedia.org/wiki/Password_strength#NIST_Special_Publication_800-63
7              
8             our $VERSION = '0.08';
9              
10              
11             # Error messages
12             my $S = 'The password ';
13             my @PART = (
14             'is too short',
15             'should contain special characters',
16             'should contain combinations of letters, numbers and special characters'
17             );
18             our @ERR = (
19             undef,
20             'There is no password given',
21             'Passwords are not allowed to contain control sequences',
22             'Passwords are not allowed to consist of repeating characters only',
23             $S . $PART[0],
24             $S . $PART[1],
25             $S . $PART[2],
26             $S . $PART[0] . ' and ' . $PART[1],
27             $S . $PART[0] . ' and ' . $PART[2],
28             $S . $PART[1] . ' and ' . $PART[2],
29             $S . $PART[0] . ', ' . $PART[1] . ' and ' . $PART[2]
30             );
31              
32              
33             # Constructor
34             sub new {
35 2     2 1 13 my $class = shift;
36              
37             # Accept threshold parameter
38 2 100 66     14 my $threshold = $_[0] && $_[0] =~ /^\d+$/ ? $_[0] : 25;
39 2         8 bless [ $threshold, 0 ], $class;
40             };
41              
42              
43             # Error code
44             sub err {
45 25     25 1 23 my $self = shift;
46 25 100       64 return 0 unless $self->[2];
47              
48 21 100       74 return $self->[2] if @$self == 3;
49              
50             # Combinations of errors
51 6 50       10 if (@$self == 4) {
52 6 100 66     35 return 7 if $self->[2] == 4 && $self->[3] == 5;
53 2 50 33     14 return 8 if $self->[2] == 4 && $self->[3] == 6;
54 0 0 0     0 return 9 if $self->[2] == 5 && $self->[3] == 6;
55             };
56              
57 0         0 return 10;
58             };
59              
60              
61             # Error string
62             sub errstr {
63 22 100 50 22 1 89 return $_[1] ? ($ERR[$_[1]] // '') : ($ERR[$_[0]->err] // '');
      100        
64             };
65              
66              
67             # Score
68             sub score {
69 10     10 1 28 $_[0]->[1];
70             };
71              
72              
73             # Threshold
74             sub threshold {
75 40     40 1 40 my $self = shift;
76 40 100       132 return $self->[0] unless $_[0];
77 1 50       8 $self->[0] = shift if $_[0] =~ /^\d+$/;
78             };
79              
80              
81             # Check the strength of the password
82             sub strong {
83 15     15 1 23 my ($self, $pwd) = @_;
84              
85             # Initialize object
86 15   50     22 @$self = ($self->threshold // 25, 0);
87              
88             # No password is too weak
89 15 100       23 unless ($pwd) {
90 1         2 $self->[2] = 1;
91 1         5 return;
92             };
93              
94             # Control characters
95 14 100       41 if ($pwd =~ m/[\a\f\v\n\r\t]/) {
96 1         2 $self->[2] = 2;
97 1         3 return;
98             };
99              
100             # Only one repeating character
101 13 100       43 if ($pwd =~ /^(.)\1*$/) {
102 2         3 $self->[2] = 3;
103 2         7 return;
104             };
105              
106 11         8 my $score = 0;
107              
108             # Based on passwordmeter by Steve Moitozo -- geekwisdom.com
109              
110             # Length
111 11         13 my $pwd_l = length $pwd;
112 11 100 33     64 if ($pwd_l < 5) {
    50 66        
    100          
    50          
113             # Less than 5 characters
114 2         2 $score += 3;
115             }
116             elsif ($pwd_l > 4 && $pwd_l < 8) {
117             # More than 4 characters
118 0         0 $score += 6;
119             }
120             elsif ($pwd_l > 7 && $pwd_l < 16) {
121             # More than 7 characters
122 7         9 $score += 12;
123             }
124             elsif ($pwd_l > 15) {
125             # More than 15 characters
126 2         2 $score += 18;
127             };
128              
129 11 100       15 if ($pwd_l > 8) {
130             # + 2 for every character above 8
131 5         8 $score += (($pwd_l - 8) * 2);
132             }
133              
134             # Password is too short
135             else {
136 6         9 push @$self, 4;
137             };
138              
139             # Letters
140 11 50       24 if ($pwd =~ /[a-z]/) {
141             # At least one lower case character
142 11         6 $score++;
143             };
144              
145 11 100       19 if ($pwd =~ /[A-Z]/) {
146             # At least one upper case character
147 6         6 $score += 5;
148             };
149              
150             # Numbers
151 11 100       19 if ($pwd =~ /\d/) {
152             # At least one number
153 2         2 $score += 5;
154              
155 2 100       9 if ($pwd =~ /(?:.*\d){3}/) {
156             # At least three numbers
157 1         1 $score += 5;
158             };
159             };
160              
161             # Special characters
162 11 100       19 if ($pwd =~ /[^a-zA-Z0-9]/) {
163             # At least one special character
164 8         7 $score += 5;
165              
166 8 100       25 if ($pwd =~ /(?:.*[^a-zA-Z0-9]){2}/) {
167             # At least two special characters
168 5         5 $score += 5;
169             };
170             }
171             else {
172 3         3 push @$self, 5;
173             };
174              
175             # Scoring is not enough to succeed
176 11 100       14 unless ($score > ($self->threshold - 6)) {
177 4         4 $self->[1] = $score;
178 4         12 return;
179             };
180              
181             # Combos
182 7 100       22 if ($pwd =~ /(?:[a-z].*[A-Z])|(?:[A-Z].*[a-z])/) {
183             # At least one combination of upper and lower case characters
184 5         6 $score += 2;
185             };
186              
187 7 100       23 if ($pwd =~ /(?:[a-zA-Z].*\d)|(?:\d.*[a-zA-Z])/) {
188             # At least one combination of letters and numbers
189 2         3 $score += 2
190             };
191              
192 7 50       16 if ($pwd =~ /(?:[a-zA-Z0-9].*[^a-zA-Z0-9])|(?:[^a-zA-Z0-9].*[a-zA-Z0-9])/) {
193             # At least one combination of letters, numbers and special characters
194 7         4 $score += 2;
195             };
196              
197 7         7 push @$self, 6;
198              
199 7         5 $self->[1] = $score;
200 7 100       8 return if $score < $self->threshold;
201              
202 4         5 @$self = ($self->threshold, $score);
203 4         12 return 1;
204             };
205              
206              
207             1;
208              
209              
210             __END__