#!/usr/bin/perl # # encrypt [-d] < file_in > file_out # # Pipelined encrypt, decrypt, using a method simular to that used by the # OpenSSL 'enc' command, and the "aespipe" pipeline encryption command. # However this program uses "Password Based Key Derivation Function v2" (or # PBKDF2 as per RFC2898) for the passphrase to key conversion, which is # designed to slow down brute force attacks, using interative hashed # functions. The other two do not have this security enhancement. # # The password is currently only read directly from /dev/tty, with files being # passed via STDIN and STDOUT. This could be improved by future work to # provide other ways to pass the password to this program securely, while # still providing secure pipelined file encryption. # # Options # -d decode the pipeline stream, default is to encode # -m MAGIC Use this for the file magic string (generally 8 characters) # # Example encrypt-decypt cycle (type some pass-phrase three times, 2 to # encrypt, 1 to decrypt) # # echo "This is a test message" | encrypt | encrypt -d # ### # # This program will also automatically decrypt a standard OpenSSL 'enc' file # (assuming it is AES encrypted), if it sees the "Salted__" file magic. # However it will only encrypt files using the PBKDF2 password hashed # encryption. This lets you convert from the weaker openssl encryption # easily using a decrypt-encrypt cycle. # # This program can be linked to "vim" to automatically request passwords # for decryption and encryption of files ending in a ".enc" suffix. # # For example add this too your ".vimrc" to enable direct editing of encryoted # files. # # =======8<-------- # " Decrypt/Encrypt .enc files using a "encrypt" command # " A PBKDF v2 (salt+count) aes-256-cbc encryption variant # augroup enc # autocmd! # autocmd BufReadPre,FileReadPre *.enc set binary # autocmd BufReadPre,FileReadPre *.enc set history=0 cmdheight=3 viminfo= # autocmd BufReadPre,FileReadPre *.enc set noswapfile nowritebackup # " # autocmd BufReadPost,FileReadPost *.enc set shell=/bin/sh shellredir=> # autocmd BufReadPost,FileReadPost *.enc '[,']!encrypt -d # autocmd BufReadPost,FileReadPost *.enc set nobinary cmdheight& shell& # autocmd BufReadPost,FileReadPost *.enc let b:encflag=1 # autocmd BufReadPost,FileReadPost *.enc exe "doau BufReadPost ".expand("%:r") # autocmd BufReadPost,FileReadPost *.enc redraw! # " # autocmd BufWritePre,FileWritePre *.enc mark z # autocmd BufWritePre,FileWritePre *.enc set binary cmdheight=3 shell=/bin/sh # autocmd BufWritePre,FileWritePre *.enc '[,']!encrypt # " # autocmd BufWritePost,FileWritePost *.enc undo # autocmd BufWritePost,FileWritePost *.enc set nobinary cmdheight& shell& # autocmd BufWritePost,FileWritePost *.enc 'z # augroup END # =======8<-------- # # Also see my file encryption notes on # http://www.ict.griffith.edu.au/~anthony/info/crypto/file_encrypt.hints # # This is also the same encryption built into my "ks" script for the storage # of "key files". That is files holding the binary master keys, encryption # commands and configuration information for general large scale block or # directory level encryption methods. # # Note "ks" too can use the 'key files' to store encrypted file data (using # this encryption technique, but with extra info). It takes this further as # it includes a specific 'command' in the encrypted file, as a data handler. # That is pipe the decrypted data section into that command for further # processing. For example extract a specific record, and display it # 'read-only', or use it in some way. # # # The code is open-source, and well commented, so you are welcome to modify it # to provide your own personal encryption method. # # Internals... # # Major differences to the standard openssl encrypted files using the # "openssl enc" command (see openssl manpage "enc"), are... # # * Different 'file magic' (or none at all) depending on setting below. # Openssl uses a 8 character magic string of "Salted__". # # * This is followed, as per "openssl" 'enc', by the random salt that was # used during encryption. # # * After the salt an iteritive count (ic) is stored in the file (4 bytes) # This is not provided by "OpenSSL enc", and the major difference. # # * Hardcoded AES encryption as per the "Crypt::OpenSSL::AES" module. # Which is equivelent to using openssl "aes-256-cbc". Changing this # is a trival modification to the script. # # * The Password is PBKDF2 hashed to slow down decryption of the file, # so that it takes approximatally 1/2 seconds to generate the decryption # key. This makes brute force dictionary attacks inpractical. # # The default openssl encryption technique uses a 'fast' password hashing # method that makes brute force dictionary attacks quite feasible, and # thus should be avoided. This is the reason this program was created. # # This means the actual format of the resulting encrypt file is... # * 8 bytes of file magic (Optional) identifing encryption style. # * 16 bytes of random salt # * 4 bytes for the ic count in network number format # * Followed by the encrypted data blocks until EOF # # The key difference to openssl file encryption is the changing (or removal) # of the 'magic' and the additon of the iterative count needed for PBKDF2 # password-key conversion in the header. The encrypted data then uses that # key with the default "standard" final block padding, as per the perl # "Crypt::CBC" module, which does the major work. # ### # # Anthony Thyssen 13 November 2009 A.Thyssen@griffith.edu.au # # Updated 3 September 2010 # use a perl PBKDF2 function rather than a external, trival C program, to # access the OpenSSL library function PKCS5_PBKDF2_HMAC_SHA1(). # use strict; use FindBin; my $PROGNAME = $FindBin::Script; use IPC::Open2; use Crypt::CBC; use Digest::HMAC_SHA1 qw(hmac_sha1); # Selection of the cryptography method to use. my $CryptModule = "Crypt::OpenSSL::AES"; # Advanced Encryption Standard #my $CryptModule = "Crypt::Rijndael"; # Original 'AES' method #my $CryptModule = "Crypt::Blowfish"; # Blowfish 'XOR' Encryption #my $CryptModule = "Crypt::DES"; #my $CryptModule = "Crypt::IDEA"; # Magic Prefix - if you really must identify encrypted files internally # This should be either a undefined, or 8 character string. # However code will accept any file magic string of less than 20 bytes. # #my $magic = ""; # Do not encrypt files with any identifing file magic #my $magic = "Salted__"; # pretend it is a openssl encryption! (DO NOT USE) my $magic = "PBKDF2__"; # A logical file magic for this type of encryption. #my $magic = "KeepOut_"; # A more peronalized bit of magic! my $ic_minimum = 20000; # minimum number of iterations acceptable (input) my $ic_generate = 30000; # minimum number of iterations when encrypting # maximum number is twice this value. # Output script comments as the Usage Documentation # FUTURE: change the above to POD format instead. sub Usage { print STDERR @_, "\n"; @ARGV = ( "$FindBin::Bin/$PROGNAME" ); while( <> ) { next if 1 .. 2; last if /^###/; s/^#$//; s/^# //; print STDERR "Usage: " if 3 .. 3; print STDERR; } exit 10; } # ------------------------------------------------------------------------- # Argument handling my $decrypt = 0; OPTION: # Multi-switch option handling while( @ARGV && $ARGV[0] =~ s/^-(?=.)// ) { $_ = shift; { m/^$/ && do { next }; # Next option m/^-$/ && do { last }; # End of options '--' m/^\?/ && do { Usage }; # Usage Help '-?' s/^d// && do { $decrypt++; next }; s/^m// && do { $magic = shift; next }; # alturnative file magic string Usage( "$PROGNAME: Unknown Option \"-$_\"" ); } continue { next OPTION }; last OPTION; } Usage( "$PROGNAME: Too Many Arguments" ) if @ARGV; # ----------------------------------------------------------------- # Subroutines sub passwd_read { chomp( my $stty_reset = qx(stty -F /dev/tty -g 2>/dev/null) ); system('stty', '-F', '/dev/tty', '-echo'); print STDERR @_; open(TTY, "/dev/tty"); chomp( my $p = ); close TTY; system('stty', '-F', '/dev/tty', $stty_reset); print STDERR "\n"; die "Zero length password not allowed!\n" unless length $p; return $p; } # Password Iterative Hashing Function # # Function by Jochen Hoenicke from the # Palm::Keyring perl module. Found on the PerlMonks Forum # http://www.perlmonks.org/?node_id=631963 # # Usage pbkdf2(password, salt, iter, keylen, prf) sub pbkdf2 { my ($password, $salt, $iter, $keylen, $prf) = @_; my ($k, $t, $u, $ui, $i); $t = ""; for ($k = 1; length($t) < $keylen; $k++) { $u = $ui = &$prf($salt.pack('N', $k), $password); for ($i = 1; $i < $iter; $i++) { $ui = &$prf($ui, $password); $u ^= $ui; } $t .= $u; } return substr($t, 0, $keylen); } # Wrapper around the above to use the "hmac_sha1" hashing function # # This can use either the above 'pure-perl' equivelent solution. # OR the special "pbkdf2" command that breaks out the PKCS5_PBKDF2_HMAC_SHA1() # from the OpenSSL library. Source for program download from # http://www.cit.griffith.edu.au/~anthony/software/#pbkdf2 # # Ideally we should be able to access the openssl library function # directly rather that these to round-a-bout methods. # # Usage pbkdf2_sha1( password, salt, iter ) sub pbkdf2_sha1 { #print STDERR "DEBUG: pbkdf2 ", unpack("H*", $_[1]), " $_[2]\n"; my $k; if( 1 ) { # Use the above function, specify final key length and 'sha1' hashing $k = pbkdf2(@_, 32+16, \&hmac_sha1); } else { # Use the command line "pbkdf2" command to access the openssl library. # this is depreciated in favor of the above perl function. local( *O, *I ); my $pid = open2( \*O, \*I, 'pbkdf2', unpack("H*", $_[1]), $_[2] ); print I $_[0]; close I; chomp( $k = ); close 0; waitpid($pid, 0); die( "Program 'pbkdf2' returns bad status : ", ($? >> 8), "\n" ) if $?; $k = pack("H*", $k); } #print STDERR unpack("H*",$k), "\n"; return( wantarray ? (substr($k,0,32), substr($k,32,16) ) : $k ); } # ----------------------------------------------------------------- if ( ! $decrypt ) { # # Encrypt a file stream # # Generate random (public) constants for this specific encryption my $salt=`openssl rand 16`; # generate random salt my $ic = $ic_generate + int(rand $ic_generate); # iterative hash count # Read Password my $p; while ( 1 ) { $p = passwd_read "Encrypt Password: "; my $p2 = passwd_read "Encrypt Password Again: "; last if $p eq $p2; print STDERR "Password Mismatch -- Try Again!\n"; } # Password + Salt + IC -> Cryptographic Key and IV my ($k,$iv) = pbkdf2_sha1( $p, $salt, $ic ); # Output the header: optional magick, salt, iterative hash count print $magic; # optional file magic (typically 0 or 8 bytes) print $salt; # salt as 16 byte raw binary string print pack("N", $ic); # count as 4 byte newtork number # Encrypt the data: stdin to stdout my $cipher = Crypt::CBC->new( -cipher => $CryptModule, -header => 'none', -literal_key => 1, -key => $k, -iv => $iv ); my $buffer; $cipher->start('encrypting'); while (read(STDIN,$buffer,1024)) { print $cipher->crypt($buffer); } print $cipher->finish; } else { # # Decrypt a file stream # my $buffer; # Read header my $ml = length($magic); # magic_length read(STDIN,$buffer,$ml+16+4) == $ml+16+4 # magick, salt, iteration_count or die("$PROGNAME: encrypted file is not big enough!\n"); # # Handle different types of file magic -- modify to suit. # # We are not 'faking' 'openssl enc' files, and its file magic is present. if ( $magic ne "Salted__" && substr($buffer,0,8) eq "Salted__" ) { # Assuming file is a normal openssl encrypted file # assume AES encryption which is (fairly standard) printf STDERR "$PROGNAME: decrypting openssl encrypted file!\n"; if ( 1 ) { # Use the Crypt::CBC command to decrypt a openssl encrypted file my $p = passwd_read "Decrypt Password: "; my $cipher = Crypt::CBC->new( -salt => 1, # special flag - openssl salted file -cipher => "Crypt::OpenSSL::AES", -key => $p ); undef $p; $cipher->start('decrypting'); print $cipher->crypt($buffer); # decrypt header (get magic & salt) print $cipher->crypt($buffer) while (read(STDIN,$buffer,1024)); # do rest of file print $cipher->finish; # output final block } else { # Use an external 'openssl enc' command to decrypt the data stream. open(OPENSSL,"|-") or exec qw( openssl enc -d -aes-256-cbc ) or die("$PROGNAME: exec to openssl failed : $!\n"); print OPENSSL $buffer; # pipe the header into "openssl enc" print OPENSSL $buffer while read(STDIN, $buffer, 1024); # now read and pipe the rest close(OPENSSL); # finish up, outputing final block } exit($?); } elsif ( $ml && substr($buffer,0,$ml) ne $magic ) { die("$PROGNAME: encrypted file has invalid file magic!\n"); } # Do we need to read more data into our buffer? # At this time we are not looking for any other information so... NO # Really the above should just read the minimum 20 bytes, and once we # know the length of the file magic, that many more bytes appended to # get the needed 16 byte salt and 4 byte ic count. # Extract the 16 byte salt (as a hex string), and the 4 byte iterative count my $salt = substr($buffer, $ml+0, 16); my $ic = unpack("N", substr($buffer, $ml+16, 4) ); #printf STDERR "DEBUG: $ic_minimum <= $ic < %d\n", $ic_generate+$ic_generate; ( $ic_minimum <= $ic || $ic < $ic_generate+$ic_generate ) or die("$PROGNAME: encrypted file is invalid (low iteration count)!\n"); # Read Password my $p = passwd_read "Decrypt Password: "; # Password + Salt + IC -> Cryptographic Key and IV my ($k,$iv) = pbkdf2_sha1( $p, $salt, $ic ); undef $p; # Decrypt the data: stdin to stdout my $cipher = Crypt::CBC->new( -cipher => $CryptModule, -header => 'none', -literal_key => 1, -key => $k, -iv => $iv ); $cipher->start('decrypting'); while (read(STDIN,$buffer,1024)) { print $cipher->crypt($buffer); } print $cipher->finish; } exit 0;