#!/usr/bin/perl # # DESCRIPTION: # # This Perl script calculates and verifies the SHA (256 or 512) hash values # of files and prints them encoded in Base64 instead of hexadecimal. The goal # is to have shorter SHA strings (about 31% less), for example, to be included # on a report. # # Copyright (C) 2019 Miguel Frade # # # LICENSE: # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # use warnings; use Getopt::Long; use Term::ANSIColor; use Digest::SHA qw(sha256 sha512); # this script version my $version = "1.4"; my $last_update = "2019-06-24"; # get script name my $scriptname = $0; my $total_args = $#ARGV + 1; if($total_args <1){ HelpMessage(); } GetOptions( 'check=s' => \ my $check, 'bits=i' => \(my $bits = 256), 'stdin' => \ my $stdin, 'recursive' => \ my $recursive, 'quiet' => \ my $quiet, 'version' => sub {Version()}, 'help' => sub {HelpMessage()} ) or HelpMessage(); if( ($bits != 256) and ($bits != 512) ){ die("$bits bits is not supported!"); } if($check){ # compares the base64 SHA 256|512 values # validates if it's a text file if(!(-T $check)){ die("\'$check\' isn't a text file\n"); } open(FICH, $check) || die ("Error opening file: $!\n"); foreach $line (){ chomp($line); # limit of split into 2 parts to support filenames with 2 consecutive spaces @vector=split(" ", $line, 2); # if less than 2 parts, file format is invalid if($#vector != 1){ die("Invalid file format"); } $read_digest = $vector[0]; $read_filename = $vector[1]; $read_digest_len = length($read_digest); if ($read_digest_len == 44){ $bits=256; }elsif ($read_digest_len == 88){ $bits=512; } else { die("Wrong SHA digest lenght, only 256 and 512 bits are supported"); } if(! -e $read_filename){ print_error("$read_filename: No such file\n"); next; } if(-d $read_filename) { print_error("$read_filename: Is a directory\n"); next; } if(! -r $read_filename) { print_error("$read_filename: Permission denied\n"); next; } if(! -f $read_filename) { # make sure it's not a special file type print_error("$read_filename: File type not supported\n"); next; } $calc_digest = b64sha($bits,$read_filename); if($calc_digest eq $read_digest){ print_ok("$read_filename\n"); } else { print_fail("$read_filename\n"); } } close(FICH); } else { # calculates and prints the base64 SHA 256|512 values if ($stdin) { # from STDIN $sha = Digest::SHA->new($bits); $sha->add(); $digest = $sha->b64digest; # padding '=' while (length($digest) % 4) { $digest .= '='; } print("$digest -\n"); } else { # from file system foreach my $filename (@ARGV) { if($recursive){ # traverse directories if(-d $filename){ # is a directory! dir_traverse($filename); } else { # check files also if(! -e $filename){ print_error("$filename: No such file or directory\n"); next; } if(! -r $filename) { print_error("$filename: Permission denied\n"); next; } if( -f $filename) { # make sure it's not a special file type $digest = b64sha($bits, $filename); print("$digest $filename\n"); } else { print_error("$filename: File type not supported\n"); } } } else { # non traversal operation if(! -e $filename){ print_error("$filename: No such file\n"); next; } if(-d $filename) { print_error("$filename: Is a directory\n"); next; } if(! -r $filename) { print_error("$filename: Permission denied\n"); next; } if( -f $filename) { # make sure it's not a special file type $digest = b64sha($bits, $filename); print("$digest $filename\n"); } else { print_error("$filename: File type not supported\n"); } } } # foreach } # from files } #################################################################### sub b64sha{ (@args) = @_; $bits = $args[0]; $filename = $args[1]; $sha = Digest::SHA->new($bits); $sha->addfile($filename); $digest = $sha->b64digest; # padding '=' while (length($digest) % 4) { $digest .= '='; } return $digest; } #################################################################### sub dir_traverse { my ($obj) = shift; if (opendir DIR, $obj) { # it's a directory for my $entry (sort readdir DIR) { next if $entry eq "." or $entry eq ".."; dir_traverse("$obj/$entry"); # recursive } } else { # it's a file if(-e $obj){ # exists? if(-r $obj) { # is readable ? if( -f $obj) { # it's not a special file type ? $digest = b64sha($bits, $obj); print("$digest $obj\n"); } else { print_error("$obj: File type not supported\n"); } } else { print_error("$obj: Permission denied\n"); } } else { print_error("$obj: No such file\n"); } } } #################################################################### sub print_error { my ($msg) = shift; if(! $quiet){ print STDERR colored("[WARN]", 'yellow'); print STDERR " $msg"; } } #################################################################### sub print_ok { my ($msg) = shift; print colored("[ OK ]", 'green'); print " $msg"; } #################################################################### sub print_fail { my ($msg) = shift; print colored("[FAIL]", 'red'); print " $msg"; } #################################################################### sub HelpMessage{ print("$scriptname parameters:\n"); print("\tfile1 file2 ... --> Calculates and prints the SHA values, encoded in Base64, of the files\n"); print("\t--check|-c filename --> Compares the SHA values stored in filename with the calculated ones\n"); print("\t and prints [ OK ] or [FAIL]. It is able to automatically detect if it's\n"); print("\t a 256 or 512 bits version of SHA\n"); print("\t--bits|-b 256|512 --> Choose SHA bit lenght, default value is 256\n"); print("\t--recursive|-r --> recursively traverse directories\n"); print("\t--stdin|-s --> read data from STDIN\n"); print("\t--quiet|-q --> omit error messages\n"); print("\t--version|-v --> Print script version\n"); print("\t--help|-h --> Print this help\n"); print("\n"); print("\tUsage examples:\n"); print("\t\t$scriptname somefile.txt\n"); print("\t\t$scriptname -b 512 *.doc\n"); print("\t\t$scriptname *.txt > SHA256.txt\n"); print("\t\t$scriptname -c SHA256.txt\n"); print("\t\t$scriptname -r dir_name\n"); print("\t\techo -n \"Hello\" | $scriptname -s\n"); print("\n"); exit(0); } #################################################################### sub Version{ print("Base64SHA script:\n"); print("\tVersion $version ($last_update)\n"); print("\tGNU General Public License v3.0\n"); print("\tCopyright (C) 2019 Miguel Frade\n"); print("\n"); exit(0); }