File Coverage

blib/lib/Crypt/Misc.pm
Criterion Covered Total %
statement 156 168 92.8
branch 48 82 58.5
condition 20 40 50.0
subroutine 33 33 100.0
pod 17 17 100.0
total 274 340 80.5


line stmt bran cond sub pod time code
1             package Crypt::Misc;
2              
3 15     15   138759 use strict;
  15         48  
  15         434  
4 15     15   73 use warnings;
  15         26  
  15         881  
5             our $VERSION = '0.079_007';
6              
7             require Exporter; our @ISA = qw(Exporter); ### use Exporter 5.57 'import';
8 15     15   90 use Carp 'croak';
  15         23  
  15         1871  
9             our %EXPORT_TAGS = ( all => [qw(encode_b64 decode_b64
10             encode_b64u decode_b64u
11             encode_b58b decode_b58b
12             encode_b58f decode_b58f
13             encode_b58r decode_b58r
14             encode_b58t decode_b58t
15             encode_b58s decode_b58s
16             encode_b32r decode_b32r
17             encode_b32b decode_b32b
18             encode_b32z decode_b32z
19             encode_b32c decode_b32c
20             pem_to_der der_to_pem
21             read_rawfile write_rawfile
22             slow_eq is_v4uuid random_v4uuid
23             increment_octets_be increment_octets_le
24             )] );
25             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
26             our @EXPORT = qw();
27              
28 15     15   113 use Carp 'carp';
  15         42  
  15         809  
29 15     15   1098 use CryptX;
  15         47  
  15         453  
30 15     15   1805 use Crypt::Digest 'digest_data';
  15         28  
  15         701  
31 15     15   6195 use Crypt::Mode::CBC;
  15         35  
  15         412  
32 15     15   5684 use Crypt::Mode::CFB;
  15         34  
  15         430  
33 15     15   5957 use Crypt::Mode::ECB;
  15         39  
  15         418  
34 15     15   5824 use Crypt::Mode::OFB;
  15         38  
  15         417  
35 15     15   85 use Crypt::Cipher;
  15         26  
  15         276  
36 15     15   5946 use Crypt::PRNG 'random_bytes';
  15         35  
  15         36738  
