Skip to content

Commit

Permalink
Merge crypt_detail() as a detail option for crypt_type(), return deta…
Browse files Browse the repository at this point in the history
…iled information as a hash instead of text
  • Loading branch information
perlancar committed Jan 31, 2015
1 parent 9369fc1 commit 8408ace
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 58 deletions.
87 changes: 47 additions & 40 deletions lib/Crypt/Password/Util.pm
Expand Up @@ -9,67 +9,83 @@ use warnings;

use Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(crypt_type looks_like_crypt crypt crypt_detail);
our @EXPORT_OK = qw(crypt_type looks_like_crypt crypt);

my $b64d = qr![A-Za-z0-9./]!;
my $hexd = qr![0-9a-f]!;

our %CRYPT_TYPES = (
'MD5-CRYPT' => {
summary => 'A baroque passphrase scheme based on MD5, designed by Poul-Henning Kamp and originally implemented in FreeBSD',
re => qr/\A (\$ (?:apr)?1 \$) ($b64d {0,8}) \$ ($b64d {22}) \z/x,
re => qr/\A
(?P<header>\$ (?:apr)?1 \$)
(?P<salt>$b64d {0,8}) \$
(?P<hash>$b64d {22}) \z/x,
re_summary => '$1$ or $apr1$ header',
link => 'http://static.usenix.org/event/usenix99/provos/provos_html/node10.html',
},
CRYPT => {
summary => 'Traditional DES crypt',
re => qr/\A () (..) ($b64d {11}) \z/x,
re => qr/\A
(?P<salt>..)
(?P<hash>$b64d {11}) \z/x,
re_summary => '11 digit base64 characters',
link => 'http://perldoc.perl.org/functions/crypt.html',
},
SSHA256 => {
summary => 'Salted SHA256, supported by glibc 2.7+',
re => qr/\A (\$ 5 \$) ($b64d {0,16}) \$ ($b64d {43}) \z/x,
re => qr/\A
(?P<header>\$ 5 \$)
(?P<salt>$b64d {0,16}) \$
(?P<hash>$b64d {43}) \z/x,
re_summary => '$5$ header',
link => 'http://en.wikipedia.org/wiki/SHA-2',
},
SSHA512 => {
summary => 'Salted SHA512, supported by glibc 2.7+',
re => qr/\A (\$ 6 \$) ($b64d {0,16}) \$ ($b64d {86}) \z/x,
re => qr/\A
(?P<header>\$ 6 \$)
(?P<salt>$b64d {0,16}) \$
(?P<hash>$b64d {86}) \z/x,
re_summary => '$6$ header',
link => 'http://en.wikipedia.org/wiki/SHA-2',
},
BCRYPT => {
summary => 'Passphrase scheme based on Blowfish, designed by Niels Provos and David Mazieres for OpenBSD',
re => qr/\A (\$ 2a? \$ \d+) \$ ($b64d {22}) ($b64d {31}) \z/x,
re_summary => '$2$ or $2a$header followed by 22 base64-digits salt and 31 digits hash',
re => qr/\A
(?P<header>\$ 2a? \$)
(?P<cost>\d+) \$
(?P<salt>$b64d {22})
(?P<hash>$b64d {31}) \z/x,
re_summary => '$2$ or $2a$ header followed by cost, followed by 22 base64-digits salt and 31 digits hash',
link => 'https://www.usenix.org/legacy/event/usenix99/provos/provos_html/',
},
'PLAIN-MD5' => {
summary => 'Unsalted MD5 hash, popular with PHP web applications',
re => qr/\A () () ($hexd {32}) \z/x,
re => qr/\A (?P<hash>$hexd {32}) \z/x,
re_summary => '32 digits of hex characters',
link => 'http://en.wikipedia.org/wiki/MD5',
},
);

sub crypt_type {
my $crypt = shift;
my $detail = shift;

for my $type (keys %CRYPT_TYPES) {
return $type if $crypt =~ $CRYPT_TYPES{$type}{re};
if ($crypt =~ $CRYPT_TYPES{$type}{re}) {
if ($detail) {
my $res = {%+};
$res->{type} = $type;
return $res;
} else {
return $type;
}
}
}
return undef;
}

