File Coverage

blib/lib/Crypt/Misc.pm
Criterion Covered Total %
statement 130 168 77.3
branch 32 82 39.0
condition 12 40 30.0
subroutine 31 33 93.9
pod 17 17 100.0
total 222 340 65.2


line stmt bran cond sub pod time code
1             package Crypt::Misc;
2              
3 15     15   157427 use strict;
  15         20  
  15         401  
4 15     15   51 use warnings;
  15         14  
  15         996  
5             our $VERSION = '0.087_003';
6              
7             require Exporter; our @ISA = qw(Exporter); ### use Exporter 5.57 'import';
8 15     15   64 use Carp 'croak';
  15         27  
  15         1934  
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   68 use Carp 'carp';
  15         17  
  15         556  
29 15     15   662 use CryptX;
  15         17  
  15         360  
30 15     15   1452 use Crypt::Digest 'digest_data';
  15         22  
  15         601  
31 15     15   5210 use Crypt::Mode::CBC;
  15         29  
  15         441  
32 15     15   4882 use Crypt::Mode::CFB;
  15         30  
  15         378  
33 15     15   5250 use Crypt::Mode::ECB;
  15         30  
  15         322  
34 15     15   4748 use Crypt::Mode::OFB;
  15         26  
  15         350  
35 15     15   54 use Crypt::Cipher;
  15         16  
  15         227  
36 15     15   5503 use Crypt::PRNG 'random_bytes';
  15         141  
  15         32054  