37              
38             sub _encode_b58 {
39 290     290   578 my ($bytes, $alphabet) = @_;
40              
41 290 50 33     1330 return '' if !defined $bytes || length($bytes) == 0;
42              
43             # handle leading zero-bytes
44 290         405 my $base58 = '';
45 290 100       932 if ($bytes =~ /^(\x00+)/) {
46 80         246 $base58 = ('0' x length($1));
47             }
48 290         5370 $base58 .= _bin_to_radix($bytes, 58);
49              
50 290 50       690 if (defined $alphabet) {
51 290         399 my $default = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv";
52 290 50       1146 return undef if $alphabet !~ /^[a-zA-Z0-9]{58}$/;
53 290         16491 eval "\$base58 =~ tr/$default/$alphabet/"; # HACK: https://stackoverflow.com/questions/11415045/using-a-char-variable-in-tr
54 290 50       1149 return undef if $@;
55             }
56              
57 290         925 return $base58;
58             }
59              
60             sub _decode_b58 {
61 291     291   581 my ($base58, $alphabet) = @_;
62              
63 291 50 33     1143 return '' if !defined $base58 || length($base58) == 0;
64              
65 291         452 my $default = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv";
66 291 50       529 if (defined $alphabet) {
67 291 100 66     9222 return undef if $alphabet !~ /^[a-zA-Z0-9]{58}$/ || $base58 !~ /^[$alphabet]+$/;
68 290         14282 eval "\$base58 =~ tr/$alphabet/$default/"; # HACK: https://stackoverflow.com/questions/11415045/using-a-char-variable-in-tr
69 290 50       1032 return undef if $@;
70             }
71 290 50       1507 return undef if $base58 !~ /^[$default]+$/;
72              
73             # handle leading zeroes
74 290         601 my $bytes = '';
75 290 100       668 if ($base58 =~ /^(0+)(.*)$/) {
76 80         170 $base58 = $2;
77 80         202 $bytes = ("\x00" x length($1));
78             }
79 290 100 66     2564 $bytes .= _radix_to_bin($base58, 58) if defined $base58 && length($base58) > 0;
80              
81 290         1732 return $bytes;
82             }
83              
84 59     59 1 619 sub decode_b58b { _decode_b58(shift, "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") } # Bitcoin
85 58     58 1 118 sub decode_b58f { _decode_b58(shift, "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ") } # Flickr
86 58     58 1 136 sub decode_b58r { _decode_b58(shift, "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz") } # Ripple
87 58     58 1 120 sub decode_b58t { _decode_b58(shift, "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz") } # Tipple
88 58     58 1 129 sub decode_b58s { _decode_b58(shift, "gsphnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCr65jkm8oFqi1tuvAxyz") } # Stellar
89              
90 58     58 1 139 sub encode_b58b { _encode_b58(shift, "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") } # Bitcoin
91 58     58 1 141 sub encode_b58f { _encode_b58(shift, "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ") } # Flickr
92 58     58 1 133 sub encode_b58r { _encode_b58(shift, "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz") } # Ripple
93 58     58 1 147 sub encode_b58t { _encode_b58(shift, "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz") } # Tipple
94 58     58 1 137 sub encode_b58s { _encode_b58(shift, "gsphnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCr65jkm8oFqi1tuvAxyz") } # Stellar
95              
96             sub pem_to_der {
97 112     112 1 311 my ($data, $password) = @_;
98              
99 112         185 my ($begin, $obj1, $content, $end, $obj2);
100             # first try to load KEY (e.g. EC pem files might contain more parts)
101 112         1053 ($begin, $obj1, $content, $end, $obj2) = $data =~ m/(----[- ]BEGIN ([^\r\n\-]+KEY)[ -]----)(.*?)(----[- ]END ([^\r\n\-]+)[ -]----)/s;
102             # if failed then try to load anything
103 112 100       387 ($begin, $obj1, $content, $end, $obj2) = $data =~ m/(----[- ]BEGIN ([^\r\n\-]+)[ -]----)(.*?)(----[- ]END ([^\r\n\-]+)[ -]----)/s unless $content;
104 112 50       220 return undef unless $content;
105              
106 112         624 $content =~ s/^\s+//sg;
107 112         1028 $content =~ s/\s+$//sg;
108 112         255 $content =~ s/\r\n/\n/sg; # CR-LF >> LF
109 112         182 $content =~ s/\r/\n/sg; # CR >> LF
110 112         174 $content =~ s/\\\n//sg; # \ + LF
111              
112 112         1690 my ($headers, undef, $b64) = $content =~ /^(([^:]+:.*?\n)*)(.*)$/s;
113 112 50       273 return undef unless $b64;
114              
115 112         976 my $binary = decode_b64($b64);
116 112 50       248 return undef unless $binary;
117              
118 112         183 my ($ptype, $cipher_name, $iv_hex);
119 112   100     547 for my $h (split /\n/, ($headers||'')) {
120 76         280 my ($k, $v) = split /:\s*/, $h, 2;
121 76 100       200 $ptype = $v if $k eq 'Proc-Type';
122 76 100       322 ($cipher_name, $iv_hex) = $v =~ /^\s*(.*?)\s*,\s*([0-9a-fA-F]+)\s*$/ if $k eq 'DEK-Info';
123             }
124 112 50 66     435 if ($cipher_name && $iv_hex && $ptype && $ptype eq '4,ENCRYPTED') {
      66        
      33        
125 33 50       76 croak "FATAL: encrypted PEM but no password provided" unless defined $password;
126 33         142 my $iv = pack("H*", $iv_hex);
127 33         72 my ($mode, $klen) = _name2mode($cipher_name);
128 33         88 my $key = _password2key($password, $klen, $iv, 'MD5');
129 33         113 return $mode->decrypt($binary, $key, $iv);
130             }
131 79         405 return $binary;
132             }
133              
134             sub der_to_pem {
135 19     19 1 50 my ($data, $header_name, $password, $cipher_name) = @_;
136 19         33 my $content = $data;
137 19         29 my @headers;
138              
139 19 50       43 if ($password) {
140 0   0     0 $cipher_name ||= 'AES-256-CBC';
141 0         0 my ($mode, $klen, $ilen) = _name2mode($cipher_name);
142 0         0 my $iv = random_bytes($ilen);
143 0         0 my $key = _password2key($password, $klen, $iv, 'MD5');
144 0         0 $content = $mode->encrypt($data, $key, $iv);
145 0         0 push @headers, 'Proc-Type: 4,ENCRYPTED', "DEK-Info: ".uc($cipher_name).",".unpack("H*", $iv);
146             }
147              
148 19         51 my $pem = "-----BEGIN $header_name-----\n";
149 19 50       43 if (@headers) {
150 0         0 $pem .= "$_\n" for @headers;
151 0         0 $pem .= "\n";
152             }
153 19         357 my @l = encode_b64($content) =~ /.{1,64}/g;
154 19         98 $pem .= join("\n", @l) . "\n";
155 19         44 $pem .= "-----END $header_name-----\n";
156 19         133 return $pem;
157             }
158              
159             sub read_rawfile {
160             # $data = read_rawfile($filename);
161 224     224 1 2850 my $f = shift;
162 224 50       2337 croak "FATAL: read_rawfile() non-existing file '$f'" unless -f $f;
163 224 50       8637 open my $fh, "<", $f or croak "FATAL: read_rawfile() cannot open file '$f': $!";
164 224         926 binmode $fh;
165 224         365 return do { local $/; <$fh> };
  224         1072  
  224         10546  
166             }
167              
168             sub write_rawfile {
169             # write_rawfile($filename, $data);
170 1 50   1 1 19700 croak "FATAL: write_rawfile() no data" unless defined $_[1];
171 1 50       165 open my $fh, ">", $_[0] or croak "FATAL: write_rawfile() cannot open file '$_[0]': $!";
172 1         6 binmode $fh;
173 1 50       27 print $fh $_[1] or croak "FATAL: write_rawfile() cannot write to '$_[0]': $!";
174 1 50       72 close $fh or croak "FATAL: write_rawfile() cannot close '$_[0]': $!";
175 1         8 return;
176             }
177              
178             sub slow_eq {
179 1     1 1 6 my ($a, $b) = @_;
180 1 50 33     9 return unless defined $a && defined $b;
181 1         4 my $diff = length $a ^ length $b;
182 1   66     7 for(my $i = 0; $i < length $a && $i < length $b; $i++) {
183 10         31 $diff |= ord(substr $a, $i) ^ ord(substr $b, $i);
184             }
185 1         5 return $diff == 0;
186             }
187              
188             sub random_v4uuid() {
189             # Version 4 - random - UUID: xxxxxxxx-xxxx-4xxx-Yxxx-xxxxxxxxxxxx
190             # where x is any hexadecimal digit and Y is one of 8, 9, A, B (1000, 1001, 1010, 1011)
191             # e.g. f47ac10b-58cc-4372-a567-0e02b2c3d479
192 1     1 1 7 my $raw = random_bytes(16);
193             # xxxxxxxxxxxx4xxxYxxxxxxxxxxxxxxx
194 1         4 $raw &= pack("H*", "FFFFFFFFFFFF0FFFFFFFFFFFFFFFFFFF");
195 1         2 $raw |= pack("H*", "00000000000040000000000000000000");
196 1         2 $raw &= pack("H*", "FFFFFFFFFFFFFFFF3FFFFFFFFFFFFFFF"); # 0x3 == 0011b
197 1         2 $raw |= pack("H*", "00000000000000008000000000000000"); # 0x8 == 1000b
198 1         3 my $hex = unpack("H*", $raw);
199 1         19 $hex =~ s/^(.{8})(.{4})(.{4})(.{4})(.{12}).*$/$1-$2-$3-$4-$5/;
200 1         5 return $hex;
201             }
202              
203             sub is_v4uuid($) {
204 1     1 1 352 my $uuid = shift;
205 1 50       5 return 0 if !$uuid;
206 1 50       11 return 1 if $uuid =~ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
207 0         0 return 0;
208             }
209              
210             ### private functions
211              
212             sub _name2mode {
213 33     33   74 my $cipher_name = uc(shift);
214 33         104 my %trans = ( 'DES-EDE3' => 'DES_EDE' );
215              
216 33         221 my ($cipher, undef, $klen, $mode) = $cipher_name =~ /^(AES|CAMELLIA|DES|DES-EDE3|SEED)(-(\d+))?-(CBC|CFB|ECB|OFB)$/i;
217 33 50 33     129 croak "FATAL: unsupported cipher '$cipher_name'" unless $cipher && $mode;
218 33   66     113 $cipher = $trans{$cipher} || $cipher;
219 33 100       71 $klen = 192 if $cipher eq 'DES_EDE';
220 33 100       57 $klen = 64 if $cipher eq 'DES';
221 33 100       62 $klen = 128 if $cipher eq 'SEED';
222 33 50       117 $klen = $klen ? int($klen/8) : Crypt::Cipher::min_keysize($cipher);
223 33         186 my $ilen = Crypt::Cipher::blocksize($cipher);
224 33 50 33     124 croak "FATAL: unsupported cipher '$cipher_name'" unless $klen && $ilen;
225              
226 33 50       417 return (Crypt::Mode::CBC->new($cipher), $klen, $ilen) if $mode eq 'CBC';
227 0 0       0 return (Crypt::Mode::CFB->new($cipher), $klen, $ilen) if $mode eq 'CFB';
228 0 0       0 return (Crypt::Mode::ECB->new($cipher), $klen, $ilen) if $mode eq 'ECB';
229 0 0       0 return (Crypt::Mode::OFB->new($cipher), $klen, $ilen) if $mode eq 'OFB';
230             }
231              
232             sub _password2key {
233 33     33   73 my ($password, $klen, $iv, $hash) = @_;
234 33         77 my $salt = substr($iv, 0, 8);
235 33         54 my $key = '';
236 33         81 while (length($key) < $klen) {
237 48         292 $key .= digest_data($hash, $key . $password . $salt);
238             }
239 33         101 return substr($key, 0, $klen);
240             }
241              
242             1;
243              
244             =pod
245              
246             =head1 NAME
247              
248             Crypt::Misc - miscellaneous functions related to (or used by) CryptX
249              
250             =head1 SYNOPSIS
251              
252             This module contains a collection of mostly unsorted functions loosely-related to CryptX distribution but not implementing cryptography.
253              
254             Most of them are also available in other perl modules but once you utilize CryptX you might avoid dependencies on other modules by using
255             functions from Crypt::Misc.
256              
257             =head1 DESCRIPTION
258              
259             use Crypt::Misc ':all';
260              
261             # Base64 and Base64/URL-safe functions
262             $base64 = encode_b64($rawbytes);
263             $rawbytes = decode_b64($base64);
264             $base64url = encode_b64u($encode_b64u);
265             $rawbytes = decode_b64u($base64url);
266              
267             # read/write file
268             $rawdata = read_rawfile($filename);
269             write_rawfile($filename, $rawdata);
270              
271             # convert PEM/DER
272             $der_data = pem_to_der($pem_data);
273             $pem_data = der_to_pem($der_data);
274              
275             # others
276             die "mismatch" unless slow_eq($str1, $str2);
277              
278             =head1 FUNCTIONS
279              
280             By default, Crypt::Misc doesn't import any function. You can import individual functions like this:
281              
282             use Crypt::Misc qw(read_rawfile);
283              
284             Or import all available functions:
285              
286             use Crypt::Misc ':all';
287              
288             =head2 read_rawfile
289              
290             I
291              
292             $rawdata = read_rawfile($filename);
293              
294             Read file C<$filename> into a scalar as a binary data (without decoding/transformation).
295              
296             =head2 write_rawfile
297              
298             I
299              
300             write_rawfile($filename, $rawdata);
301              
302             Write C<$rawdata> to file C<$filename> as binary data.
303              
304             =head2 slow_eq
305              
306             I
307              
308             if (slow_eq($data1, $data2)) { ... }
309              
310             Constant time compare (to avoid timing side-channel).
311              
312             =head2 pem_to_der
313              
314             I
315              
316             $der_data = pem_to_der($pem_data);
317             #or
318             $der_data = pem_to_der($pem_data, $password);
319              
320             Convert PEM to DER representation. Supports also password protected PEM data.
321              
322             =head2 der_to_pem
323              
324             I
325              
326             $pem_data = der_to_pem($der_data, $header_name);
327             #or
328             $pem_data = der_to_pem($der_data, $header_name, $password);
329             #or
330             $pem_data = der_to_pem($der_data, $header_name, $passord, $cipher_name);
331              
332             # $header_name e.g. "PUBLIC KEY", "RSA PRIVATE KEY" ...
333             # $cipher_name e.g. "DES-EDE3-CBC", "AES-256-CBC" (DEFAULT) ...
334              
335             Convert DER to PEM representation. Supports also password protected PEM data.
336              
337             =head2 random_v4uuid
338              
339             I
340              
341             my $uuid = random_v4uuid();
342              
343             Returns cryptographically strong Version 4 random UUID: C
344             where C is any hexadecimal digit and C is one of 8, 9, A, B (1000, 1001, 1010, 1011)
345             e.g. C.
346              
347             =head2 is_v4uuid
348              
349             I
350              
351             if (is_v4uuid($uuid)) {
352             ...
353             }
354              
355             Checks the given C<$uuid> string whether it matches V4 UUID format and returns C<0> (mismatch) or C<1> (match).
356              
357             =head2 increment_octets_le
358              
359             I
360              
361             $octects = increment_octets_le($octets);
362              
363             Take input C<$octets> as a little-endian big number and return an increment.
364              
365             =head2 increment_octets_be
366              
367             I
368              
369             $octects = increment_octets_be($octets);
370              
371             Take input C<$octets> as a big-endian big number and return an increment.
372              
373             =head2 encode_b64
374              
375             I
376              
377             $base64string = encode_b64($rawdata);
378              
379             Encode $rawbytes into Base64 string, no line-endings in the output string.
380              
381             =head2 decode_b64
382              
383             I
384              
385             $rawdata = decode_b64($base64string);
386              
387             Decode a Base64 string.
388              
389             =head2 encode_b64u
390              
391             I
392              
393             $base64url_string = encode_b64($rawdata);
394              
395             Encode $rawbytes into Base64/URL-Safe string, no line-endings in the output string.
396              
397             =head2 decode_b64u
398              
399             I
400              
401             $rawdata = decode_b64($base64url_string);
402              
403             Decode a Base64/URL-Safe string.
404              
405             =head2 encode_b32r
406              
407             I
408              
409             $string = encode_b32r($rawdata);
410              
411             Encode bytes into Base32 (rfc4648 alphabet) string, without "=" padding.
412              
413             =head2 decode_b32r
414              
415             I
416              
417             $rawdata = decode_b32r($string);
418              
419             Decode a Base32 (rfc4648 alphabet) string into bytes.
420              
421             =head2 encode_b32b
422              
423             I
424              
425             $string = encode_b32b($rawdata);
426              
427             Encode bytes into Base32 (base32hex alphabet) string, without "=" padding.
428              
429             =head2 decode_b32b
430              
431             I
432              
433             $rawdata = decode_b32b($string);
434              
435             Decode a Base32 (base32hex alphabet) string into bytes.
436              
437             =head2 encode_b32z
438              
439             I
440              
441             $string = encode_b32z($rawdata);
442              
443             Encode bytes into Base32 (zbase32 alphabet) string.
444              
445             =head2 decode_b32z
446              
447             I
448              
449             $rawdata = decode_b32z($string);
450              
451             Decode a Base32 (zbase32 alphabet) string into bytes.
452              
453             =head2 encode_b32c
454              
455             I
456              
457             $string = encode_b32c($rawdata);
458              
459             Encode bytes into Base32 (crockford alphabet) string.
460              
461             =head2 decode_b32c
462              
463             I
464              
465             $rawdata = decode_b32c($string);
466              
467             Decode a Base32 (crockford alphabet) string into bytes.
468              
469             =head2 encode_b58b
470              
471             I
472              
473             $string = encode_b58b($rawdata);
474              
475             Encode bytes into Base58 (Bitcoin alphabet) string.
476              
477             =head2 decode_b58b
478              
479             I
480              
481             $rawdata = decode_b58b($string);
482              
483             Decode a Base58 (Bitcoin alphabet) string into bytes.
484              
485             =head2 encode_b58f
486              
487             I
488              
489             $string = encode_b58f($rawdata);
490              
491             Encode bytes into Base58 (Flickr alphabet) string.
492              
493             =head2 decode_b58f
494              
495             I
496              
497             $rawdata = decode_b58f($string);
498              
499             Decode a Base58 (Flickr alphabet) string into bytes.
500              
501             =head2 encode_b58r
502              
503             I
504              
505             $string = encode_b58r($rawdata);
506              
507             Encode bytes into Base58 (Ripple alphabet) string.
508              
509             =head2 decode_b58r
510              
511             I
512              
513             $rawdata = decode_b58r($string);
514              
515             Decode a Base58 (Ripple alphabet) string into bytes.
516              
517             =head2 encode_b58t
518              
519             I
520              
521             $string = encode_b58t($rawdata);
522              
523             Encode bytes into Base58 (Tipple alphabet) string.
524              
525             =head2 decode_b58t
526              
527             I
528              
529             $rawdata = decode_b58t($string);
530              
531             Decode a Base58 (Tipple alphabet) string into bytes.
532              
533             =head2 encode_b58s
534              
535             I
536              
537             $string = encode_b58s($rawdata);
538              
539             Encode bytes into Base58 (Stellar alphabet) string.
540              
541             =head2 decode_b58s
542              
543             I
544              
545             $rawdata = decode_b58s($string);
546              
547             Decode a Base58 (Stellar alphabet) string into bytes.
548              
549             =head1 SEE ALSO
550              
551             =over
552              
553             =item * L
554              
555             =back
556              
557             =cut