File Coverage

blib/lib/Crypt/Mimetic.pm
Criterion Covered Total %
statement 68 169 40.2
branch 4 42 9.5
condition 8 24 33.3
subroutine 23 37 62.1
pod 15 17 88.2
total 118 289 40.8


line stmt bran cond sub pod time code
1             =pod
2            
3             =head1 NAME
4            
5             Crypt::Mimetic - Crypt a file and mask it behind another file
6            
7            
8             =head1 SYNOPSIS
9            
10             use Crypt::Mimetic;
11            
12             Crypt::Mimetic::Mask($original_file, $mask_file, $destination_file, $algorithm);
13             Crypt::Mimetic::Unmask($mimetic_file);
14            
15             Crypt::Mimetic::Main($original_file, $mask_file, $destination_file, [$algorithm]);
16             Crypt::Mimetic::Main($mimetic_file);
17            
18            
19             =head1 DESCRIPTION
20            
21             This module allows you to hide a file by encrypting in and then attaching
22             it to another file of your choice.
23             This mimetic file then looks and behaves like a normal file,
24             and can be stored, used or emailed without attracting attention.
25            
26            
27             =head1 EXAMPLES
28            
29             =head2 Here your first running example
30            
31             use Crypt::Mimetic;
32             use Error;
33             $Error::Debug = 1;
34            
35             &Crypt::Mimetic::Main(@ARGV);
36            
37             You should already have it in your bin/ files: write I and follow instructions.
38            
39             =head2 How to make a test of all encryption algorithms
40            
41             use Crypt::Mimetic;
42            
43             use Error qw(:try);
44             $Error::Debug = 1;
45            
46             print "\nPerforming tests for Crypt::Mimetic\n";
47             print "Looking for available encryption algorithms, please wait... ";
48             select((select(STDOUT), $| = 1)[0]); #flush stdout
49            
50             @algo = Crypt::Mimetic::GetEncryptionAlgorithms();
51             print @algo ." algorithms found.\n\n";
52            
53             $str = "This is a test string";
54             $failed = 0;
55             $warn = 0;
56            
57             foreach my $algo (@algo) {
58            
59             try {
60            
61             print ''. Crypt::Mimetic::ShortDescr($algo) ."\n";
62             print " Encrypting string '$str' with $algo...";
63             select((select(STDOUT), $| = 1)[0]); #flush stdout
64            
65             ($enc,@info) = Crypt::Mimetic::EncryptString($str,$algo,"my stupid password");
66             print " done.\n";
67            
68             print " Decrypting encrypted string with $algo...";
69             select((select(STDOUT), $| = 1)[0]);
70            
71             $dec = Crypt::Mimetic::DecryptString($enc,$algo,"my stupid password",@info);
72             print " '$dec'.\n";
73            
74             if ($dec eq $str) {
75             print "Algorithm $algo: ok.\n\n";
76             } else {
77             print "Algorithm $algo: failed. Decrypted string '$dec' not equals to original string '$str'\n\n";
78             $failed++;
79             }#if-else
80            
81             } catch Error::Mimetic with {
82             my $x = shift;
83            
84             if ($x->type() eq "error") {
85             print "Algorithm $algo: error. ". $x->stringify() ."\n";
86             $failed++;
87             } elsif ($x->type() eq "warning") {
88             print "Algorithm $algo: warning. ". $x->stringify() ."\n";
89             $warn++;
90             }#if-else
91            
92             }#try-catch
93            
94             }#foreach
95            
96             print @algo ." tests performed: ". (@algo - $failed) ." passed, $failed failed ($warn warnings).\n\n";
97             exit $failed;
98            
99             Script I used by I in this distribution
100             do exactly the same thing.
101            
102             =cut
103            
104             package Crypt::Mimetic;
105 1     1   945 use strict;
  1         2  
  1         45  
106 1     1   7 use vars qw($VERSION);
  1         1  
  1         64  
107            
108 1     1   1260 use Error qw(:try);
  1         14324  
  1         6  
109 1     1   1165 use Error::Mimetic;
  1         78  
  1         9  
110 1     1   1186 use Term::ReadKey;
  1         13138  
  1         111  
