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; |