File Coverage

blib/lib/Crypt/Password/Util.pm
Criterion Covered Total %
statement 38 48 79.1
branch 6 14 42.8
condition 1 8 12.5
subroutine 9 9 100.0
pod 3 3 100.0
total 57 82 69.5


line stmt bran cond sub pod time code
1             package Crypt::Password::Util;
2              
3             our $DATE = '2016-01-21'; # DATE
4             our $VERSION = '0.17'; # VERSION
5              
6 1     1   926 use 5.010001;
  1         3  
7 1     1   5 use strict;
  1         1  
  1         20  
8 1     1   4 use warnings;
  1         2  
  1         22  
9              
10 1     1   4 use Exporter;
  1         2  
  1         340  
11             our @ISA = qw(Exporter);
12             our @EXPORT_OK = qw(crypt_type looks_like_crypt crypt);
13              
14             my $b64d = qr![A-Za-z0-9./]!;
15             my $hexd = qr![0-9a-f]!;
16              
17             our %CRYPT_TYPES = (
18             'MD5-CRYPT' => {
19             summary => 'A baroque passphrase scheme based on MD5, designed by Poul-Henning Kamp and originally implemented in FreeBSD',
20             re => qr/\A
21             (?P<header>\$ (?:apr)?1 \$)
22             (?P<salt>$b64d {0,8}) \$
23             (?P<hash>$b64d {22}) \z/x,
24             re_summary => '$1$ or $apr1$ header',
25             link => 'http://static.usenix.org/event/usenix99/provos/provos_html/node10.html',
26             },
27             CRYPT => {
28             summary => 'Traditional DES crypt',
29             re => qr/\A
30             (?P<salt>$b64d {2} | \$\$) # $$ is not accepted as salt, but we see crypts using those in the wild
31             (?P<hash>$b64d {11}) \z/x,
32             re_summary => '11 digit base64 characters',
33             link => 'http://perldoc.perl.org/functions/crypt.html',
34             },
35             'EXT-DES' => {
36             summary => 'Extended DES crypt',
37             re => qr/\A
38             (?P<salt>_ $b64d {8} )
39             (?P<hash>$b64d {11}) \z/x,
40             re_summary => 'underscore followed by 19 digit base64 characters',
41             link => 'https://en.wikipedia.org/wiki/Crypt_%28C%29#BSDi_extended_DES-based_scheme',
42             },
43             SSHA256 => {
44             summary => 'Salted SHA256, supported by glibc 2.7+',
45             re => qr/\A
46             (?P<header>\$ 5 \$)
47             (?P<salt> (?:rounds=[1-9][0-9]{3,8}\$)? $b64d {0,16}) \$
48             (?P<hash>$b64d {43}) \z/x,
49             re_summary => '$5$ header',
50             link => 'http://en.wikipedia.org/wiki/SHA-2',
51             },
52             SSHA512 => {
53             summary => 'Salted SHA512, supported by glibc 2.7+',
54             re => qr/\A
55             (?P<header>\$ 6 \$)
56             (?P<salt> (?:rounds=[1-9][0-9]{3,8}\$)? $b64d {0,16}) \$
57             (?P<hash>$b64d {86}) \z/x,
58             re_summary => '$6$ header',
59             link => 'http://en.wikipedia.org/wiki/SHA-2',
60             },
61             BCRYPT => {
62             summary => 'Passphrase scheme based on Blowfish, designed by Niels Provos and David Mazieres for OpenBSD',
63             re => qr/\A
64             (?P<header>\$ 2 [ayb]? \$)
65             (?P<cost>\d+) \$
66             (?P<salt>$b64d {22})
67             (?P<hash>$b64d {31}) \z/x,
68             re_summary => '$2$ or $2a$ header followed by cost, followed by 22 base64-digits salt and 31 digits hash',
69             link => 'https://www.usenix.org/legacy/event/usenix99/provos/provos_html/',
70             },
71             'PLAIN-MD5' => {
72             summary => 'Unsalted MD5 hash, popular with PHP web applications',
73             re => qr/\A (?P<hash>$hexd {32}) \z/x,
74             re_summary => '32 digits of hex characters',
75             link => 'http://en.wikipedia.org/wiki/MD5',
76             },
77             );
78              
79             sub crypt_type {
80 32     32 1 64 my $crypt = shift;
81 32         49 my $detail = shift;
82              
83 32         107 for my $type (keys %CRYPT_TYPES) {
84 161 100       775 if ($crypt =~ $CRYPT_TYPES{$type}{re}) {
85 29 100       60 if ($detail) {
86 1     1   761 my $res = {%+};
  1         407  
  1         424  
  9         131  
87 9         31 $res->{type} = $type;
88 9         65 return $res;
89             } else {
90 20         232 return $type;
91             }
92             }
93             }
94 3         21 return undef;
95             }
96              
97 2     2 1 7 sub looks_like_crypt { !!crypt_type($_[0]) }
98              
99             sub _random_base64_chars {
100 7     7   10 state $dummy = do { require Bytes::Random::Secure };
  1         1105  
101              
102 7         18428 my $num_chars = shift;
103              
104 7         22 my $num_bytes = int($num_chars * 3/4) + 1;
105 7         22 my $res = substr(
106             Bytes::Random::Secure::random_bytes_base64($num_bytes), 0, $num_chars);
107 7         879 $res =~ s/\+/./g;
108             #say "D:random_base64_chars=<$res> ($num_chars)";
109 7         35 return $res;
110             }
111              
112             sub crypt {
113 7     7 1 721 my $pass = shift;
114 7         27 my ($salt, $crypt);
115              
116             # on OpenBSD, first try BCRYPT
117 7 50       27 if ($^O eq 'openbsd') {
118 0         0 $salt = sprintf('$2b$%02d$%s', 7, _random_base64_chars(22));
119 0         0 $crypt = CORE::crypt($pass, $salt);
120 0 0 0     0 return $crypt if 'BCRYPT' eq (crypt_type($crypt) // '');
121             } else {
122             # otherwise, try SSHA512
123 7         16 $salt = sprintf('$6$rounds=%d$%s', 15000, _random_base64_chars(16));
124 7         185290 $crypt = CORE::crypt($pass, $salt);
125 7 50 50     25 return $crypt if 'SSHA512' eq (crypt_type($crypt) // '');
126             }
127              
128             # next, try MD5-CRYPT
129 0           $salt = sprintf('$1$%s', _random_base64_chars(8));
130 0           $crypt = CORE::crypt($pass, $salt);
131 0 0 0       return $crypt if 'MD5-CRYPT' eq (crypt_type($crypt) // '');
132              
133             # fallback to CRYPT if failed
134 0           $salt = _random_base64_chars(2);
135 0           $crypt = CORE::crypt($pass, $salt);
136 0 0 0       return $crypt if 'CRYPT' eq (crypt_type($crypt) // '');
137              
138 0           die "Can't generate crypt (tried all methods)";
139             }
140              
141             1;
142             # ABSTRACT: Crypt password utilities
143              
144             __END__
145              
146             =pod
147              
148             =encoding UTF-8
149              
150             =head1 NAME
151              
152             Crypt::Password::Util - Crypt password utilities
153              
154             =head1 VERSION
155              
156             This document describes version 0.17 of Crypt::Password::Util (from Perl distribution Crypt-Password-Util), released on 2016-01-21.
157              
158             =head1 SYNOPSIS
159              
160             use Crypt::Password::Util qw(
161             crypt
162             looks_like_crypt
163             crypt_type
164             );
165              
166             Generating crypted password:
167              
168             say crypt('pass'); # automatically choose the appropriate type and salt
169              
170             Recognizing whether a string is a crypted password:
171              
172             # return yes/no
173             say looks_like_crypt('62F4a6/89.12z'); # 1
174             say looks_like_crypt('foo'); # 0
175              
176             # return the crypt type
177             say crypt_type('62F4a6/89.12z'); # CRYPT
178             say crypt_type('$1$$...'); # MD5-CRYPT
179             say crypt_type('$apr1$4DdvgCFk$...'); # MD5-CRYPT
180             say crypt_type('$5$4DdvgCFk$...'); # SSHA256
181             say crypt_type('$6$4DdvgCFk$...'); # SSHA512
182             say crypt_type('1a1dc91c907325c69271ddf0c944bc72'); # PLAIN-MD5
183             say crypt_type('$2a$08$TTSynMjJTrXiv3qEZFyM1.H9tjv71i57p2r63QEJe/2p0p/m1GIy2'); # BCRYPT
184             say crypt_type('foo'); # undef
185              
186             # return detailed information
187             my $res = crypt_type('$1$$oXYGukVGYa16SN.Pw5vNt/', 1);
188             # => {type=>'MD5-CRYPT', header=>'$1$', salt=>'', hash=>'oXYGukVGYa16SN.Pw5vNt/'}
189             $res = crypt_type('foo', 1);
190             # => undef
191              
192             =head1 DESCRIPTION
193              
194             Crypt::Password::Util provides routines to: 1) generate crypted password; 2)
195             recognition of whether a string is a crypted password or not, and its crypt
196             type.
197              
198             It recognizes several types of crypt methods:
199              
200             =over
201              
202             =item * BCRYPT
203              
204             Passphrase scheme based on Blowfish, designed by Niels Provos and David Mazieres for OpenBSD.
205              
206             Recognized by: $2$ or $2a$ header followed by cost, followed by 22 base64-digits salt and 31 digits hash.
207              
208             More info: L<https://www.usenix.org/legacy/event/usenix99/provos/provos_html/>
209              
210             =item * CRYPT
211              
212             Traditional DES crypt.
213              
214             Recognized by: 11 digit base64 characters.
215              
216             More info: L<http://perldoc.perl.org/functions/crypt.html>
217              
218             =item * EXT-DES
219              
220             Extended DES crypt.
221              
222             Recognized by: underscore followed by 19 digit base64 characters.
223              
224             More info: L<https://en.wikipedia.org/wiki/Crypt_%28C%29#BSDi_extended_DES-based_scheme>
225              
226             =item * MD5-CRYPT
227              
228             A baroque passphrase scheme based on MD5, designed by Poul-Henning Kamp and originally implemented in FreeBSD.
229              
230             Recognized by: $1$ or $apr1$ header.
231              
232             More info: L<http://static.usenix.org/event/usenix99/provos/provos_html/node10.html>
233              
234             =item * PLAIN-MD5
235              
236             Unsalted MD5 hash, popular with PHP web applications.
237              
238             Recognized by: 32 digits of hex characters.
239              
240             More info: L<http://en.wikipedia.org/wiki/MD5>
241              
242             =item * SSHA256
243              
244             Salted SHA256, supported by glibc 2.7+.
245              
246             Recognized by: $5$ header.
247              
248             More info: L<http://en.wikipedia.org/wiki/SHA-2>
249              
250             =item * SSHA512
251              
252             Salted SHA512, supported by glibc 2.7+.
253              
254             Recognized by: $6$ header.
255              
256             More info: L<http://en.wikipedia.org/wiki/SHA-2>
257              
258             =back
259              
260             =head1 FUNCTIONS
261              
262             =head2 looks_like_crypt($str) => bool
263              
264             Return true if C<$str> looks like a crypted password. If you want more
265             information instead of just a yes/no, use C<crypt_type()>.
266              
267             =head2 crypt_type($str[, $detail]) => str|hash
268              
269             Return crypt type, or undef if C<$str> does not look like a crypted password.
270             Currently known types:
271              
272             If C<$detail> is set to true, will return a hashref of information instead. This
273             include C<type>, as well as the parsed header, salt, etc.
274              
275             =head2 crypt($str) => str
276              
277             Try to create a "reasonably secure" crypt password with the support available
278             from the system's crypt().
279              
280             Will first try to create a cost-based crypt, using rounds value that will
281             approximately take ~10ms (on my PC computer, an Intel Core i5-2400 CPU, that is)
282             to create. This lets a server verify ~100 passwords per second, which should be
283             enough for many cases. On OpenBSD, will try BCRYPT with cost=7. On other
284             systems, will try SSHA512 with rounds=15000.
285              
286             If the above fails (unsupported by your crypt()), will fallback to MD5-CRYPT
287             (supported by NetBSD), then CRYPT. Will die if that also fails.
288              
289             =head1 HOMEPAGE
290              
291             Please visit the project's homepage at L<https://metacpan.org/release/Crypt-Password-Util>.
292              
293             =head1 SOURCE
294              
295             Source repository is at L<https://github.com/perlancar/perl-Crypt-Password-Util>.
296              
297             =head1 BUGS
298              
299             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Crypt-Password-Util>
300              
301             When submitting a bug or request, please include a test-file or a
302             patch to an existing test-file that illustrates the bug or desired
303             feature.
304              
305             =head1 SEE ALSO
306              
307             L<Authen::Passphrase> which recognizes more encodings (but currently not SSHA256
308             and SSHA512).
309              
310             L<Crypt::Bcrypt::Easy> to generate BCRYPT crypts on systems that do not natively
311             support it.
312              
313             L<Crypt::PasswdMD5> to generate MD5-CRYPT crypts on systems that do not natively
314             support it.
315              
316             L<Crypt::Password> which also provides a routine to compare a password with a
317             crypted password.
318              
319             =head1 AUTHOR
320              
321             perlancar <perlancar@cpan.org>
322              
323             =head1 COPYRIGHT AND LICENSE
324              
325             This software is copyright (c) 2016 by perlancar@cpan.org.
326              
327             This is free software; you can redistribute it and/or modify it under
328             the same terms as the Perl 5 programming language system itself.
329              
330             =cut