37              
38             sub _encode_b58 {
39 290     290   651 my ($bytes, $alphabet) = @_;
40              
41 290 50 33     1375 return '' if !defined $bytes || length($bytes) == 0;
42              
43             # handle leading zero-bytes
44 290         418 my $base58 = '';
45 290 100       917 if ($bytes =~ /^(\x00+)/) {
46 80         212 $base58 = ('0' x length($1));
47             }
48 290         4231 $base58 .= _bin_to_radix($bytes, 58);
49              
50 290 50       680 if (defined $alphabet) {
51 290         408 my $default = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv";
52 290 50       1146 return undef if $alphabet !~ /^[a-zA-Z0-9]{58}$/;
53 290         26028 eval "\$base58 =~ tr/$default/$alphabet/"; # HACK: https://stackoverflow.com/questions/11415045/using-a-char-variable-in-tr
54 290 50       1168 return undef if $@;
55             }
56              
57 290         918 return $base58;
58             }
59              
60             sub _decode_b58 {
61 291     291   608 my ($base58, $alphabet) = @_;
62              
63 291 50 33     1041 return '' if !defined $base58 || length($base58) == 0;
64              
65 291         366 my $default = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv";
66 291 50       493 if (defined $alphabet) {
67 291 100 66     13110 return undef if $alphabet !~ /^[a-zA-Z0-9]{58}$/ || $base58 !~ /^[$alphabet]+$/;
68 290         19192 eval "\$base58 =~ tr/$alphabet/$default/"; # HACK: https://stackoverflow.com/questions/11415045/using-a-char-variable-in-tr
69 290 50       878 return undef if $@;
70             }
71 290 50       1324 return undef if $base58 !~ /^[$default]+$/;
72              
73             # handle leading zeroes
74 290         305 my $bytes = '';
75 290 100       602 if ($base58 =~ /^(0+)(.*)$/) {
76 80         143 $base58 = $2;
77 80         148 $bytes = ("\x00" x length($1));
78             }
79 290 100 66     2070 $bytes .= _radix_to_bin($base58, 58) if defined $base58 && length($base58) > 0;
80              
81 290         1414 return $bytes;
82             }
83              
84 59     59 1 632 sub decode_b58b { _decode_b58(shift, "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") } # Bitcoin
85 58     58 1 116 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 121 sub decode_b58t { _decode_b58(shift, "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz") } # Tipple
88 58     58 1 152 sub decode_b58s { _decode_b58(shift, "gsphnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCr65jkm8oFqi1tuvAxyz") } # Stellar
89              
90 58     58 1 152 sub encode_b58b { _encode_b58(shift, "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") } # Bitcoin
91 58     58 1 160 sub encode_b58f { _encode_b58(shift, "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ") } # Flickr
92 58     58 1 150 sub encode_b58r { _encode_b58(shift, "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz") } # Ripple
93 58     58 1 153 sub encode_b58t { _encode_b58(shift, "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz") } # Tipple
94 58     58 1 157 sub encode_b58s { _encode_b58(shift, "gsphnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCr65jkm8oFqi1tuvAxyz") } # Stellar
95              
96             sub pem_to_der {
97 11     11 1 21 my ($data, $password) = @_;
98              
99 11         17 my ($begin, $obj1, $content, $end, $obj2);
100             # first try to load KEY (e.g. EC pem files might contain more parts)
101 11         80 ($begin, $obj1, $content, $end, $obj2) = $data =~ m/(----[- ]BEGIN ([^\r\n\-]+KEY)[ -]----)(.*?)(----[- ]END ([^\r\n\-]+)[ -]----)/s;
102             # if failed then try to load anything
103 11 100       39 ($begin, $obj1, $content, $end, $obj2) = $data =~ m/(----[- ]BEGIN ([^\r\n\-]+)[ -]----)(.*?)(----[- ]END ([^\r\n\-]+)[ -]----)/s unless $content;
104 11 50       22 return undef unless $content;
105              
106 11         44 $content =~ s/^\s+//sg;
107 11         52 $content =~ s/\s+$//sg;
108 11         23 $content =~ s/\r\n/\n/sg; # CR-LF >> LF
109 11         13 $content =~ s/\r/\n/sg; # CR >> LF
110 11         18 $content =~ s/\\\n//sg; # \ + LF
111              
112 11         61 my ($headers, undef, $b64) = $content =~ /^(([^:]+:.*?\n)*)(.*)$/s;
113 11 50       20 return undef unless $b64;
114              
115 11         47 my $binary = decode_b64($b64);
116 11 50       17 return undef unless $binary;
117              
118 11         17 my ($ptype, $cipher_name, $iv_hex);
119 11   50     51 for my $h (split /\n/, ($headers||'')) {
120 0         0 my ($k, $v) = split /:\s*/, $h, 2;
121 0 0       0 $ptype = $v if $k eq 'Proc-Type';
122 0 0       0 ($cipher_name, $iv_hex) = $v =~ /^\s*(.*?)\s*,\s*([0-9a-fA-F]+)\s*$/ if $k eq 'DEK-Info';
123             }
124 11 0 33     23 if ($cipher_name && $iv_hex && $ptype && $ptype eq '4,ENCRYPTED') {
      33        
      0        
125 0 0       0 croak "FATAL: encrypted PEM but no password provided" unless defined $password;
126 0         0 my $iv = pack("H*", $iv_hex);
127 0         0 my ($mode, $klen) = _name2mode($cipher_name);
128 0         0 my $key = _password2key($password, $klen, $iv, 'MD5');
129 0         0 return $mode->decrypt($binary, $key, $iv);
130             }
131 11         11093 return $binary;
132             }
133              
134             sub der_to_pem {
135 21     21 1 41 my ($data, $header_name, $password, $cipher_name) = @_;
136 21         34 my $content = $data;
137 21         27 my @headers;
138              
139 21 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 21         36 my $pem = "-----BEGIN $header_name-----\n";
149 21 50       48 if (@headers) {
150 0         0 $pem .= "$_\n" for @headers;
151 0         0 $pem .= "\n";
152             }
153 21         320 my @l = encode_b64($content) =~ /.{1,64}/g;
154 21         70 $pem .= join("\n", @l) . "\n";
155 21         28 $pem .= "-----END $header_name-----\n";
156 21         119 return $pem;
157             }
158              
159             sub read_rawfile {
160             # $data = read_rawfile($filename);
161 272     272 1 2790 my $f = shift;
162 272 50       2371 croak "FATAL: read_rawfile() non-existing file '$f'" unless -f $f;
163 272 50       9026 open my $fh, "<", $f or croak "FATAL: read_rawfile() cannot open file '$f': $!";
164 272         688 binmode $fh;
165 272         393 return do { local $/; <$fh> };
  272         1122  
  272         13455  
166             }
167              
168             sub write_rawfile {
169             # write_rawfile($filename, $data);
170 1 50   1 1 144182 croak "FATAL: write_rawfile() no data" unless defined $_[1];
171 1 50       304 open my $fh, ">", $_[0] or croak "FATAL: write_rawfile() cannot open file '$_[0]': $!";
172 1         6 binmode $fh;
173 1 50       15 print $fh $_[1] or croak "FATAL: write_rawfile() cannot write to '$_[0]': $!";
174 1 50       44 close $fh or croak "FATAL: write_rawfile() cannot close '$_[0]': $!";
175 1         8 return;
176             }
177              
178             sub slow_eq {
179 1     1 1 3 my ($a, $b) = @_;
180 1 50 33     6 return unless defined $a && defined $b;
181 1         2 my $diff = length $a ^ length $b;
182 1   66     6 for(my $i = 0; $i < length $a && $i < length $b; $i++) {
183 10         23 $diff |= ord(substr $a, $i) ^ ord(substr $b, $i);
184             }
185 1         6 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         3 $raw &= pack("H*", "FFFFFFFFFFFF0FFFFFFFFFFFFFFFFFFF");
195 1         1 $raw |= pack("H*", "00000000000040000000000000000000");
196 1         1 $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         15 $hex =~ s/^(.{8})(.{4})(.{4})(.{4})(.{12}).*$/$1-$2-$3-$4-$5/;
200 1         3 return $hex;
201             }
202              
203             sub is_v4uuid($) {
204 1     1 1 254 my $uuid = shift;
205 1 50       3 return 0 if !$uuid;
206 1 50       10 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           return 0;
208             }
209              
210             ### private functions
211              
212             sub _name2mode {
213 0     0     my $cipher_name = uc(shift);
214 0           my %trans = ( 'DES-EDE3' => 'DES_EDE' );
215              
216 0           my ($cipher, undef, $klen, $mode) = $cipher_name =~ /^(AES|CAMELLIA|DES|DES-EDE3|SEED)(-(\d+))?-(CBC|CFB|ECB|OFB)$/i;
217 0 0 0       croak "FATAL: unsupported cipher '$cipher_name'" unless $cipher && $mode;
218 0   0       $cipher = $trans{$cipher} || $cipher;
219 0 0         $klen = 192 if $cipher eq 'DES_EDE';
220 0 0         $klen = 64 if $cipher eq 'DES';
221 0 0         $klen = 128 if $cipher eq 'SEED';
222 0 0         $klen = $klen ? int($klen/8) : Crypt::Cipher::min_keysize($cipher);
223 0           my $ilen = Crypt::Cipher::blocksize($cipher);
224 0 0 0       croak "FATAL: unsupported cipher '$cipher_name'" unless $klen && $ilen;
225              
226 0 0         return (Crypt::Mode::CBC->new($cipher), $klen, $ilen) if $mode eq 'CBC';
227 0 0         return (Crypt::Mode::CFB->new($cipher), $klen, $ilen) if $mode eq 'CFB';
228 0 0         return (Crypt::Mode::ECB->new($cipher), $klen, $ilen) if $mode eq 'ECB';
229 0 0         return (Crypt::Mode::OFB->new($cipher), $klen, $ilen) if $mode eq 'OFB';
230             }
231              
232             sub _password2key {
233 0     0     my ($password, $klen, $iv, $hash) = @_;
234 0           my $salt = substr($iv, 0, 8);
235 0           my $key = '';
236 0           while (length($key) < $klen) {
237 0           $key .= digest_data($hash, $key . $password . $salt);
238             }
239 0           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