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   106297 use strict;
  15         43  
  15         373  
4 15     15   62 use warnings;
  15         20  
  15         730  
5             our $VERSION = '0.080';
6              
7             require Exporter; our @ISA = qw(Exporter); ### use Exporter 5.57 'import';
8 15     15   75 use Carp 'croak';
  15         21  
  15         1596  
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   89 use Carp 'carp';
  15         28  
  15         679  
29 15     15   812 use CryptX;
  15         40  
  15         381  
30 15     15   1375 use Crypt::Digest 'digest_data';
  15         20  
  15         564  
31 15     15   4827 use Crypt::Mode::CBC;
  15         30  
  15         336  
32 15     15   4353 use Crypt::Mode::CFB;
  15         28  
  15         414  
33 15     15   4443 use Crypt::Mode::ECB;
  15         32  
  15         342  
34 15     15   4516 use Crypt::Mode::OFB;
  15         30  
  15         368  
35 15     15   71 use Crypt::Cipher;
  15         22  
  15         228  
36 15     15   4625 use Crypt::PRNG 'random_bytes';
  15         31  
  15         30621  
37              
38             sub _encode_b58 {
39 290     290   477 my ($bytes, $alphabet) = @_;
40              
41 290 50 33     1066 return '' if !defined $bytes || length($bytes) == 0;
42              
43             # handle leading zero-bytes
44 290         361 my $base58 = '';
45 290 100       736 if ($bytes =~ /^(\x00+)/) {
46 80         216 $base58 = ('0' x length($1));
47             }
48 290         4384 $base58 .= _bin_to_radix($bytes, 58);
49              
50 290 50       575 if (defined $alphabet) {
51 290         349 my $default = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv";
52 290 50       910 return undef if $alphabet !~ /^[a-zA-Z0-9]{58}$/;
53 290         13113 eval "\$base58 =~ tr/$default/$alphabet/"; # HACK: https://stackoverflow.com/questions/11415045/using-a-char-variable-in-tr
54 290 50       884 return undef if $@;
55             }
56              
57 290         762 return $base58;
58             }
59              
60             sub _decode_b58 {
61 291     291   460 my ($base58, $alphabet) = @_;
62              
63 291 50 33     991 return '' if !defined $base58 || length($base58) == 0;
64              
65 291         329 my $default = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv";
66 291 50       416 if (defined $alphabet) {
67 291 100 66     8234 return undef if $alphabet !~ /^[a-zA-Z0-9]{58}$/ || $base58 !~ /^[$alphabet]+$/;
68 290         11628 eval "\$base58 =~ tr/$alphabet/$default/"; # HACK: https://stackoverflow.com/questions/11415045/using-a-char-variable-in-tr
69 290 50       893 return undef if $@;
70             }
71 290 50       1243 return undef if $base58 !~ /^[$default]+$/;
72              
73             # handle leading zeroes
74 290         423 my $bytes = '';
75 290 100       574 if ($base58 =~ /^(0+)(.*)$/) {
76 80         153 $base58 = $2;
77 80         161 $bytes = ("\x00" x length($1));
78             }
79 290 100 66     2096 $bytes .= _radix_to_bin($base58, 58) if defined $base58 && length($base58) > 0;
80              
81 290         1391 return $bytes;
82             }
83              
84 59     59 1 509 sub decode_b58b { _decode_b58(shift, "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") } # Bitcoin
85 58     58 1 103 sub decode_b58f { _decode_b58(shift, "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ") } # Flickr
86 58     58 1 105 sub decode_b58r { _decode_b58(shift, "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz") } # Ripple
87 58     58 1 109 sub decode_b58t { _decode_b58(shift, "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz") } # Tipple
88 58     58 1 110 sub decode_b58s { _decode_b58(shift, "gsphnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCr65jkm8oFqi1tuvAxyz") } # Stellar
89              
90 58     58 1 128 sub encode_b58b { _encode_b58(shift, "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") } # Bitcoin
91 58     58 1 133 sub encode_b58f { _encode_b58(shift, "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ") } # Flickr
92 58     58 1 129 sub encode_b58r { _encode_b58(shift, "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz") } # Ripple
93 58     58 1 128 sub encode_b58t { _encode_b58(shift, "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz") } # Tipple
94 58     58 1 126 sub encode_b58s { _encode_b58(shift, "gsphnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCr65jkm8oFqi1tuvAxyz") } # Stellar
95              
96             sub pem_to_der {
97 112     112 1 639 my ($data, $password) = @_;
98              
99 112         197 my ($begin, $obj1, $content, $end, $obj2);
100             # first try to load KEY (e.g. EC pem files might contain more parts)
101 112         932 ($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       322 ($begin, $obj1, $content, $end, $obj2) = $data =~ m/(----[- ]BEGIN ([^\r\n\-]+)[ -]----)(.*?)(----[- ]END ([^\r\n\-]+)[ -]----)/s unless $content;
104 112 50       248 return undef unless $content;
105              
106 112         543 $content =~ s/^\s+//sg;
107 112         2968 $content =~ s/\s+$//sg;
108 112         244 $content =~ s/\r\n/\n/sg; # CR-LF >> LF
109 112         171 $content =~ s/\r/\n/sg; # CR >> LF
110 112         189 $content =~ s/\\\n//sg; # \ + LF
111              
112 112         1540 my ($headers, undef, $b64) = $content =~ /^(([^:]+:.*?\n)*)(.*)$/s;
113 112 50       253 return undef unless $b64;
114              
115 112         909 my $binary = decode_b64($b64);
116 112 50       288 return undef unless $binary;
117              
118 112         172 my ($ptype, $cipher_name, $iv_hex);
119 112   100     527 for my $h (split /\n/, ($headers||'')) {
120 76         243 my ($k, $v) = split /:\s*/, $h, 2;
121 76 100       159 $ptype = $v if $k eq 'Proc-Type';
122 76 100       335 ($cipher_name, $iv_hex) = $v =~ /^\s*(.*?)\s*,\s*([0-9a-fA-F]+)\s*$/ if $k eq 'DEK-Info';
123             }
124 112 50 66     457 if ($cipher_name && $iv_hex && $ptype && $ptype eq '4,ENCRYPTED') {
      66        
      33        
125 33 50       64 croak "FATAL: encrypted PEM but no password provided" unless defined $password;
126 33         124 my $iv = pack("H*", $iv_hex);
127 33         63 my ($mode, $klen) = _name2mode($cipher_name);
128 33         82 my $key = _password2key($password, $klen, $iv, 'MD5');
129 33         98 return $mode->decrypt($binary, $key, $iv);
130             }
131 79         345 return $binary;
132             }
133              
134             sub der_to_pem {
135 19     19 1 44 my ($data, $header_name, $password, $cipher_name) = @_;
136 19         27 my $content = $data;
137 19         25 my @headers;
138              
139 19 50       36 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         47 my $pem = "-----BEGIN $header_name-----\n";
149 19 50       37 if (@headers) {
150 0         0 $pem .= "$_\n" for @headers;
151 0         0 $pem .= "\n";
152             }
153 19         265 my @l = encode_b64($content) =~ /.{1,64}/g;
154 19         84 $pem .= join("\n", @l) . "\n";
155 19         34 $pem .= "-----END $header_name-----\n";
156 19         111 return $pem;
157             }
158              
159             sub read_rawfile {
160             # $data = read_rawfile($filename);
161 224     224 1 2320 my $f = shift;
162 224 50       1860 croak "FATAL: read_rawfile() non-existing file '$f'" unless -f $f;
163 224 50       6595 open my $fh, "<", $f or croak "FATAL: read_rawfile() cannot open file '$f': $!";
164 224         708 binmode $fh;
165 224         296 return do { local $/; <$fh> };
  224         912  
  224         7963  
166             }
167              
168             sub write_rawfile {
169             # write_rawfile($filename, $data);
170 1 50   1 1 19687 croak "FATAL: write_rawfile() no data" unless defined $_[1];
171 1 50       113 open my $fh, ">", $_[0] or croak "FATAL: write_rawfile() cannot open file '$_[0]': $!";
172 1         5 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         5 return;
176             }
177              
178             sub slow_eq {
179 1     1 1 3 my ($a, $b) = @_;
180 1 50 33     7 return unless defined $a && defined $b;
181 1         3 my $diff = length $a ^ length $b;
182 1   66     6 for(my $i = 0; $i < length $a && $i < length $b; $i++) {
183 10         24 $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 5 my $raw = random_bytes(16);
193             # xxxxxxxxxxxx4xxxYxxxxxxxxxxxxxxx
194 1         3 $raw &= pack("H*", "FFFFFFFFFFFF0FFFFFFFFFFFFFFFFFFF");
195 1         2 $raw |= pack("H*", "00000000000040000000000000000000");
196 1         2 $raw &= pack("H*", "FFFFFFFFFFFFFFFF3FFFFFFFFFFFFFFF"); # 0x3 == 0011b
197 1         1 $raw |= pack("H*", "00000000000000008000000000000000"); # 0x8 == 1000b
198 1         4 my $hex = unpack("H*", $raw);
199 1         14 $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 337 my $uuid = shift;
205 1 50       3 return 0 if !$uuid;
206 1 50       9 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   65 my $cipher_name = uc(shift);
214 33         118 my %trans = ( 'DES-EDE3' => 'DES_EDE' );
215              
216 33         191 my ($cipher, undef, $klen, $mode) = $cipher_name =~ /^(AES|CAMELLIA|DES|DES-EDE3|SEED)(-(\d+))?-(CBC|CFB|ECB|OFB)$/i;
217 33 50 33     116 croak "FATAL: unsupported cipher '$cipher_name'" unless $cipher && $mode;
218 33   66     127 $cipher = $trans{$cipher} || $cipher;
219 33 100       62 $klen = 192 if $cipher eq 'DES_EDE';
220 33 100       52 $klen = 64 if $cipher eq 'DES';
221 33 100       64 $klen = 128 if $cipher eq 'SEED';
222 33 50       99 $klen = $klen ? int($klen/8) : Crypt::Cipher::min_keysize($cipher);
223 33         150 my $ilen = Crypt::Cipher::blocksize($cipher);
224 33 50 33     112 croak "FATAL: unsupported cipher '$cipher_name'" unless $klen && $ilen;
225              
226 33 50       444 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   76 my ($password, $klen, $iv, $hash) = @_;
234 33         59 my $salt = substr($iv, 0, 8);
235 33         48 my $key = '';
236 33         68 while (length($key) < $klen) {
237 48         252 $key .= digest_data($hash, $key . $password . $salt);
238             }
239 33         82 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