111 1     1   969 use File::Copy;
  1         2605  
  1         86  
112 1     1   7 use File::Find ();
  1         2  
  1         46  
113            
114             $VERSION = '0.02';
115            
116             =pod
117            
118             =head1 PROCEDURAL INTERFACE
119            
120             =over 4
121            
122             =item @array I ()
123            
124             Return an array with names of encryption algorithms. Each algorithm is
125             implemented in module Crypt::Mimetic::
126            
127             =cut
128            
129             sub GetEncryptionAlgorithms {
130             # Set the variable $File::Find::dont_use_nlink if you're using AFS,
131             # since AFS cheats.
132            
133             # for the convenience of &wanted calls, including -eval statements:
134 1     1   8 use vars qw/*name *dir *prune/;
  1         2  
  1         818  
135 1     1 0 86 *name = *File::Find::name;
136 1         4 *dir = *File::Find::dir;
137 1         4 *prune = *File::Find::prune;
138            
139 1         2 my (@dirs, %algo);
140             my $wanted = sub {
141 4049     4049   5193 my ($dev,$ino,$mode,$nlink,$uid,$gid);
142            
143 4049 50 66     164652 /^Mimetic\z/os &&
      66        
144             (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) &&
145             -d _ &&
146             push(@dirs,"$name");
147 1         9 };
148            
149             # Traverse desired filesystems
150 1         168 File::Find::find({wanted => $wanted}, @INC);
151            
152             $wanted = sub {
153 36     36   53 my ($dev,$ino,$mode,$nlink,$uid,$gid);
154            
155 36 50 66     1561 /^.*\.pm\z/os &&
      66        
156             (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) &&
157             -f _ || return;
158 15         150 s/^(.+)\.pm$/$1/o;
159 15         483 $algo{$_} = $_;
160 1         13 };
161            
162 1         105 File::Find::find({wanted => $wanted}, @dirs);
163 1         19 return ( keys %algo );
164             }
165            
166             =pod
167            
168             =item string I ($prompt)
169            
170             Ask for a password with a given prompt (default "Password: ")
171             and return it.
172            
173             =cut
174            
175             sub GetPasswd {
176 0     0 1 0 my ($prompt) = @_;
177 0 0       0 $prompt = "Password: " unless $prompt;
178 0         0 print STDERR $prompt;
179 0         0 ReadMode('noecho');
180 0         0 my $key = ReadLine(0);
181 0         0 ReadMode('restore');
182 0         0 print "\n";
183 0         0 $key =~ s/[\r\n]*$//o;
184 0         0 return $key;
185             }
186            
187             =pod
188            
189             =item string I ()
190            
191             Ask for a password twice and return it only if it's correct.
192            
193             Throws an I if passwords don't match
194            
195             =cut
196            
197             sub GetConfirmedPasswd {
198 0     0 1 0 my $passwd = GetPasswd();
199 0 0       0 return "" if ($passwd eq "");
200 0         0 my $confirm = GetPasswd("Again: ");
201 0 0       0 return $passwd if ($passwd eq $confirm);
202 0         0 throw Error::Mimetic "Passwords don't match at ". __FILE__ ." line ". __LINE__;
203             }
204            
205             #
206             # @array ExternalCall($algoritm,$func)
207             #
208             sub ExternalCall {
209 5     5 0 12 my ($algorithm,$func,@args) = @_;
210 1     1   891 eval('use Crypt::Mimetic::' . $algorithm);
  1     1   3  
  1     1   20  
  1     1   11  
  1     1   2  
  1         22  
  1         6  
  1         2  
  1         12  
  1         655  
  0         0  
  0         0  
  1         662  
  0         0  
  0         0  
  5         402  
211 5 100       42 throw Error::Mimetic ("Error using algorithm '$algorithm' at ". __FILE__ ." line ". __LINE__, $@) if $@;
212 1     1   7 no strict 'refs';
  1         3  
  1         8422  
213 3         6 return &{ 'Crypt::Mimetic::' . $algorithm . '::' . $func }(@args);
  3         17  
214             }
215            
216             =pod
217            
218             =item string I ($algorithm)
219            
220             Return a short description of $algorithm
221            
222             =cut
223            
224             sub ShortDescr {
225 3     3 1 786 my ($algorithm) = @_;
226             try {
227 3     3   70 return ExternalCall($algorithm,'ShortDescr');
228             } catch Error::Mimetic with {
229 2     2   105 my $x = shift;
230 2         5 $x->{'-type'} = "warning";
231 2         8 throw $x;
232             }
233 3         33 }
234            
235             =pod
236            
237             =item boolean I ($algorithm)
238            
239             Return true if password is needed by this $algorithm, false otherwise.
240            
241             =cut
242            
243             sub PasswdNeeded {
244 0     0 1 0 my ($algorithm) = @_;
245 0         0 return ExternalCall($algorithm,'PasswdNeeded');
246             }
247            
248             =pod
249            
250             =item ($len,$blocklen,$padlen,[string]) I ($filename,$output,$algorithm,$key,@info)
251            
252             Call specific routine to encrypt $filename according to $algorithm. Return 3 int:
253             $len - is the total output length
254             $blocklen - length of an encrypted block (if needed)
255             $padlen - length of last encrypted block (if needed)
256            
257             If $output is null then the output is returned as string.
258             Ask for a password if key not given.
259            
260             Throws an I if cannot open files or if password is not correctly given.
261            
262             =cut
263            
264             sub EncryptFile {
265 0     0 1 0 my ($filename,$output,$algorithm,$key,@info) = @_;
266 0         0 return ExternalCall($algorithm,'EncryptFile',$filename,$output,$algorithm,$key,@info);
267             }
268            
269             =pod
270            
271             =item string I ($string,$algorithm,$key,@info)
272            
273             Call specific routine to encrypt $string according to $algorithm and return an encrypted string.
274             Ask for a password if key not given.
275            
276             Throws an I if password is not correctly given.
277            
278             =cut
279            
280             sub EncryptString {
281 1     1 1 111 my ($string,$algorithm,$key,@info) = @_;
282 1         8 return ExternalCall($algorithm,'EncryptString',$string,$algorithm,$key,@info);
283             }
284            
285             =pod
286            
287             =item [string] I ($filename,$output,$offset,$len,$algorithm,$key,@info)
288            
289             Call specific routine to decrypt $filename according to $algorithm. Return decrypted file as string if $output is not given, void otherwise.
290             Ask for a password if key not given.
291            
292             Throws an I if cannot open files or if password is not given
293            
294             =cut
295            
296             sub DecryptFile {
297 0     0 1 0 my ($filename,$output,$offset,$len,$algorithm,$key,@info) = @_;
298 0         0 return ExternalCall($algorithm,'DecryptFile',$filename,$output,$offset,$len,$algorithm,$key,@info);
299             }
300            
301             =pod
302            
303             =item string I ($string,$algorithm,$key,@info)
304            
305             Call specific routine to decrypt $string according to $algorithm and return a decrypted string.
306             Ask for a password if key not given.
307            
308             Throws an I if password is not correctly given.
309            
310             =cut
311            
312             sub DecryptString {
313 1     1 1 62 my ($string,$algorithm,$key,@info) = @_;
314 1         4 return ExternalCall($algorithm,'DecryptString',$string,$algorithm,$key,@info);
315             }
316            
317             =pod
318            
319             =item string I ($original_file,$mask_file,$dlen,$algorithm,$key,@info)
320            
321             Create following sign (all on the same line):
322             Mimetic\0
323             version\0
324             mask_file_name\0
325             mask_file_length\0
326             original_file_name\0
327             encrypted_file_length\0
328             @info
329            
330             than encrypt it and calculate length of encrypted sign.
331             Return a string composed by concatenation of encrypted sign, algorithm (32 bytes null padding string) and its length (8 bytes hex number).
332            
333             =cut
334            
335             sub Sign {
336 0     0 1   my ($original_file,$mask_file,$dlen,$algorithm,$key,@info) = @_;
337 0           my $mlen = (stat($mask_file))[7];
338 0           my $sign = join "\0", "Mimetic", $VERSION, $mask_file, $mlen, $original_file, $dlen, @info;
339 0           $sign = EncryptString($sign,$algorithm,$key,@info);
340 0           my $slen = pack "a8", sprintf "%x", length($sign);
341 0           my $algo = pack "A32", $algorithm;
342 0           return join '', $sign, ~$algo, ~$slen;
343             }
344            
345             =pod
346            
347             =item (string,int) I ($mimetic_file)
348            
349             Return the algorithm and the length of the sing read from last 40 bytes of $mimetic_file.
350            
351             Throws an I if cannot open file
352            
353             =cut
354            
355             sub GetSignInfo {
356 0     0 1   my ($mimetic_file) = @_;
357 0           my $len = (stat($mimetic_file))[7];
358 0           my $offset = $len - 40;
359 0 0         open (FH, "$mimetic_file") or throw Error::Mimetic "Cannot open $mimetic_file: $!";
360 0           my ($algo,$slen) = ("","");
361 0           seek FH, $offset, 0;
362 0           read FH, $algo, 32;
363 0           read FH, $slen, 8;
364 0           close(FH);
365 0           return (unpack ("A32", ~$algo) , hex(~$slen));
366             }
367            
368             =pod
369            
370             =item ($Mimetic,$version,$mask_file,$mlen,$original_file,$olen,@pinfo) = I ($mimetic_file,$slen,$algorithm,$key,@info);
371            
372             Extract information from sign of $mimetic_file.
373             You can obtain $slen and $algorithm from I($mimetic_file) and key from I(void)
374             This sub returns an array:
375             $Mimetic - constant string "Mimetic"
376             $version - version of the module
377             $mask_file - mask file's name
378             $mlen - mask file's length
379             $original_file - original file's name
380             $olen - original file's length
381             @pinfo - specific encryption algorithm information
382            
383             Throws an I if cannot open file
384            
385             =cut
386            
387             sub ParseSign {
388 0     0 1   my ($mimetic_file,$slen,$algorithm,$key,@info) = @_;
389 0           my $len = (stat($mimetic_file))[7];
390 0           my $offset = $len - 40 - $slen;
391 0 0         open (FH, "$mimetic_file") or throw Error::Mimetic "Cannot open $mimetic_file: $!";
392 0           my $sign = "";
393 0           seek FH, $offset, 0;
394 0           read FH, $sign, $slen;
395 0           close(FH);
396 0           $sign = DecryptString($sign,$algorithm,$key,@info);
397 0           return split "\0", $sign;
398             }
399            
400             =pod
401            
402             =item void I ($mimetic_file,$len,$mask_file)
403            
404             Extract the mask file from $mimetic_file and save it in $mask_file.
405            
406             Throws an I if cannot open files
407            
408             =cut
409            
410             sub WriteMaskFile {
411 0     0 1   my ($mimetic_file,$len,$mask_file) = @_;
412 0           my ($buf,$blocks,$padlen) = ("",int($len/32768),($len%32768));
413 0 0         open (IN, "$mimetic_file") or throw Error::Mimetic "Cannot open $mimetic_file: $!";
414 0 0         open (OUT, ">$mask_file") or throw Error::Mimetic "Cannot open $mask_file: $!";
415 0           for (my $i = 0; $i < $blocks; $i++ ) {
416 0           read(IN,$buf,32768);
417 0           print OUT $buf;
418             }
419 0           read(IN,$buf,$padlen);
420 0           print OUT $buf;
421 0           close(OUT);
422 0           close(IN);
423             }
424            
425             =pod
426            
427             =item void I ($original_file,$mask_file,$destination_file,$algorithm,$key,@info)
428            
429             Mask the $original_file with a $mask_file and put everything in $destination_file, according $algorithm and @info instruction. Return true on success, false otherwise.
430            
431             Throws an I if cannot open files or password not correctly given
432            
433             =cut
434            
435             sub Mask {
436 0     0 1   my ($original_file,$mask_file,$destination_file,$algorithm,$key,@info) = @_;
437            
438             #test if destination file is ok
439 0 0         open (OF,">$destination_file") or throw Error::Mimetic "Cannot open $destination_file: $!";
440 0           close(OF);
441            
442 0           my $passwd_needed = ExternalCall($algorithm,'PasswdNeeded');
443            
444 0 0         copy ($mask_file,$destination_file) or throw Error::Mimetic "Cannot copy $mask_file to $destination_file: $!";
445            
446 0 0 0       $key = GetConfirmedPasswd() or throw Error::Mimetic "Password is needed at ". __FILE__ ." line ". __LINE__ unless ($key || !$passwd_needed);
      0        
447 0           my ($len,@einfo) = EncryptFile($original_file,$destination_file,$algorithm,$key,@info);
448 0           my $sign = Sign($original_file,$mask_file,$len,$algorithm,$key,@einfo);
449            
450 0 0         open (OF,">>$destination_file") or throw Error::Mimetic "Cannot open $destination_file: $!";
451 0           print OF $sign;
452 0           close(OF);
453             }
454            
455             =pod
456            
457             =item boolean I ($mimetic_file,$algorithm,$key,@info)
458            
459             Unmask a $mimetic file splitting it in 2 files:
460             1. mask file
461             2. original file
462            
463             Throws an I if cannot open files or password not given
464            
465             =cut
466            
467             sub Unmask {
468 0     0 1   my ($mimetic_file,$algorithm,$key,@info) = @_;
469 0           my ($algo,$slen) = GetSignInfo($mimetic_file);
470            
471 0 0         $algorithm = $algo unless $algorithm;
472 0           my $passwd_needed = ExternalCall($algorithm,'PasswdNeeded');
473            
474 0 0 0       $key = GetPasswd() or throw Error::Mimetic "Password is needed at ". __FILE__ ." line ". __LINE__ unless ($key || !$passwd_needed);
      0        
475 0           my ($Mimetic,$version,$mask_file,$mlen,$original_file,$olen,@pinfo) = ParseSign($mimetic_file,$slen,$algorithm,$key,@info);
476            
477 0 0         throw Error::Mimetic "Cannot do anything on this file: signature not recognized at ". __FILE__ ." line ". __LINE__ if ($Mimetic ne "Mimetic");
478            
479 0           WriteMaskFile($mimetic_file,$mlen,$mask_file);
480 0           DecryptFile($mimetic_file,$original_file,$mlen,$olen,$algorithm,$key,@pinfo);
481             }
482            
483             =pod
484            
485             =item void I
(@arguments)
486            
487             A demo main to use this module
488             Usage:
489             to camouflage a file with a mask
490             Main($original_file, $mask_file, $destination_file, [$algorithm]);
491             to split camouflaged file in original file and mask
492             Main($mimetic_file);
493            
494             =cut
495            
496             sub Main {
497 0     0 1   my @argv = @_;
498 0           my $argc = $#argv + 1;
499 0 0         if ($argc == 1) {
    0          
    0          
500 0           return Unmask($argv[0]);
501             } elsif ($argc == 3) {
502 0           return Mask($argv[0],$argv[1],$argv[2],"None");
503             } elsif ($argc == 4) {
504 0           return Mask($argv[0],$argv[1],$argv[2],$argv[3]);
505             } else {
506 0           print <<"END";
507            
508             Usage (see also Perl documentation about Crypt::Mimetic):
509            
510             to camouflage a file with a mask:
511             $0 original-file mask-file destination-file [algorithm]
512            
513             to split camouflaged file in original file and mask:
514             $0 mimetic-file
515            
516             (Looking for available encryption algorithms, please wait...)
517             END
518 0           my @algo = GetEncryptionAlgorithms();
519 0           print " Encryption algorithms found:\n";
520 0           my $err = $Error::Debug;
521 0 0         $Error::Debug = 1 if $err < 1;
522 0           foreach my $algo (@algo) {
523             try {
524 0     0     print ' * '. ShortDescr($algo) ."\n";
525             } catch Error::Mimetic with {
526 0     0     my $x = shift;
527 0           print " * $algo - ". $x->stringify();
528             }
529 0           }
530 0           $Error::Debug = $err;
531 0           print " See Perl documentation about Crypt::Mimetic:: for details.\n\n";
532 0           exit;
533             }
534             }
535            
536             1;
537             __END__