sub crypt_detail {
my $crypt = shift;
my $type = crypt_type($crypt);
return $type if !defined $type;

my ($header, $salt, $hash) = ($crypt =~ $CRYPT_TYPES{$type}{re});
return "Type: $type, Header: $header, Salt: $salt, Hash: $hash";
}

sub looks_like_crypt { !!crypt_type($_[0]) }

sub crypt {
Expand Down Expand Up @@ -109,20 +125,12 @@ sub crypt {
say crypt_type('$5$4DdvgCFk$...'); # SSHA256
say crypt_type('$6$4DdvgCFk$...'); # SSHA512
say crypt_type('1a1dc91c907325c69271ddf0c944bc72'); # PLAIN-MD5
say crypt_type('$2a$08$TTSynMjJTrXiv3qEZFyM1.H9tjv71i57p2r63QEJe/2p0p/m1GIy2'); # BCRYPT
say crypt_type('foo'); # undef
say crypt_detail('62F4a6/89.12z');
# Type: CRYPT, Header: , Salt: 62, Hash: F4a6/89.12z
say crypt_detail('$1$$oXYGukVGYa16SN.Pw5vNt/');
# Type: MD5-CRYPT, Header: $1$, Salt: , Hash: oXYGukVGYa16SN.Pw5vNt/
say crypt_detail('$5$123456789$'.("a" x 43));
# Type: SSHA256, Header: $5$, Salt: 123456789, Hash: a...
say crypt_detail('$6$12345678$'.("a" x 86));
# Type: SSHA512, Header: $6$, Salt: 12345678, Hash: a...
say crypt_detail('1a1dc91c907325c69271ddf0c944bc72');
# Type: PLAIN-MD5, Header: , Salt: , Hash: 1a1dc91c907325c69271ddf0c944bc72
say crypt_detail('foo');
# undef
# return detailed information
say crypt_type('$1$$oXYGukVGYa16SN.Pw5vNt/', 1);
# => {}
say looks_like_crypt('62F4a6/89.12z'); # 1
say looks_like_crypt('foo'); # 0
Expand All @@ -132,25 +140,24 @@ sub crypt {
=head1 DESCRIPTION
Crypt::Password::Util facilitates the generation and recognition of unix
passwords as found in /etc/shadow on Unix/Linux systems and /etc/master.passwd
on BSD systems. When using crypt(), it is possible several methods will be
attempted before returning a result. This is done to insure that your system
supports the selected hash type.
Crypt::Password::Util facilitates the generation and recognition of unix
passwords as found in /etc/shadow on Unix/Linux systems and /etc/master.passwd
on BSD systems. When using crypt(), it is possible several methods will be
attempted before returning a result. This is done to insure that your system
supports the selected hash type.
=head1 FUNCTIONS
=head2 crypt_type($str) => STR
=head2 crypt_type($str[, $detail]) => str|hash
Return crypt type, or undef if C<$str> does not look like a crypted password.
Currently known types:
# CODE: require Crypt::Password::Util; my $types = \%Crypt::Password::Util::CRYPT_TYPES; print "=over\n\n"; for my $type (sort keys %$types) { print "=item * $type\n\n$types->{$type}{summary}.\n\nRecognized by: $types->{$type}{re_summary}.\n\nMore info: L<$types->{$type}{link}>\n\n" } print "=back\n\n";
=head2 crypt_detail($str) => STR
Return crypt type, header, salt and hash values in a string, or undef if
C<$str> does not look like a crypted password.
If C<$detail> is set to true, will return a hashref of information instead. This
include C<type>, as well as the parsed header, salt, etc.
=head2 looks_like_crypt($str) => BOOL
Expand Down
28 changes: 10 additions & 18 deletions t/01-basics.t
Expand Up @@ -5,7 +5,7 @@ use strict;
use warnings;

use Config;
use Crypt::Password::Util qw(crypt_type looks_like_crypt crypt crypt_detail);
use Crypt::Password::Util qw(crypt_type looks_like_crypt crypt);
use Sort::Versions;
use Test::More 0.98;

Expand All @@ -19,23 +19,15 @@ is( crypt_type('1a1dc91c907325c69271ddf0c944bc72'), "PLAIN-MD5");
is( crypt_type('$2a$08$TTSynMjJTrXiv3qEZFyM1.H9tjv71i57p2r63QEJe/2p0p/m1GIy2'), "BCRYPT");
ok(!crypt_type('foo'));

is( crypt_detail('$$.Pw5vNt/...'), 'Type: CRYPT, Header: , Salt: $$, Hash: .Pw5vNt/...');
is( crypt_detail('$1$$oXYGukVGYa16SN.Pw5vNt/'),
'Type: MD5-CRYPT, Header: $1$, Salt: , Hash: oXYGukVGYa16SN.Pw5vNt/');
is( crypt_detail('$apr1$x$A8hldSzKARXWgJiwY6zTC.'),
'Type: MD5-CRYPT, Header: $apr1$, Salt: x, Hash: A8hldSzKARXWgJiwY6zTC.');
is( crypt_detail('$apr1$12345678$A8hldSzKARXWgJiwY6zTC.'),
'Type: MD5-CRYPT, Header: $apr1$, Salt: 12345678, Hash: A8hldSzKARXWgJiwY6zTC.');
is( crypt_detail('$5$123456789$'.("a" x 43)),
'Type: SSHA256, Header: $5$, Salt: 123456789, Hash: '.("a" x 43));
is( crypt_detail('$6$12345678$'.("a" x 86)),
'Type: SSHA512, Header: $6$, Salt: 12345678, Hash: '.("a" x 86));
is( crypt_detail('1a1dc91c907325c69271ddf0c944bc72'),
'Type: PLAIN-MD5, Header: , Salt: , Hash: 1a1dc91c907325c69271ddf0c944bc72');
is( crypt_detail('$2a$08$TTSynMjJTrXiv3qEZFyM1.H9tjv71i57p2r63QEJe/2p0p/m1GIy2'),
'Type: BCRYPT, Header: $2a$08, Salt: TTSynMjJTrXiv3qEZFyM1., '.
'Hash: H9tjv71i57p2r63QEJe/2p0p/m1GIy2');
ok(!crypt_detail('foo'));
is_deeply( crypt_type('$$.Pw5vNt/...', 1), {type=>"CRYPT", salt=>'$$', hash=>'.Pw5vNt/...'});
is_deeply( crypt_type('$1$$oXYGukVGYa16SN.Pw5vNt/', 1), {type=>"MD5-CRYPT", header=>'$1$', salt=>'', hash=>'oXYGukVGYa16SN.Pw5vNt/'});
is_deeply( crypt_type('$apr1$x$A8hldSzKARXWgJiwY6zTC.', 1), {type=>"MD5-CRYPT", header=>'$apr1$', salt=>'x', hash=>'A8hldSzKARXWgJiwY6zTC.'});
is_deeply( crypt_type('$apr1$12345678$A8hldSzKARXWgJiwY6zTC.', 1), {type=>"MD5-CRYPT", header=>'$apr1$', salt=>'12345678', hash=>'A8hldSzKARXWgJiwY6zTC.'});
is_deeply( crypt_type('$5$123456789$'.("a" x 43), 1), {type=>"SSHA256", header=>'$5$', salt=>'123456789', hash=>('a'x43)});
is_deeply( crypt_type('$6$12345678$'.("a" x 86), 1), {type=>"SSHA512", header=>'$6$', salt=>'12345678', hash=>('a'x86)});
is_deeply( crypt_type('1a1dc91c907325c69271ddf0c944bc72', 1), {type=>"PLAIN-MD5", hash=>'1a1dc91c907325c69271ddf0c944bc72'});
is_deeply( crypt_type('$2a$08$TTSynMjJTrXiv3qEZFyM1.H9tjv71i57p2r63QEJe/2p0p/m1GIy2', 1), {type=>"BCRYPT", header=>'$2a$', cost=>'08', salt=>'TTSynMjJTrXiv3qEZFyM1.', hash=>'H9tjv71i57p2r63QEJe/2p0p/m1GIy2'});
ok(!crypt_type('foo', 1));

ok( looks_like_crypt('$6$12345678$'.("a" x 86)));
ok(!looks_like_crypt('foo'));
Expand Down

0 comments on commit 8408ace

Please sign in to comment.