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   137580 use strict;
  15         54  
  15         429  
4 15     15   73 use warnings;
  15         27  
  15         888  
5             our $VERSION = '0.080_001';
6              
7             require Exporter; our @ISA = qw(Exporter); ### use Exporter 5.57 'import';
8 15     15   90 use Carp 'croak';
  15         28  
  15         1917  
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   117 use Carp 'carp';
  15         49  
  15         763  
29 15     15   1117 use CryptX;
  15         61  
  15         496  
30 15     15   1802 use Crypt::Digest 'digest_data';
  15         32  
  15         725  
31 15     15   6093 use Crypt::Mode::CBC;
  15         36  
  15         418  
32 15     15   5683 use Crypt::Mode::CFB;
  15         36  
  15         430  
33 15     15   5894 use Crypt::Mode::ECB;
  15         41  
  15         416  
34 15     15   5770 use Crypt::Mode::OFB;
  15         40  
  15         444  
35 15     15   91 use Crypt::Cipher;
  15         30  
  15         267  
36 15     15   6323 use Crypt::PRNG 'random_bytes';
  15         37  
  15         36997  
37              
38             sub _encode_b58 {
39 290     290   562 my ($bytes, $alphabet) = @_;
40              
41 290 50 33     1261 return '' if !defined $bytes || length($bytes) == 0;
42              
43             # handle leading zero-bytes
44 290         485 my $base58 = '';
45 290 100       914 if ($bytes =~ /^(\x00+)/) {
46 80         261 $base58 = ('0' x length($1));
47             }
48 290         5414 $base58 .= _bin_to_radix($bytes, 58);
49              
50 290 50       717 if (defined $alphabet) {
51 290         408 my $default = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv";
52 290 50       1118 return undef if $alphabet !~ /^[a-zA-Z0-9]{58}$/;
53 290         16310 eval "\$base58 =~ tr/$default/$alphabet/"; # HACK: https://stackoverflow.com/questions/11415045/using-a-char-variable-in-tr
54 290 50       1141 return undef if $@;
55             }
56              
57 290         898 return $base58;
58             }
59              
60             sub _decode_b58 {
61 291     291   582 my ($base58, $alphabet) = @_;
62              
63 291 50 33     1150 return '' if !defined $base58 || length($base58) == 0;
64              
65 291         449 my $default = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv";
66 291 50       512 if (defined $alphabet) {
67 291 100 66     9349 return undef if $alphabet !~ /^[a-zA-Z0-9]{58}$/ || $base58 !~ /^[$alphabet]+$/;
68 290         14625 eval "\$base58 =~ tr/$alphabet/$default/"; # HACK: https://stackoverflow.com/questions/11415045/using-a-char-variable-in-tr
69 290 50       1065 return undef if $@;
70             }
71 290 50       1530 return undef if $base58 !~ /^[$default]+$/;
72              
73             # handle leading zeroes
74 290         535 my $bytes = '';
75 290 100       691 if ($base58 =~ /^(0+)(.*)$/) {
76 80         181 $base58 = $2;
77 80         206 $bytes = ("\x00" x length($1));
78             }
79 290 100 66     2475 $bytes .= _radix_to_bin($base58, 58) if defined $base58 && length($base58) > 0;
80              
81 290         1619 return $bytes;
82             }
83              
84 59     59 1 611 sub decode_b58b { _decode_b58(shift, "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") } # Bitcoin
85 58     58 1 113 sub decode_b58f { _decode_b58(shift, "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ") } # Flickr
86 58     58 1 118 sub decode_b58r { _decode_b58(shift, "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz") } # Ripple
87 58     58 1 130 sub decode_b58t { _decode_b58(shift, "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz") } # Tipple
88 58     58 1 125 sub decode_b58s { _decode_b58(shift, "gsphnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCr65jkm8oFqi1tuvAxyz") } # Stellar
89              
90 58     58 1 141 sub encode_b58b { _encode_b58(shift, "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") } # Bitcoin
91 58     58 1 146 sub encode_b58f { _encode_b58(shift, "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ") } # Flickr
92 58     58 1 156 sub encode_b58r { _encode_b58(shift, "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz") } # Ripple
93 58     58 1 143 sub encode_b58t { _encode_b58(shift, "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz") } # Tipple
94 58     58 1 142 sub encode_b58s { _encode_b58(shift, "gsphnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCr65jkm8oFqi1tuvAxyz") } # Stellar
95              
96             sub pem_to_der {
97 112     112 1 306 my ($data, $password) = @_;
98              
99 112         187 my ($begin, $obj1, $content, $end, $obj2);
100             # first try to load KEY (e.g. EC pem files might contain more parts)
101 112         1010 ($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       346 ($begin, $obj1, $content, $end, $obj2) = $data =~ m/(----[- ]BEGIN ([^\r\n\-]+)[ -]----)(.*?)(----[- ]END ([^\r\n\-]+)[ -]----)/s unless $content;
104 112 50       214 return undef unless $content;
105              
106 112         589 $content =~ s/^\s+//sg;
107 112         1059 $content =~ s/\s+$//sg;
108 112         254 $content =~ s/\r\n/\n/sg; # CR-LF >> LF
109 112         167 $content =~ s/\r/\n/sg; # CR >> LF
110 112         185 $content =~ s/\\\n//sg; # \ + LF
111              
112 112         1642 my ($headers, undef, $b64) = $content =~ /^(([^:]+:.*?\n)*)(.*)$/s;
113 112 50       275 return undef unless $b64;
114              
115 112         931 my $binary = decode_b64($b64);
116 112 50       246 return undef unless $binary;
117              
118 112         169 my ($ptype, $cipher_name, $iv_hex);
119 112   100     562 for my $h (split /\n/, ($headers||'')) {
120 76         269 my ($k, $v) = split /:\s*/, $h, 2;
121 76 100       187 $ptype = $v if $k eq 'Proc-Type';
122 76 100       331 ($cipher_name, $iv_hex) = $v =~ /^\s*(.*?)\s*,\s*([0-9a-fA-F]+)\s*$/ if $k eq 'DEK-Info';
123             }
124 112 50 66     426 if ($cipher_name && $iv_hex && $ptype && $ptype eq '4,ENCRYPTED') {
      66        
      33        
125 33 50       70 croak "FATAL: encrypted PEM but no password provided" unless defined $password;
126 33         127 my $iv = pack("H*", $iv_hex);
127 33         76 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         365 return $binary;
132             }
133              
134             sub der_to_pem {
135 19     19 1 49 my ($data, $header_name, $password, $cipher_name) = @_;
136 19         34 my $content = $data;
137 19         30 my @headers;
138              
139 19 50       48 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         49 my $pem = "-----BEGIN $header_name-----\n";
149 19 50       46 if (@headers) {
150 0         0 $pem .= "$_\n" for @headers;
151 0         0 $pem .= "\n";
152             }
153 19         323 my @l = encode_b64($content) =~ /.{1,64}/g;
154 19         105 $pem .= join("\n", @l) . "\n";
155 19         47 $pem .= "-----END $header_name-----\n";
156 19         132 return $pem;
157             }
158              
159             sub read_rawfile {
160             # $data = read_rawfile($filename);
161 224     224 1 2805 my $f = shift;
162 224 50       2284 croak "FATAL: read_rawfile() non-existing file '$f'" unless -f $f;
163 224 50       8435 open my $fh, "<", $f or croak "FATAL: read_rawfile() cannot open file '$f': $!";
164 224         811 binmode $fh;
165 224         352 return do { local $/; <$fh> };
  224         1045  
  224         10346  
166             }
167              
168             sub write_rawfile {
169             # write_rawfile($filename, $data);
170 1 50   1 1 19646 croak "FATAL: write_rawfile() no data" unless defined $_[1];
171 1 50       153 open my $fh, ">", $_[0] or croak "FATAL: write_rawfile() cannot open file '$_[0]': $!";
172 1         7 binmode $fh;
173 1 50       18 print $fh $_[1] or croak "FATAL: write_rawfile() cannot write to '$_[0]': $!";
174 1 50       58 close $fh or croak "FATAL: write_rawfile() cannot close '$_[0]': $!";
175 1         7 return;
176             }
177              
178             sub slow_eq {
179 1     1 1 4 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     8 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 4 my $raw = random_bytes(16);
193             # xxxxxxxxxxxx4xxxYxxxxxxxxxxxxxxx
194 1         4 $raw &= pack("H*", "FFFFFFFFFFFF0FFFFFFFFFFFFFFFFFFF");
195 1         2 $raw |= pack("H*", "00000000000040000000000000000000");
196 1         3 $raw &= pack("H*", "FFFFFFFFFFFFFFFF3FFFFFFFFFFFFFFF"); # 0x3 == 0011b
197 1         2 $raw |= pack("H*", "00000000000000008000000000000000"); # 0x8 == 1000b
198 1         4 my $hex = unpack("H*", $raw);
199 1         21 $hex =~ s/^(.{8})(.{4})(.{4})(.{4})(.{12}).*$/$1-$2-$3-$4-$5/;
200 1         6 return $hex;
201             }
202              
203             sub is_v4uuid($) {
204 1     1 1 302 my $uuid = shift;
205 1 50       9 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   73 my $cipher_name = uc(shift);
214 33         90 my %trans = ( 'DES-EDE3' => 'DES_EDE' );
215              
216 33         201 my ($cipher, undef, $klen, $mode) = $cipher_name =~ /^(AES|CAMELLIA|DES|DES-EDE3|SEED)(-(\d+))?-(CBC|CFB|ECB|OFB)$/i;
217 33 50 33     119 croak "FATAL: unsupported cipher '$cipher_name'" unless $cipher && $mode;
218 33   66     108 $cipher = $trans{$cipher} || $cipher;
219 33 100       69 $klen = 192 if $cipher eq 'DES_EDE';
220 33 100       62 $klen = 64 if $cipher eq 'DES';
221 33 100       64 $klen = 128 if $cipher eq 'SEED';
222 33 50       118 $klen = $klen ? int($klen/8) : Crypt::Cipher::min_keysize($cipher);
223 33         179 my $ilen = Crypt::Cipher::blocksize($cipher);
224 33 50 33     119 croak "FATAL: unsupported cipher '$cipher_name'" unless $klen && $ilen;
225              
226 33 50       424 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   78 my ($password, $klen, $iv, $hash) = @_;
234 33         68 my $salt = substr($iv, 0, 8);
235 33         54 my $key = '';
236 33         76 while (length($key) < $klen) {
237 48         283 $key .= digest_data($hash, $key . $password . $salt);
238             }
239 33         91 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