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