| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Data::Password::zxcvbn::Match::Sequence; |
|
2
|
3
|
|
|
3
|
|
9282
|
use Moo; |
|
|
3
|
|
|
|
|
9
|
|
|
|
3
|
|
|
|
|
21
|
|
|
3
|
|
|
|
|
|
|
with 'Data::Password::zxcvbn::Match'; |
|
4
|
|
|
|
|
|
|
our $VERSION = '1.1.2'; # VERSION |
|
5
|
|
|
|
|
|
|
# ABSTRACT: match class for sequences of uniformly-spaced codepoints |
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
has ascending => (is => 'ro', default => 1); |
|
9
|
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
sub estimate_guesses { |
|
12
|
654
|
|
|
654
|
1
|
15346
|
my ($self,$min_guesses) = @_; |
|
13
|
|
|
|
|
|
|
|
|
14
|
654
|
|
|
|
|
2783
|
my $first_char = substr($self->token,0,1); |
|
15
|
|
|
|
|
|
|
|
|
16
|
654
|
|
|
|
|
1181
|
my $guesses; |
|
17
|
|
|
|
|
|
|
# lower guesses for obvious starting points |
|
18
|
654
|
100
|
|
|
|
3988
|
if ($first_char =~ m{[aAzZ019]}) { |
|
|
|
100
|
|
|
|
|
|
|
19
|
179
|
|
|
|
|
413
|
$guesses = 4; |
|
20
|
|
|
|
|
|
|
} |
|
21
|
|
|
|
|
|
|
elsif ($first_char =~ m{[0-9]}) { |
|
22
|
180
|
|
|
|
|
434
|
$guesses = 10; # digits |
|
23
|
|
|
|
|
|
|
} |
|
24
|
|
|
|
|
|
|
else { |
|
25
|
|
|
|
|
|
|
# could give a higher base for uppercase, assigning 26 to both |
|
26
|
|
|
|
|
|
|
# upper and lower sequences is more conservative. |
|
27
|
295
|
|
|
|
|
678
|
$guesses = 26; |
|
28
|
|
|
|
|
|
|
} |
|
29
|
|
|
|
|
|
|
|
|
30
|
654
|
100
|
|
|
|
2849
|
$guesses *= 2 unless $self->ascending; |
|
31
|
|
|
|
|
|
|
|
|
32
|
654
|
|
|
|
|
3084
|
return $guesses * length($self->token); |
|
33
|
|
|
|
|
|
|
} |
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
sub feedback_warning { |
|
37
|
5
|
|
|
5
|
1
|
16
|
my ($self) = @_; |
|
38
|
|
|
|
|
|
|
|
|
39
|
5
|
|
|
|
|
37
|
return 'Sequences like abc or 6543 are easy to guess'; |
|
40
|
|
|
|
|
|
|
} |
|
41
|
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
sub feedback_suggestions { |
|
43
|
5
|
|
|
5
|
1
|
28
|
return [ 'Avoid sequences' ]; |
|
44
|
|
|
|
|
|
|
} |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
my $MAX_DELTA = 5; |
|
48
|
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
sub make { |
|
50
|
1506
|
|
|
1506
|
1
|
93363
|
my ($class, $password) = @_; |
|
51
|
|
|
|
|
|
|
# Identifies sequences by looking for repeated differences in |
|
52
|
|
|
|
|
|
|
# unicode codepoint. this allows skipping, such as 9753, and also |
|
53
|
|
|
|
|
|
|
# matches some extended unicode sequences such as Greek and |
|
54
|
|
|
|
|
|
|
# Cyrillic alphabets. |
|
55
|
|
|
|
|
|
|
# |
|
56
|
|
|
|
|
|
|
# for example, consider the input 'abcdb975zy' |
|
57
|
|
|
|
|
|
|
# |
|
58
|
|
|
|
|
|
|
# password: a b c d b 9 7 5 z y |
|
59
|
|
|
|
|
|
|
# index: 0 1 2 3 4 5 6 7 8 9 |
|
60
|
|
|
|
|
|
|
# delta: 1 1 1 -2 -41 -2 -2 69 1 |
|
61
|
|
|
|
|
|
|
# |
|
62
|
|
|
|
|
|
|
# expected result: |
|
63
|
|
|
|
|
|
|
# [(i, j, delta), ...] = [(0, 3, 1), (5, 7, -2), (8, 9, 1)] |
|
64
|
|
|
|
|
|
|
|
|
65
|
1506
|
|
|
|
|
3337
|
my $length = length($password); |
|
66
|
1506
|
100
|
|
|
|
5594
|
return [] if $length <= 1; |
|
67
|
|
|
|
|
|
|
|
|
68
|
1055
|
|
|
|
|
2547
|
my @matches; |
|
69
|
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
my $update = sub { |
|
71
|
6378
|
|
|
6378
|
|
12025
|
my ($i,$j,$delta) = @_; |
|
72
|
6378
|
|
100
|
|
|
15581
|
my $abs_delta = abs($delta||0); |
|
73
|
6378
|
100
|
100
|
|
|
22761
|
return unless $j-$i>1 or $abs_delta == 1; |
|
74
|
733
|
100
|
|
|
|
2919
|
return if $abs_delta == 0; |
|
75
|
690
|
100
|
|
|
|
2704
|
return if $abs_delta > $MAX_DELTA; |
|
76
|
|
|
|
|
|
|
|
|
77
|
662
|
|
|
|
|
2109
|
my $token = substr($password,$i,$j-$i+1); |
|
78
|
662
|
|
|
|
|
18559
|
push @matches, $class->new({ |
|
79
|
|
|
|
|
|
|
token => $token, |
|
80
|
|
|
|
|
|
|
i => $i, j => $j, |
|
81
|
|
|
|
|
|
|
ascending => !!($delta>0), |
|
82
|
|
|
|
|
|
|
}); |
|
83
|
1055
|
|
|
|
|
10246
|
}; |
|
84
|
|
|
|
|
|
|
|
|
85
|
1055
|
|
|
|
|
3258
|
my $i=0; |
|
86
|
1055
|
|
|
|
|
2495
|
my $last_delta; |
|
87
|
1055
|
|
|
|
|
4416
|
for my $k (1..$length-1) { |
|
88
|
6611
|
|
|
|
|
15326
|
my $delta = ord(substr($password,$k,1)) - ord(substr($password,$k-1,1)); |
|
89
|
6611
|
100
|
|
|
|
13791
|
$last_delta = $delta unless defined($last_delta); |
|
90
|
6611
|
100
|
|
|
|
14034
|
next if $delta == $last_delta; |
|
91
|
5323
|
|
|
|
|
8949
|
my $j = $k-1; |
|
92
|
5323
|
|
|
|
|
13645
|
$update->($i,$j,$last_delta); |
|
93
|
5323
|
|
|
|
|
30406
|
$i = $j; $last_delta = $delta; |
|
|
5323
|
|
|
|
|
9452
|
|
|
94
|
|
|
|
|
|
|
} |
|
95
|
1055
|
|
|
|
|
5587
|
$update->($i,$length-1,$last_delta); |
|
96
|
|
|
|
|
|
|
|
|
97
|
1055
|
|
|
|
|
19328
|
return \@matches; |
|
98
|
|
|
|
|
|
|
} |
|
99
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
around fields_for_json => sub { |
|
102
|
|
|
|
|
|
|
my ($orig,$self) = @_; |
|
103
|
|
|
|
|
|
|
( $self->$orig(), qw(ascending) ) |
|
104
|
|
|
|
|
|
|
}; |
|
105
|
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
1; |
|
107
|
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
__END__ |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
=pod |
|
111
|
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
=encoding UTF-8 |
|
113
|
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=head1 NAME |
|
115
|
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
Data::Password::zxcvbn::Match::Sequence - match class for sequences of uniformly-spaced codepoints |
|
117
|
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
=head1 VERSION |
|
119
|
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
version 1.1.2 |
|
121
|
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
123
|
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
This class represents the guess that a certain substring of a |
|
125
|
|
|
|
|
|
|
password, consisting of uniformly-spaced codepoints, is easy to guess. |
|
126
|
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=head2 C<ascending> |
|
130
|
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
Boolean, true if the sequence starts at a lower codepoint and ends at |
|
132
|
|
|
|
|
|
|
a higher one (e.g. C<acegi> is ascending, C<86420> is not). |
|
133
|
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=head1 METHODS |
|
135
|
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
=head2 C<estimate_guesses> |
|
137
|
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
The number of guesses is I<linear> with the length of the |
|
139
|
|
|
|
|
|
|
sequence. Descending sequences get a higher estimate, sequences that |
|
140
|
|
|
|
|
|
|
start at obvious points (e.g. C<A> or C<1>) get lower estimates. |
|
141
|
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=head2 C<feedback_warning> |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=head2 C<feedback_suggestions> |
|
145
|
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
This class suggests not using sequences. |
|
147
|
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
=head2 C<make> |
|
149
|
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
my @matches = @{ Data::Password::zxcvbn::Match::Sequence->make( |
|
151
|
|
|
|
|
|
|
$password, |
|
152
|
|
|
|
|
|
|
) }; |
|
153
|
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
Scans the C<$password> for sequences of characters whose codepoints |
|
155
|
|
|
|
|
|
|
increase or decrease by a constant. |
|
156
|
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
=head2 C<fields_for_json> |
|
158
|
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
The JSON serialisation for matches of this class will contain C<token |
|
160
|
|
|
|
|
|
|
i j guesses guesses_log10 ascending>. |
|
161
|
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=head1 AUTHOR |
|
163
|
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
Gianni Ceccarelli <gianni.ceccarelli@broadbean.com> |
|
165
|
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
This software is copyright (c) 2022 by BroadBean UK, a CareerBuilder Company. |
|
169
|
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
|
171
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
|
172
|
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
=cut |