#!/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);
}