File Coverage

blib/lib/Authen/DecHpwd.pm
Criterion Covered Total %
statement 205 209 98.0
branch 23 26 88.4
condition 7 9 77.7
subroutine 47 48 97.9
pod 3 3 100.0
total 285 295 96.6


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Authen::DecHpwd - DEC VMS password hashing
4              
5             =head1 SYNOPSIS
6              
7             use Authen::DecHpwd qw(
8             UAI_C_AD_II UAI_C_PURDY UAI_C_PURDY_V UAI_C_PURDY_S
9             lgi_hpwd
10             );
11              
12             $hash = lgi_hpwd("JRANDOM", "PASSWORD", UAI_C_PURDY_S, 1234);
13              
14             use Authen::DecHpwd qw(vms_username vms_password);
15              
16             $username = vms_username($username);
17             $password = vms_password($password);
18              
19             =head1 DESCRIPTION
20              
21             This module implements the C password hashing function
22             from VMS (also known as C), and some associated VMS username
23             and password handling functions.
24              
25             The password hashing function is implemented in XS, with a hideously
26             slow pure Perl backup version for systems that can't handle XS.
27              
28             =cut
29              
30             package Authen::DecHpwd;
31              
32 12     12   920467 { use 5.006; }
  12         51  
  12         799  
33 12     12   69 use warnings;
  12         23  
  12         412  
34 12     12   74 use strict;
  12         31  
  12         510  
35              
36 12     12   17943 use Digest::CRC 0.14 qw(crc32);
  12         925881  
  12         1486  
37              
38             our $VERSION = "2.006";
39              
40 12     12   7587 use parent "Exporter";
  12         1404  
  12         95  
41             our @EXPORT_OK = qw(
42             UAI_C_AD_II UAI_C_PURDY UAI_C_PURDY_V UAI_C_PURDY_S
43             lgi_hpwd
44             vms_username vms_password
45             );
46              
47             eval { local $SIG{__DIE__};
48             require XSLoader;
49             XSLoader::load(__PACKAGE__, $VERSION);
50             };
51              
52             =head1 FUNCTIONS
53              
54             =over
55              
56             =item UAI_C_AD_II
57              
58             =item UAI_C_PURDY
59              
60             =item UAI_C_PURDY_V
61              
62             =item UAI_C_PURDY_S
63              
64             These constants are used to identify the four password hashing algorithms
65             used by VMS. They are the C constants in VMS.
66              
67             C refers to a 32-bit CRC algorithm. The CRC polynomial used
68             is the IEEE CRC-32 polynomial, as used in Ethernet, and in this context
69             is known as "AUTODIN-II". The hash is merely the CRC of the password.
70              
71             C, C, and C refer to successive
72             refinements of an algorithm based on Purdy polynomials. All of these
73             algorithms use the salt and username parameters as salt, use the whole
74             password, and return an eight-byte (64-bit) hash. The main part
75             of the algorithm, the Purdy polynomial, is identical in all three.
76             They differ in the pre-hashing, particularly in the treatment of the
77             username parameter.
78              
79             In C the username is truncated or space-padded to 12 characters
80             before being hashed in. C accepts a variable-length username.
81             C accepts a variable-length username and also includes the
82             password length in the hash. C also does some extra bit
83             rotations when hashing in the username and password strings, in order
84             to avoid aliasing.
85              
86             =cut
87              
88 12     12   1786 use constant UAI_C_AD_II => 0;
  12         73  
  12         1020  
89 12     12   121 use constant UAI_C_PURDY => 1;
  12         27  
  12         915  
90 12     12   67 use constant UAI_C_PURDY_V => 2;
  12         24  
  12         638  
91 12     12   60 use constant UAI_C_PURDY_S => 3;
  12         28  
  12         4724  
