| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Crypt::GeneratePassword; |
|
2
|
|
|
|
|
|
|
$Crypt::GeneratePassword::VERSION = '0.05'; |
|
3
|
|
|
|
|
|
|
# ABSTRACT: generate secure random pronounceable passwords |
|
4
|
|
|
|
|
|
|
|
|
5
|
4
|
|
|
4
|
|
63109
|
use 5.006; |
|
|
4
|
|
|
|
|
14
|
|
|
6
|
4
|
|
|
4
|
|
20
|
use strict; |
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
107
|
|
|
7
|
4
|
|
|
4
|
|
35
|
use warnings; |
|
|
4
|
|
|
|
|
12
|
|
|
|
4
|
|
|
|
|
9220
|
|
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
=encoding utf-8 |
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
=head1 NAME |
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
Crypt::GeneratePassword - generate secure random pronounceable passwords |
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
use Crypt::GeneratePassword qw(word chars); |
|
18
|
|
|
|
|
|
|
$word = word($minlen,$maxlen); |
|
19
|
|
|
|
|
|
|
$word = chars($minlen,$maxlen); |
|
20
|
|
|
|
|
|
|
*Crypt::GeneratePassword::restrict = \&my_restriction_filter; |
|
21
|
|
|
|
|
|
|
*Crypt::GeneratePassword::random_number = \&my_random_number_generator; |
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
Crypt::GeneratePassword generates random passwords that are |
|
26
|
|
|
|
|
|
|
(more or less) pronounceable. Unlike Crypt::RandPasswd, it |
|
27
|
|
|
|
|
|
|
doesn't use the FIPS-181 NIST standard, which is proven to be |
|
28
|
|
|
|
|
|
|
insecure. It does use a similar interface, so it should be a |
|
29
|
|
|
|
|
|
|
drop-in replacement in most cases. |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
If you want to use passwords from a different language than english, |
|
32
|
|
|
|
|
|
|
you can use one of the packaged alternate unit tables or generate |
|
33
|
|
|
|
|
|
|
your own. See below for details. |
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
For details on why FIPS-181 is insecure and why the solution |
|
36
|
|
|
|
|
|
|
used in this module is reasonably secure, see "A New Attack on |
|
37
|
|
|
|
|
|
|
Random Pronounceable Password Generators" by Ravi Ganesan and |
|
38
|
|
|
|
|
|
|
Chris Davies, available online in may places - use your |
|
39
|
|
|
|
|
|
|
favourite search engine. |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
This module improves on FIPS-181 using a true random selection with |
|
42
|
|
|
|
|
|
|
the word generator as mere filter. Other improvements are |
|
43
|
|
|
|
|
|
|
better pronounceability using third order approximation instead |
|
44
|
|
|
|
|
|
|
of second order and multi-language support. |
|
45
|
|
|
|
|
|
|
Drawback of this method is that it is usually slower. Then again, |
|
46
|
|
|
|
|
|
|
computer speed has improved a little since 1977. |
|
47
|
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
=head1 Functions |
|
49
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
=cut |
|
51
|
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
require Exporter; |
|
53
|
|
|
|
|
|
|
our @ISA = ('Exporter'); |
|
54
|
|
|
|
|
|
|
our @EXPORT_OK = qw(word word3 analyze analyze3 chars generate_language load_language); |
|
55
|
|
|
|
|
|
|
our %EXPORT_TAGS = ( 'all' => [ @Crypt::GeneratePassword::EXPORT_OK ] ); |
|
56
|
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
my $default_language = 'en'; |
|
58
|
|
|
|
|
|
|
our %languages = (); |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
=head2 chars |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
$word = chars($minlen, $maxlen [, $set [, $characters, $maxcount ] ... ] ); |
|
63
|
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
Generates a completely random word between $minlen and $maxlen in length. |
|
65
|
|
|
|
|
|
|
If $set is given, it must be an array ref of characters to use. You can |
|
66
|
|
|
|
|
|
|
restrict occurrence of some characters by providing ($characters, $maxcount) |
|
67
|
|
|
|
|
|
|
pairs, as many as you like. $characters must be a string consisting of those |
|
68
|
|
|
|
|
|
|
characters which may appear at most $maxcount times in the word. |
|
69
|
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
Note that the length is determined via relative probability, not uniformly. |
|
71
|
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
=cut |
|
73
|
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
my @signs = ('0'..'9', '%', '$', '_', '-', '+', '*', '&', '/', '=', '!', '#'); |
|
75
|
|
|
|
|
|
|
my $signs = join('',@signs); |
|
76
|
|
|
|
|
|
|
my @caps = ('A' .. 'Z'); |
|
77
|
|
|
|
|
|
|
my $caps = join('',@caps); |
|
78
|
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
my @set = ( |
|
80
|
|
|
|
|
|
|
[ ["\x00",'a'..'z'], ["\x00",'a'..'z',@caps] ], |
|
81
|
|
|
|
|
|
|
[ ["\x00",'a'..'z',@signs], ["\x00",'a'..'z',@caps,@signs] ] |
|
82
|
|
|
|
|
|
|
); |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
sub chars($$;$@) { |
|
85
|
1317014
|
|
|
1317014
|
1
|
2048917
|
my ($minlen, $maxlen, $set, @restrict) = @_; |
|
86
|
1317014
|
|
66
|
|
|
2742630
|
$set ||= $set[1][1]; |
|
87
|
1317014
|
|
|
|
|
1448771
|
my $res; |
|
88
|
1317014
|
|
|
|
|
1689136
|
my $diff = $maxlen-$minlen; |
|
89
|
|
|
|
|
|
|
WORD: { |
|
90
|
1317014
|
|
|
|
|
1357424
|
$res = join '', map { $$set[random_number(scalar(@$set))] } 1..$maxlen; |
|
|
2135481
|
|
|
|
|
3855430
|
|
|
|
29601854
|
|
|
|
|
50580380
|
|
|
91
|
2135481
|
|
|
|
|
11061087
|
$res =~ s/\x00{0,$diff}$//; |
|
92
|
2135481
|
100
|
|
|
|
5458902
|
redo if $res =~ m/\x00/; |
|
93
|
1317014
|
|
|
|
|
3687257
|
for (my $i = 0; $i < @restrict; $i+=2) { |
|
94
|
0
|
|
|
|
|
0
|
my $match = $restrict[$i]; |
|
95
|
0
|
|
|
|
|
0
|
my $more = int($restrict[$i+1])+1; |
|
96
|
0
|
0
|
|
|
|
0
|
redo WORD if $res =~ m/([\Q$match\E].*){$more,}/; |
|
97
|
|
|
|
|
|
|
} |
|
98
|
|
|
|
|
|
|
} |
|
99
|
1317014
|
|
|
|
|
2528414
|
return $res; |
|
100
|
|
|
|
|
|
|
} |
|
101
|
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
=head2 word |
|
103
|
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
$word = word($minlen, $maxlen [, $lang [, $numbers [, $caps [, $minfreq, $avgfreq ] ] ] ); |
|
105
|
|
|
|
|
|
|
$word = word3($minlen, $maxlen [, $lang [, $numbers [, $caps [, $minfreq, $avgfreq ] ] ] ); |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
Generates a random pronounceable word. The length of the returned |
|
108
|
|
|
|
|
|
|
word will be between $minlen and $maxlen. If you supply a non-zero |
|
109
|
|
|
|
|
|
|
value for $numbers, up to that many numbers and special characters |
|
110
|
|
|
|
|
|
|
will occur in the password. If you specify a non-zero value for $caps, |
|
111
|
|
|
|
|
|
|
up to this many characters will be upper case. $lang is the language |
|
112
|
|
|
|
|
|
|
description to use, loaded via load_language or built-in. Built-in |
|
113
|
|
|
|
|
|
|
languages are: 'en' (english) and 'de' (german). Contributions |
|
114
|
|
|
|
|
|
|
welcome. The default language is 'en' but may be changed by calling |
|
115
|
|
|
|
|
|
|
load_language with a true value as third parameter. Pass undef as |
|
116
|
|
|
|
|
|
|
language to select the current default language. $minfreq and $minsum |
|
117
|
|
|
|
|
|
|
determine quality of the password: $minfreq and $avgfreq are the minimum |
|
118
|
|
|
|
|
|
|
frequency each quad/trigram must have and the average frequency that the |
|
119
|
|
|
|
|
|
|
quad/trigrams must have for a word to be selected. Both are values between 0.0 |
|
120
|
|
|
|
|
|
|
and 1.0, specifying the percentage of the maximum frequency. Higher |
|
121
|
|
|
|
|
|
|
values create less secure, better pronounceable passwords and are slower. |
|
122
|
|
|
|
|
|
|
Useful $minfreq values are usually between 0.001 and 0.0001, useful $avgfreq |
|
123
|
|
|
|
|
|
|
values are around 0.05 for trigrams (word3) and 0.001 for quadgrams (word). |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
=cut |
|
126
|
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
our $total; |
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
sub word($$;$$$$$) |
|
130
|
|
|
|
|
|
|
{ |
|
131
|
10
|
|
50
|
10
|
1
|
5826
|
my $language = splice(@_,2,1) || ''; |
|
132
|
10
|
|
|
|
|
21
|
$language =~ s/[^a-zA-Z_]//g; |
|
133
|
10
|
|
33
|
|
|
45
|
$language ||= $default_language; |
|
134
|
10
|
|
|
|
|
1181
|
eval "require Crypt::GeneratePassword::$language"; |
|
135
|
10
|
|
|
|
|
49
|
my $lang = $languages{$language}; |
|
136
|
10
|
50
|
|
|
|
53
|
die "language '${language}' not found" if !$lang; |
|
137
|
|
|
|
|
|
|
|
|
138
|
10
|
|
|
|
|
22
|
my ($minlen, $maxlen, $numbers, $capitals, $minfreq, $avgfreq) = map { int($_) } @_; |
|
|
20
|
|
|
|
|
47
|
|
|
139
|
10
|
|
50
|
|
|
49
|
$minfreq ||= 0; |
|
140
|
10
|
|
50
|
|
|
45
|
$avgfreq ||= 0.001; |
|
141
|
10
|
|
50
|
|
|
48
|
$minfreq = int($$lang{'maxquad'}*$minfreq) || 1; |
|
142
|
10
|
|
|
|
|
30
|
$avgfreq = int($$lang{'maxquad'}*$avgfreq); |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
WORD: { |
|
145
|
10
|
50
|
|
|
|
17
|
my $randword = chars($minlen,$maxlen,$set[$numbers?1:0][$capitals?1:0],($numbers?($signs,$numbers):()),($capitals?($caps,$capitals):())); |
|
|
97364
|
50
|
|
|
|
402277
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
146
|
97364
|
|
|
|
|
132773
|
$total++; |
|
147
|
97364
|
|
|
|
|
142384
|
my $stripped = lc($randword); |
|
148
|
97364
|
|
|
|
|
273351
|
$stripped =~ s/[\Q$signs\E]//g; |
|
149
|
97364
|
50
|
|
|
|
186347
|
redo WORD if length($stripped) == 0; |
|
150
|
|
|
|
|
|
|
|
|
151
|
97364
|
|
|
|
|
110628
|
my $sum = 0; |
|
152
|
97364
|
|
|
|
|
113082
|
my $k0 = -1; |
|
153
|
97364
|
|
|
|
|
113146
|
my $k1 = -1; |
|
154
|
97364
|
|
|
|
|
104080
|
my $k2 = -1; |
|
155
|
97364
|
|
|
|
|
104397
|
my $k3 = -1; |
|
156
|
|
|
|
|
|
|
|
|
157
|
97364
|
|
|
|
|
306616
|
foreach my $char (split(//,$stripped)) { |
|
158
|
415442
|
|
|
|
|
476798
|
$k3 = $char; |
|
159
|
415442
|
50
|
|
|
|
758628
|
if ($k3 gt 'Z') { |
|
160
|
415442
|
|
|
|
|
513154
|
$k3 = ord($k3) - ord('a'); |
|
161
|
|
|
|
|
|
|
} else { |
|
162
|
0
|
|
|
|
|
0
|
$k3 = ord($k3) - ord('A'); |
|
163
|
|
|
|
|
|
|
} |
|
164
|
|
|
|
|
|
|
|
|
165
|
415442
|
100
|
|
|
|
757549
|
if ($k0 > 0) { |
|
166
|
117384
|
100
|
|
|
|
500452
|
redo WORD if $$lang{'quads'}[$k0][$k1][$k2][$k3] < $minfreq; |
|
167
|
20032
|
|
|
|
|
32756
|
$sum += $$lang{'quads'}[$k0][$k1][$k2][$k3]; |
|
168
|
|
|
|
|
|
|
} |
|
169
|
|
|
|
|
|
|
|
|
170
|
318090
|
|
|
|
|
350464
|
$k0 = $k1; |
|
171
|
318090
|
|
|
|
|
332119
|
$k1 = $k2; |
|
172
|
318090
|
|
|
|
|
426363
|
$k2 = $k3; |
|
173
|
|
|
|
|
|
|
} |
|
174
|
12
|
100
|
|
|
|
62
|
redo if $sum/length($stripped) < $avgfreq; |
|
175
|
10
|
50
|
|
|
|
24
|
redo if (restrict($stripped,$language)); |
|
176
|
10
|
|
|
|
|
70
|
return $randword; |
|
177
|
|
|
|
|
|
|
} |
|
178
|
|
|
|
|
|
|
} |
|
179
|
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
sub word3($$;$$$$$) |
|
181
|
|
|
|
|
|
|
{ |
|
182
|
2
|
|
50
|
2
|
0
|
796
|
my $language = splice(@_,2,1) || ''; |
|
183
|
2
|
|
|
|
|
4
|
$language =~ s/[^a-zA-Z_]//g; |
|
184
|
2
|
|
33
|
|
|
11
|
$language ||= $default_language; |
|
185
|
2
|
|
|
|
|
200
|
eval "require Crypt::GeneratePassword::$language"; |
|
186
|
2
|
|
|
|
|
19
|
my $lang = $languages{$language}; |
|
187
|
2
|
50
|
|
|
|
23
|
die "language '${language}' not found" if !$lang; |
|
188
|
|
|
|
|
|
|
|
|
189
|
2
|
|
|
|
|
9
|
my ($minlen, $maxlen, $numbers, $capitals, $minfreq, $avgfreq) = map { int($_) } @_; |
|
|
4
|
|
|
|
|
16
|
|
|
190
|
2
|
|
50
|
|
|
18
|
$minfreq ||= 0.01; |
|
191
|
2
|
|
50
|
|
|
13
|
$avgfreq ||= 0.05; |
|
192
|
2
|
|
50
|
|
|
15
|
$minfreq = int($$lang{'maxtri'}*$minfreq) || 1; |
|
193
|
2
|
|
|
|
|
6
|
$avgfreq = int($$lang{'maxtri'}*$avgfreq); |
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
WORD: { |
|
196
|
2
|
50
|
|
|
|
6
|
my $randword = chars($minlen,$maxlen,$set[$numbers?1:0][$capitals?1:0],($numbers?($signs,$numbers):()),($capitals?($caps,$capitals):())); |
|
|
1219640
|
50
|
|
|
|
4696659
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
197
|
1219640
|
|
|
|
|
1584367
|
$total++; |
|
198
|
1219640
|
|
|
|
|
1741480
|
my $stripped = lc($randword); |
|
199
|
1219640
|
|
|
|
|
3614818
|
$stripped =~ s/[\Q$signs\E]//g; |
|
200
|
1219640
|
50
|
|
|
|
2407729
|
redo WORD if length($stripped) == 0; |
|
201
|
|
|
|
|
|
|
|
|
202
|
1219640
|
|
|
|
|
1467926
|
my $sum = 0; |
|
203
|
1219640
|
|
|
|
|
1452373
|
my $k1 = -1; |
|
204
|
1219640
|
|
|
|
|
1305793
|
my $k2 = -1; |
|
205
|
1219640
|
|
|
|
|
1400081
|
my $k3 = -1; |
|
206
|
|
|
|
|
|
|
|
|
207
|
1219640
|
|
|
|
|
4259742
|
foreach my $char (split(//,$stripped)) { |
|
208
|
3992596
|
|
|
|
|
4936682
|
$k3 = $char; |
|
209
|
3992596
|
50
|
|
|
|
6705111
|
if ($k3 gt 'Z') { |
|
210
|
3992596
|
|
|
|
|
5083785
|
$k3 = ord($k3) - ord('a'); |
|
211
|
|
|
|
|
|
|
} else { |
|
212
|
0
|
|
|
|
|
0
|
$k3 = ord($k3) - ord('A'); |
|
213
|
|
|
|
|
|
|
} |
|
214
|
|
|
|
|
|
|
|
|
215
|
3992596
|
100
|
|
|
|
7609022
|
if ($k1 > 0) { |
|
216
|
1477377
|
100
|
|
|
|
5848597
|
redo WORD if $$lang{'tris'}[$k1][$k2][$k3] < $minfreq; |
|
217
|
257741
|
|
|
|
|
407385
|
$sum += $$lang{'tris'}[$k1][$k2][$k3]; |
|
218
|
|
|
|
|
|
|
} |
|
219
|
|
|
|
|
|
|
|
|
220
|
2772960
|
|
|
|
|
3036581
|
$k1 = $k2; |
|
221
|
2772960
|
|
|
|
|
3768000
|
$k2 = $k3; |
|
222
|
|
|
|
|
|
|
} |
|
223
|
4
|
100
|
|
|
|
44
|
redo if $sum/length($stripped) < $avgfreq; |
|
224
|
2
|
50
|
|
|
|
11
|
redo if (restrict($stripped,$language)); |
|
225
|
2
|
|
|
|
|
23
|
return $randword; |
|
226
|
|
|
|
|
|
|
} |
|
227
|
|
|
|
|
|
|
} |
|
228
|
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
=head2 analyze |
|
230
|
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
$ratio = analyze($count,@word_params); |
|
232
|
|
|
|
|
|
|
$ratio = analyze3($count,@word_params); |
|
233
|
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
Returns a statistical(!) security ratio to measure password |
|
235
|
|
|
|
|
|
|
quality. $ratio is the ratio of passwords chosen among all |
|
236
|
|
|
|
|
|
|
possible ones, e.g. a ratio of 0.0149 means 1.49% of the |
|
237
|
|
|
|
|
|
|
theoretical password space was actually considered a |
|
238
|
|
|
|
|
|
|
pronounceable password. Since this analysis is only |
|
239
|
|
|
|
|
|
|
statistical, it proves absolutely nothing if you are deeply |
|
240
|
|
|
|
|
|
|
concerned about security - but in that case you should use |
|
241
|
|
|
|
|
|
|
chars(), not word() anyways. In reality, it says a lot |
|
242
|
|
|
|
|
|
|
about your chosen parameters if you use large values for |
|
243
|
|
|
|
|
|
|
$count. |
|
244
|
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
=cut |
|
246
|
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
sub analyze($@) { |
|
248
|
0
|
|
|
0
|
1
|
0
|
my $count = shift; |
|
249
|
0
|
|
|
|
|
0
|
$total = 0; |
|
250
|
0
|
|
|
|
|
0
|
for (1..$count) { |
|
251
|
0
|
|
|
|
|
0
|
my $word = &word(@_); |
|
252
|
|
|
|
|
|
|
} |
|
253
|
0
|
|
|
|
|
0
|
return $count/$total; |
|
254
|
|
|
|
|
|
|
} |
|
255
|
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
sub analyze3($@) { |
|
257
|
0
|
|
|
0
|
0
|
0
|
my $count = shift; |
|
258
|
0
|
|
|
|
|
0
|
$total = 0; |
|
259
|
0
|
|
|
|
|
0
|
for (1..$count) { |
|
260
|
0
|
|
|
|
|
0
|
my $word = &word3(@_); |
|
261
|
|
|
|
|
|
|
} |
|
262
|
0
|
|
|
|
|
0
|
return $count/$total; |
|
263
|
|
|
|
|
|
|
} |
|
264
|
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
=head2 generate_language |
|
266
|
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
$language_description = generate_language($wordlist); |
|
268
|
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
Generates a language description which can be saved in a file and/or |
|
270
|
|
|
|
|
|
|
loaded with load_language. $wordlist can be a string containing |
|
271
|
|
|
|
|
|
|
whitespace separated words, an array ref containing one word per |
|
272
|
|
|
|
|
|
|
element or a file handle or name to read words from, one word per line7. |
|
273
|
|
|
|
|
|
|
Alternatively, you may pass an array directly, not as reference. |
|
274
|
|
|
|
|
|
|
A language description is about 1MB in size. |
|
275
|
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
If you generate a general-purpose language description for a |
|
277
|
|
|
|
|
|
|
language not yet built-in, feel free to contribute it for inclusion |
|
278
|
|
|
|
|
|
|
into this package. |
|
279
|
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
=cut |
|
281
|
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
sub generate_language($@) { |
|
283
|
0
|
|
|
0
|
1
|
0
|
my ($wordlist) = @_; |
|
284
|
0
|
0
|
|
|
|
0
|
if (@_ > 1) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
285
|
0
|
|
|
|
|
0
|
$wordlist = \@_; |
|
286
|
|
|
|
|
|
|
} elsif (!ref($wordlist)) { |
|
287
|
0
|
|
|
|
|
0
|
$wordlist = [ split(/\s+/,$wordlist) ]; |
|
288
|
0
|
0
|
|
|
|
0
|
if (@$wordlist == 1) { |
|
289
|
0
|
|
|
|
|
0
|
local *FH; |
|
290
|
0
|
|
|
|
|
0
|
open(FH,'<'.$$wordlist[0]); |
|
291
|
0
|
|
|
|
|
0
|
$wordlist = [ ]; |
|
292
|
0
|
|
|
|
|
0
|
close(FH); |
|
293
|
|
|
|
|
|
|
} |
|
294
|
|
|
|
|
|
|
} elsif (ref($wordlist) ne 'ARRAY') { |
|
295
|
0
|
|
|
|
|
0
|
$wordlist = [ <$wordlist> ]; |
|
296
|
|
|
|
|
|
|
} |
|
297
|
|
|
|
|
|
|
|
|
298
|
0
|
|
|
|
|
0
|
my @quads = map { [ map { [ map { [ map { 0 } 1..26 ] } 1..26 ] } 1..26 ] } 1..26; |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
299
|
0
|
|
|
|
|
0
|
my @tris = map { [ map { [ map { 0 } 1..26 ] } 1..26 ] } 1..26; |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
300
|
0
|
|
|
|
|
0
|
my $sigmaquad = 0; |
|
301
|
0
|
|
|
|
|
0
|
my $maxquad = 0; |
|
302
|
0
|
|
|
|
|
0
|
my $sigmatri = 0; |
|
303
|
0
|
|
|
|
|
0
|
my $maxtri = 0; |
|
304
|
|
|
|
|
|
|
|
|
305
|
0
|
|
|
|
|
0
|
foreach my $word (@$wordlist) { |
|
306
|
0
|
|
|
|
|
0
|
my $k0 = -1; |
|
307
|
0
|
|
|
|
|
0
|
my $k1 = -1; |
|
308
|
0
|
|
|
|
|
0
|
my $k2 = -1; |
|
309
|
0
|
|
|
|
|
0
|
my $k3 = -1; |
|
310
|
|
|
|
|
|
|
|
|
311
|
0
|
|
|
|
|
0
|
foreach my $char (split(//,$word)) { |
|
312
|
0
|
|
|
|
|
0
|
$k3 = $char; |
|
313
|
0
|
0
|
|
|
|
0
|
if ($k3 gt 'Z') { |
|
314
|
0
|
|
|
|
|
0
|
$k3 = ord($k3) - ord('a'); |
|
315
|
|
|
|
|
|
|
} else { |
|
316
|
0
|
|
|
|
|
0
|
$k3 = ord($k3) - ord('A'); |
|
317
|
|
|
|
|
|
|
} |
|
318
|
|
|
|
|
|
|
|
|
319
|
0
|
0
|
0
|
|
|
0
|
next unless ($k3 >= 0 && $k3 <= 25); |
|
320
|
|
|
|
|
|
|
|
|
321
|
0
|
0
|
|
|
|
0
|
if ($k0 >= 0) { |
|
322
|
0
|
|
|
|
|
0
|
$quads[$k0][$k1][$k2][$k3]++; |
|
323
|
0
|
|
|
|
|
0
|
$sigmaquad++; |
|
324
|
0
|
0
|
|
|
|
0
|
if ($quads[$k0][$k1][$k2][$k3] > $maxquad) { |
|
325
|
0
|
|
|
|
|
0
|
$maxquad = $quads[$k0][$k1][$k2][$k3]; |
|
326
|
|
|
|
|
|
|
} |
|
327
|
|
|
|
|
|
|
} |
|
328
|
|
|
|
|
|
|
|
|
329
|
0
|
0
|
|
|
|
0
|
if ($k1 >= 0) { |
|
330
|
0
|
|
|
|
|
0
|
$tris[$k1][$k2][$k3]++; |
|
331
|
0
|
|
|
|
|
0
|
$sigmatri++; |
|
332
|
0
|
0
|
|
|
|
0
|
if ($tris[$k1][$k2][$k3] > $maxtri) { |
|
333
|
0
|
|
|
|
|
0
|
$maxtri = $tris[$k1][$k2][$k3]; |
|
334
|
|
|
|
|
|
|
} |
|
335
|
|
|
|
|
|
|
} |
|
336
|
|
|
|
|
|
|
|
|
337
|
0
|
|
|
|
|
0
|
$k0 = $k1; |
|
338
|
0
|
|
|
|
|
0
|
$k1 = $k2; |
|
339
|
0
|
|
|
|
|
0
|
$k2 = $k3; |
|
340
|
|
|
|
|
|
|
} |
|
341
|
|
|
|
|
|
|
} |
|
342
|
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
{ |
|
344
|
0
|
|
|
|
|
0
|
require Data::Dumper; |
|
|
0
|
|
|
|
|
0
|
|
|
345
|
4
|
|
|
4
|
|
25
|
no warnings 'once'; |
|
|
4
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
1832
|
|
|
346
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Indent = 0; |
|
347
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Purity = 0; |
|
348
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Pad = ''; |
|
349
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Deepcopy = 1; |
|
350
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Terse = 1; |
|
351
|
|
|
|
|
|
|
|
|
352
|
0
|
|
|
|
|
0
|
my $res = Data::Dumper::Dumper( |
|
353
|
|
|
|
|
|
|
{ |
|
354
|
|
|
|
|
|
|
maxtri => $maxtri, |
|
355
|
|
|
|
|
|
|
sigmatri => $sigmatri, |
|
356
|
|
|
|
|
|
|
maxquad => $maxquad, |
|
357
|
|
|
|
|
|
|
sigmaquad => $sigmaquad, |
|
358
|
|
|
|
|
|
|
tris => \@tris, |
|
359
|
|
|
|
|
|
|
quads => \@quads, |
|
360
|
|
|
|
|
|
|
} |
|
361
|
|
|
|
|
|
|
); |
|
362
|
0
|
|
|
|
|
0
|
$res =~ s/[' ]//g; |
|
363
|
0
|
|
|
|
|
0
|
return $res; |
|
364
|
|
|
|
|
|
|
} |
|
365
|
|
|
|
|
|
|
} |
|
366
|
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
=head2 load_language |
|
368
|
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
load_language($language_description, $name [, $default]); |
|
370
|
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
Loads a language description which is then available in words(). |
|
372
|
|
|
|
|
|
|
$language_description is a string returned by generate_language, |
|
373
|
|
|
|
|
|
|
$name is a name of your choice which is used to select this |
|
374
|
|
|
|
|
|
|
language as the fifth parameter of words(). You should use the |
|
375
|
|
|
|
|
|
|
well-known ISO two letter language codes if possible, for best |
|
376
|
|
|
|
|
|
|
interoperability. |
|
377
|
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
If you specify $default with a true value, this language will |
|
379
|
|
|
|
|
|
|
be made global default language. If you give undef as |
|
380
|
|
|
|
|
|
|
$language_description, only the default language will be changed. |
|
381
|
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
=cut |
|
383
|
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
sub load_language($$;$) { |
|
385
|
0
|
|
|
0
|
1
|
0
|
my ($desc,$name,$default) = @_; |
|
386
|
0
|
0
|
|
|
|
0
|
$languages{$name} = eval $desc if $desc; |
|
387
|
0
|
0
|
|
|
|
0
|
$default_language = $name if $default; |
|
388
|
|
|
|
|
|
|
} |
|
389
|
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
=head2 random_number |
|
391
|
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
$number = random_number($limit); |
|
393
|
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
Returns a random integer between 0 (inclusive) and C<$limit> (exclusive). |
|
395
|
|
|
|
|
|
|
Change this to a function of your choice by doing something like this: |
|
396
|
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
sub my_rng ($) { |
|
398
|
|
|
|
|
|
|
... |
|
399
|
|
|
|
|
|
|
} |
|
400
|
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
{ |
|
402
|
|
|
|
|
|
|
# suppress warning about function being redefined |
|
403
|
|
|
|
|
|
|
no warnings 'redefine'; |
|
404
|
|
|
|
|
|
|
*Crypt::GeneratePassword::random_number = \&my_rng; |
|
405
|
|
|
|
|
|
|
} |
|
406
|
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
The default implementation uses perl's rand(), |
|
408
|
|
|
|
|
|
|
which might not be appropriate for some sites. |
|
409
|
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
=cut |
|
411
|
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
sub random_number($) { |
|
413
|
29601854
|
|
|
29601854
|
1
|
71748594
|
return int(rand()*$_[0]); |
|
414
|
|
|
|
|
|
|
} |
|
415
|
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
=head2 restrict |
|
417
|
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
$forbidden = restrict($word,$language); |
|
419
|
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
Filters undesirable words. Returns false if the $word is allowed |
|
421
|
|
|
|
|
|
|
in language $lang, false otherwise. Change this to a function of |
|
422
|
|
|
|
|
|
|
your choice by doing something like this: |
|
423
|
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
sub my_filter ($$) { |
|
425
|
|
|
|
|
|
|
... |
|
426
|
|
|
|
|
|
|
} |
|
427
|
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
{ |
|
429
|
|
|
|
|
|
|
no warnings 'redefine'; |
|
430
|
|
|
|
|
|
|
*Crypt::GeneratePassword::restrict = \&my_filter; |
|
431
|
|
|
|
|
|
|
} |
|
432
|
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
The default implementation scans for a few letter sequences that |
|
434
|
|
|
|
|
|
|
english or german people might find offending, mostly because of |
|
435
|
|
|
|
|
|
|
their sexual nature. You might want to hook up a regular password |
|
436
|
|
|
|
|
|
|
checker here, or a wordlist comparison. |
|
437
|
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
=cut |
|
439
|
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
sub restrict($$) { |
|
441
|
12
|
|
|
12
|
1
|
147
|
return ($_[0] =~ m/f.ck|ass|rsch|tit|cum|ack|asm|orn|eil|otz|oes/i); |
|
442
|
|
|
|
|
|
|
} |
|
443
|
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
445
|
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
L |
|
447
|
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
=head1 REPOSITORY |
|
449
|
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
L |
|
451
|
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
=head1 AUTHOR |
|
453
|
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
Copyright 2002 by Jörg Walter , |
|
455
|
|
|
|
|
|
|
inspired by ideas from Tom Van Vleck and Morris |
|
456
|
|
|
|
|
|
|
Gasser/FIPS-181. |
|
457
|
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
Now maintained by Neil Bowers Eneilb@cpan.orgE |
|
459
|
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
=head1 COPYRIGHT |
|
461
|
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
This perl module is free software; it may be redistributed and/or modified |
|
463
|
|
|
|
|
|
|
under the same terms as Perl itself. |
|
464
|
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
=cut |
|
467
|
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
1; |