File Coverage

blib/lib/Authen/DecHpwd.pm
Criterion Covered Total %
statement 204 208 98.0
branch 23 26 88.4
condition 7 9 77.7
subroutine 47 48 97.9
pod 3 3 100.0
total 284 294 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   259050 { use 5.006; }
  12         50  
33 12     12   78 use warnings;
  12         31  
  12         392  
34 12     12   75 use strict;
  12         37  
  12         452  
35              
36 12     12   7245 use Digest::CRC 0.14 qw(crc32);
  12         30826  
  12         1196  
37              
38             our $VERSION = "2.007";
39              
40 12     12   2127 use parent "Exporter";
  12         1088  
  12         84  
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   1450 use constant UAI_C_AD_II => 0;
  12         35  
  12         1029  
89 12     12   81 use constant UAI_C_PURDY => 1;
  12         34  
  12         565  
90 12     12   77 use constant UAI_C_PURDY_V => 2;
  12         29  
  12         617  
91 12     12   73 use constant UAI_C_PURDY_S => 3;
  12         35  
  12         3627  
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 41 unless(defined &lgi_hpwd) { { local $SIG{__DIE__}; eval q{
  5 100 33 5   17  
  5 100 100 5   233  
  5 100   5   47  
  5 50   5   14  
  5 50   5   218  
  5 50   5   3187  
  5 100   5   57831  
  5 100   5   875  
  5 100   5   767  
  5 100   5   1101  
  5     5   2147  
  5     5   47  
  5     5   13  
  5     5   447  
  5     5   37  
  5     5   15  
  5     5   269  
  5     5   33  
  5     2400   15  
  5     302400   313  
  5     258000   37  
  5     3600   15  
  5     100800   256  
  5     1200   143  
  5     50400   16  
  5     1200   231  
  5     1206   33  
  5     2412   13  
  5     1600   246  
  5     1600   33  
  5     1819304   13  
  5     0   302  
  5     2412   35  
  5     604800   16  
  5     33390   242  
  5     1602   32  
  5         13  
  5         244  
  5         32  
  5         12  
  5         341  
  5         39  
  5         15  
  5         292  
  5         34  
  5         13  
  5         279  
  5         33  
  5         13  
  5         277  
  5         36  
  5         13  
  5         290  
  5         34  
  5         13  
  5         7825  
  2400         7232  
  2400         9043  
  33390         163444  
  33390         115225  
  33390         1766442  
  1206         3408  
  302400         598353  
  302400         765387  
  302400         17090802  
  302400         14379850  
  302400         755337  
  302400         15744340  
  302400         20360026  
  302400         20393160  
  302400         15742207  
  302400         20458585  
  302400         20550382  
  258000         556908  
  258000         722208  
  258000         563311  
  258000         475519  
  258000         548424  
  258000         17445755  
  258000         17405811  
  46852         117263  
  46852         3168798  
  258000         4032232  
  3600         11739  
  3600         8597  
  3600         8724  
  3600         7276  
  3600         7400  
  3600         8724  
  3600         12199  
  37200         126522  
  15600         52580  
  12000         40445  
  3600         9470  
  3600         7710  
  15600         56217  
  3600         12283  
  12000         27317  
  33600         76199  
  33600         65292  
  33600         96139  
  0         0  
  100800         225103  
  100800         293025  
  100800         208507  
  100800         184883  
  100800         271916  
  100800         248624  
  100800         274636  
  1200         4799  
  1200         7237  
  0         0  
  50400         131874  
  50400         161576  
  50400         139190  
  50400         105942  
  50400         90408  
  50400         98232  
  50400         95107  
  50400         143717  
  50400         162812  
  50400         146266  
  50400         156539  
  50400         160625  
  50400         149954  
  50400         165858  
  50400         165962  
  50400         162322  
  1200         2864  
  1200         2507  
  1200         2440  
  1200         4747  
  1200         4664  
  1200         5751  
  1200         4737  
  1200         4930  
  1200         5104  
  1200         5301  
  1200         5147  
  1200         4568  
  1200         4198  
  1200         4840  
  1200         4667  
  1200         5444  
  1206         4350  
  1206         107108  
  2412         9208  
  1600         10211  
  1600         5001  
  1819304         54757888  
  1819304         4864090  
  1819304         102422619  
  0         0  
  2412         4357  
  2412         6101  
  2412         6897  
  604800         1089780  
  604800         1429651  
  33390         80920  
  1602         1014278  
  1602         14981  
  0         0  
  1602         6982  
  1602         75669  
  1602         5789  
  1602         14224  
  1600         6126  
  400         7783  
  1200         3208  
  1200         2913  
  1200         5038  
  400         1251  
  400         1284  
  400         2131  
  1200         27754  
  1200         6013  
  1200         66566  
  1200         4572  
  1200         10294  
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 132 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 108 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, 2017
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;