92              
93             =item lgi_hpwd(USERNAME, PASSWORD, ALGORITHM, SALT)
94              
95             This is the C function from VMS (also known as
96             C), but with the parameters in a different order. It hashes
97             the PASSWORD string in a manner determined by the other parameters,
98             and returns the hash as a string of bytes.
99              
100             ALGORITHM determines which hashing algorithm will be used. It must
101             be the value of one of the algorithm constants supplied by this module
102             (see above).
103              
104             SALT must be an integer in the range [0, 2^16). It modifies the hashing
105             so that the same password does not always produce the same hash.
106              
107             USERNAME is a string that is used as more salt. In VMS it is the username
108             of the account to which the password controls access.
109              
110             VMS usernames and passwords are constrained in character set and
111             length, and are case-insensitive. This function does not enforce
112             these restrictions, nor perform canonicalisation. If restrictions
113             and canonicalisation are desired then they must be applied separately.
114             The functions C and C described below may
115             be useful.
116              
117             =cut
118              
119 5 100 100 5 1 29 unless(defined &lgi_hpwd) { { local $SIG{__DIE__}; eval q{
  5 100 33 5   9  
  5 100 100 5   209  
  5 100   5   26  
  5 50   5   627  
  5 50   5   261  
  5 50   5   568223  
  5 100   5   55385  
  5 100   5   962  
  5 100   5   1036  
  5 100   5   1268  
  5     5   2368  
  5     5   34  
  5     5   12  
  5     5   391  
  5     5   26  
  5     5   12  
  5     5   295  
  5     5   27  
  5     2400   10  
  5     302400   439  
  5     258000   24  
  5     3600   8  
  5     100800   231  
  5     1200   22  
  5     50400   8  
  5     1200   194  
  5     1206   21  
  5     2412   8  
  5     1600   228  
  5     1600   22  
  5     1819304   7  
  5     0   310  
  5     2412   24  
  5     604800   8  
  5     33390   256  
  5     1602   23  
  5         8  
  5         207  
  5         22  
  5         7  
  5         268  
  5         23  
  5         9  
  5         245  
  5         22  
  5         7  
  5         260  
  5         23  
  5         6  
  5         248  
  5         32  
  5         6  
  5         253  
  5         23  
  5         8  
  5         9091  
  2400         5177  
  2400         10756  
  33390         144122  
  33390         137042  
  33390         1892212  
  1206         3357  
  302400         498942  
  302400         787917  
  302400         16976376  
  302400         15346373  
  302400         921445  
  302400         16527200  
  302400         20347659  
  302400         19884466  
  302400         16421073  
  302400         20136013  
  302400         20324045  
  258000         469412  
  258000         672506  
  258000         492586  
  258000         313786  
  258000         494882  
  258000         17207675  
  258000         17409969  
  46852         210914  
  46852         3023237  
  258000         4207674  
  3600         9411  
  3600         7327  
  3600         6357  
  3600         6056  
  3600         4964  
  3600         5638  
  3600         11045  
  37200         105313  
  15600         64120  
  12000         41130  
  3600         7920  
  3600         5147  
  15600         65306  
  3600         11650  
  12000         22932  
  33600         69235  
  33600         75140  
  33600         97419  
  0         0  
  100800         193860  
  100800         251041  
  100800         184516  
  100800         174905  
  100800         204683  
  100800         238746  
  100800         249657  
  1200         4152  
  1200         6833  
  0         0  
  50400         115413  
  50400         150698  
  50400         122971  
  50400         86987  
  50400         73539  
  50400         80720  
  50400         70952  
  50400         103310  
  50400         126498  
  50400         117248  
  50400         142752  
  50400         161679  
  50400         138828  
  50400         143607  
  50400         132811  
  50400         182372  
  1200         3305  
  1200         2064  
  1200         2057  
  1200         4554  
  1200         3994  
  1200         5153  
  1200         3505  
  1200         9720  
  1200         5490  
  1200         3580  
  1200         4798  
  1200         3337  
  1200         3440  
  1200         4341  
  1200         9477  
  1200         4422  
  1206         3488  
  1206         110094  
  2412         8829  
  1600         10194  
  1600         5066  
  1819304         58343038  
  1819304         6060226  
  1819304         103811827  
  0         0  
  2412         2833  
  2412         5601  
  2412         8445  
  604800         801184  
  604800         1477148  
  33390         121312  
  1602         24727343  
  1602         6919  
  0         0  
  1602         7001  
  1602         96085  
  1602         4494  
  1602         16753  
  1600         5410  
  400         1387  
  1200         3176  
  1200         2268  
  1200         5303  
  400         570  
  400         911  
  400         2501  
  1200         29184  
  1200         8406  
  1200         69320  
  1200         3899  
  1200         12143  
120              
121             use warnings;
122             use strict;
123              
124             use Data::Integer 0.003 qw(
125             natint_bits
126             uint_shl uint_shr uint_rol
127             uint_and uint_or
128             uint_madd uint_cadd
129             );
130             use Scalar::String 0.000 qw(sclstr_is_downgraded sclstr_downgraded);
131              
132             my $u32_mask = 0xffffffff;
133              
134             sub _u32_shl($$) {
135             if(natint_bits == 32) {
136             return &uint_shl;
137             } else {
138             return uint_and(&uint_shl, $u32_mask);
139             }
140             }
141              
142             *_u32_shr = \&uint_shr;
143              
144             *_u32_and = \&uint_and;
145              
146             sub _u32_rol($$) {
147             if(natint_bits == 32) {
148             return &uint_rol;
149             } else {
150             return $_[0] if $_[1] == 0;
151             return uint_and(uint_or(uint_shl($_[0], $_[1]),
152             uint_shr($_[0], 32-$_[1])),
153             $u32_mask);
154             }
155             }
156              
157             sub _u32_madd($$) { uint_and(&uint_madd, $u32_mask) }
158              
159             sub _u32_cadd($$$) {
160             if(natint_bits == 32) {
161             return &uint_cadd;
162             } else {
163             my(undef, $val) = uint_cadd($_[0], $_[1], $_[2]);
164             return (uint_and(uint_shr($val, 32), 1),
165             uint_and($val, $u32_mask));
166             }
167             }
168              
169             my $u16_mask = 0xffff;
170              
171             sub _u16_madd($$) { uint_and(&uint_madd, $u16_mask) }
172              
173             my $u8_mask = 0xff;
174              
175             sub _u8_madd($$) { uint_and(&uint_madd, $u8_mask) }
176              
177             sub _addUnalignedWord($$) {
178             $_[0] = pack("v", _u16_madd(unpack("v", $_[0]), $_[1]));
179             }
180              
181             use constant _PURDY_USERNAME_LENGTH => 12;
182              
183             use constant _A => 59;
184             use constant _DWORD_MAX => 0xffffffff;
185             use constant _P_D_LOW => _DWORD_MAX - _A + 1;
186             use constant _P_D_HIGH => _DWORD_MAX;
187              
188             use constant _N0 => 0xfffffd;
189             use constant _N1 => 0xffffc1;
190             use constant _Na => 448;
191             use constant _Nb => 37449;
192              
193             use constant _MASK => 7;
194              
195             use constant _C1 => pack("VV", 0xffffffad, 0xffffffff);
196             use constant _C2 => pack("VV", 0xffffff4d, 0xffffffff);
197             use constant _C3 => pack("VV", 0xfffffeff, 0xffffffff);
198             use constant _C4 => pack("VV", 0xfffffebd, 0xffffffff);
199             use constant _C5 => pack("VV", 0xfffffe95, 0xffffffff);
200              
201             sub _PQMOD_R0($) {
202             my($low, $high) = unpack("VV", $_[0]);
203             if($high == _P_D_HIGH && $low >= _P_D_LOW) {
204             $_[0] = pack("VV", _u32_madd($low, _A), 0);
205             }
206             }
207              
208             sub _ROL1($) { $_[0] = pack("V", _u32_rol(unpack("V", $_[0]), 1)); }
209              
210             sub _QROL1($) {
211             _ROL1(substr($_[0], 0, 4));
212             _ROL1(substr($_[0], 4, 4));
213             }
214              
215             sub _EMULQ($$$) {
216             my($a, $b, undef) = @_;
217             my $hi = _u32_shr($a, 16) * _u32_shr($b, 16);
218             my $lo = _u32_and($a, 0xffff) * _u32_and($b, 0xffff);
219             my $carry;
220             my $p = _u32_shr($a, 16) * _u32_and($b, 0xffff);
221             ($carry, $lo) = _u32_cadd($lo, _u32_shl($p, 16), 0);
222             ($carry, $hi) = _u32_cadd($hi, _u32_shr($p, 16), $carry);
223             $p = _u32_and($a, 0xffff) * _u32_shr($b, 16);
224             ($carry, $lo) = _u32_cadd($lo, _u32_shl($p, 16), 0);
225             ($carry, $hi) = _u32_cadd($hi, _u32_shr($p, 16), $carry);
226             $_[2] = pack("VV", $lo, $hi);
227             }
228              
229             sub _PQADD_R0($$$) {
230             my($u, $y, undef) = @_;
231             my($ulo, $uhi) = unpack("VV", $u);
232             my($ylo, $yhi) = unpack("VV", $y);
233             my($carry, $rlo, $rhi);
234             ($carry, $rlo) = _u32_cadd($ulo, $ylo, 0);
235             ($carry, $rhi) = _u32_cadd($uhi, $yhi, $carry);
236             while($carry) {
237             ($carry, $rlo) = _u32_cadd($rlo, _A, 0);
238             ($carry, $rhi) = _u32_cadd($rhi, 0, $carry);
239             }
240             $_[2] = pack("VV", $rlo, $rhi);
241             }
242              
243             sub _COLLAPSE_R2($$$) {
244             my($s, undef, $isPurdyS) = @_;
245             for(my $p = length($s); $p != 0; $p--) {
246             my $pp = $p & _MASK;
247             substr($_[1], $pp, 1) = pack("C",
248             _u8_madd(unpack("C", substr($_[1], $pp, 1)),
249             unpack("C", substr($s, -$p, 1))));
250             if($isPurdyS && $pp == _MASK) { _QROL1($_[1]); }
251             }
252             }
253              
254             sub _PQLSH_R0($$) {
255             my($u, undef) = @_;
256             my($ulo, $uhi) = unpack("VV", $u);
257             my $stack = pack("VV", 0, 0);
258             my $x = pack("VV", 0, 0);
259             _EMULQ($uhi, _A, $stack);
260             $x = pack("VV", 0, $ulo);
261             _PQADD_R0($x, $stack, $_[1]);
262             }
263              
264             sub _PQMUL_R2($$$) {
265             my($u, $y, undef) = @_;
266             my($ulo, $uhi) = unpack("VV", $u);
267             my($ylo, $yhi) = unpack("VV", $y);
268             my $stack = pack("VV", 0, 0);
269             my $part1 = pack("VV", 0, 0);
270             my $part2 = pack("VV", 0, 0);
271             my $part3 = pack("VV", 0, 0);
272             _EMULQ($uhi, $yhi, $stack);
273             _PQLSH_R0($stack, $part1);
274             _EMULQ($uhi, $ylo, $stack);
275             _EMULQ($ulo, $yhi, $part2);
276             _PQADD_R0($stack, $part2, $part3);
277             _PQADD_R0($part1, $part3, $stack);
278             _PQLSH_R0($stack, $part1);
279             _EMULQ($ulo, $ylo, $stack);
280             _PQADD_R0($part1, $stack, $_[2]);
281             }
282              
283             sub _PQEXP_R3($$$) {
284             my($u, $n, undef) = @_;
285             my $y = pack("VV", 0, 0);
286             my $z = pack("VV", 0, 0);
287             my $z1 = pack("VV", 0, 0);
288             my $yok = 0;
289             $z = $u;
290             while($n != 0) {
291             if($n & 1) {
292             if($yok) {
293             _PQMUL_R2($y, $z, $_[2]);
294             } else {
295             $_[2] = $z;
296             $yok = 1;
297             }
298             if($n == 1) { return; }
299             $y = $_[2];
300             }
301             $n >>= 1;
302             $z1 = $z;
303             _PQMUL_R2($z1, $z1, $z);
304             }
305             $_[2] = pack("VV", 1, 0);
306             }
307              
308             sub _Purdy($) {
309             my $t1 = pack("VV", 0, 0);
310             my $t2 = pack("VV", 0, 0);
311             my $t3 = pack("VV", 0, 0);
312              
313             _PQEXP_R3($_[0], _Na, $t1);
314             _PQEXP_R3($t1, _Nb, $t2);
315             _PQEXP_R3($_[0], (_N0 - _N1), $t1);
316             _PQADD_R0($t1, _C1, $t3);
317             _PQMUL_R2($t2, $t3, $t1);
318              
319             _PQMUL_R2($_[0], _C2, $t2);
320             _PQADD_R0($t2, _C3, $t3);
321             _PQMUL_R2($_[0], $t3, $t2);
322             _PQADD_R0($t2, _C4, $t3);
323              
324             _PQADD_R0($t1, $t3, $t2);
325             _PQMUL_R2($_[0], $t2, $t1);
326             _PQADD_R0($t1, _C5, $_[0]);
327              
328             _PQMOD_R0($_[0]);
329             }
330              
331             sub lgi_hpwd($$$$) {
332             my($username, $password, $alg, $salt) = @_;
333             if($alg > UAI_C_PURDY_S) {
334             die "algorithm value $alg is not recognised";
335             }
336             $salt = uint_and($salt, 0xffff);
337             # This string downgrading is necessary for correct behaviour on
338             # perl 5.6 and 5.8. It is not necessary on 5.10, but will still
339             # slightly improve performance.
340             $username = sclstr_downgraded($username, 1);
341             $password = sclstr_downgraded($password, 1);
342             die "input must contain only octets"
343             unless sclstr_is_downgraded($username) &&
344             sclstr_is_downgraded($password);
345             if($alg == UAI_C_AD_II) {
346             return pack("VV", Digest::CRC::crc32($password)^0xffffffff, 0);
347             }
348             my $isPurdyS = $alg == UAI_C_PURDY_S;
349             my $output = pack("VV", 0, 0);
350             if($alg == UAI_C_PURDY) {
351             $username .= " " x 12;
352             $username = substr($username, 0, _PURDY_USERNAME_LENGTH);
353             } elsif($alg == UAI_C_PURDY_S) {
354             _addUnalignedWord(substr($output, 0, 2), length($password));
355             }
356             _COLLAPSE_R2($password, $output, $isPurdyS);
357             _addUnalignedWord(substr($output, 3, 2), $salt);
358             _COLLAPSE_R2($username, $output, $isPurdyS);
359             _Purdy($output);
360             return $output;
361             }
362              
363             1;
364              
365             }; } die $@ if $@ ne "" }
366              
367             =item vms_username(USERNAME)
368              
369             Checks whether the USERNAME string matches VMS username syntax, and
370             canonicalises it. VMS username syntax is 1 to 31 characters from
371             case-insensitive alphanumerics, "B<_>", and "B<$>". If the string has
372             correct username syntax then the username is returned in canonical form
373             (uppercase). If the string is not a username then C is returned.
374              
375             =cut
376              
377             sub vms_username($) {
378 15 100   15 1 648 return $_[0] =~ /\A[_\$0-9A-Za-z]{1,31}\z/ ? uc("$_[0]") : undef;
379             }
380              
381             =item vms_password(PASSWORD)
382              
383             Checks whether the PASSWORD string is an acceptable VMS password,
384             and canonicalises it. VMS password syntax is 1 to 32 characters from
385             case-insensitive alphanumerics, "B<_>", and "B<$>". If the string is
386             an acceptable password then the password is returned in canonical form
387             (uppercase). If the string is not an acceptable password then C
388             is returned.
389              
390             =cut
391              
392             sub vms_password($) {
393 16 100   16 1 180 return $_[0] =~ /\A[_\$0-9A-Za-z]{1,32}\z/ ? uc("$_[0]") : undef;
394             }
395              
396             =back
397              
398             =head1 SEE ALSO
399              
400             L
401              
402             =head1 AUTHOR
403              
404             The original C implementation of C was written by Shawn Clifford.
405             The code has since been developed by Davide Casale, Mario Ambrogetti,
406             Terence Lee, Jean-loup Gailly, Solar Designer, and Andrew Main (Zefram).
407              
408             Mike McCauley created the first version of
409             C, establishing the Perl interface. This was based on
410             Shawn Clifford's code without the later developments.
411              
412             Andrew Main (Zefram) created a new C
413             based on the more developed C code presently used, and added ancillary
414             functions.
415              
416             =head1 COPYRIGHT
417              
418             Copyright (C) 2002 Jean-loup Gailly
419              
420             Based in part on code from John the Ripper, Copyright (C) 1996-2002
421             Solar Designer
422              
423             Copyright (C) 2006, 2007, 2009, 2010, 2011
424             Andrew Main (Zefram)
425              
426             =head1 LICENSE
427              
428             This module is free software; you can redistribute it and/or modify it
429             under the terms of the GNU General Public License as published by the
430             Free Software Foundation; either version 2 of the License, or (at your
431             option) any later version.
432              
433             =cut
434              
435             1;