1#!perl 2 3 ## shasum: filter for computing SHA digests (ref. sha1sum/md5sum) 4 ## 5 ## Copyright (C) 2003-2018 Mark Shelor, All Rights Reserved 6 ## 7 ## Version: 6.02 8 ## Fri Apr 20 16:25:30 MST 2018 9 10 ## shasum SYNOPSIS adapted from GNU Coreutils sha1sum. Add 11 ## "-a" option for algorithm selection, 12 ## "-U" option for Universal Newlines support, and 13 ## "-0" option for reading bit strings. 14 15BEGIN { pop @INC if $INC[-1] eq '.' } 16 17use strict; 18use warnings; 19use Fcntl; 20use Getopt::Long; 21use Digest::SHA qw($errmsg); 22 23my $POD = <<'END_OF_POD'; 24 25=head1 NAME 26 27shasum - Print or Check SHA Checksums 28 29=head1 SYNOPSIS 30 31 Usage: shasum [OPTION]... [FILE]... 32 Print or check SHA checksums. 33 With no FILE, or when FILE is -, read standard input. 34 35 -a, --algorithm 1 (default), 224, 256, 384, 512, 512224, 512256 36 -b, --binary read in binary mode 37 -c, --check read SHA sums from the FILEs and check them 38 --tag create a BSD-style checksum 39 -t, --text read in text mode (default) 40 -U, --UNIVERSAL read in Universal Newlines mode 41 produces same digest on Windows/Unix/Mac 42 -0, --01 read in BITS mode 43 ASCII '0' interpreted as 0-bit, 44 ASCII '1' interpreted as 1-bit, 45 all other characters ignored 46 47 The following five options are useful only when verifying checksums: 48 --ignore-missing don't fail or report status for missing files 49 -q, --quiet don't print OK for each successfully verified file 50 -s, --status don't output anything, status code shows success 51 --strict exit non-zero for improperly formatted checksum lines 52 -w, --warn warn about improperly formatted checksum lines 53 54 -h, --help display this help and exit 55 -v, --version output version information and exit 56 57 When verifying SHA-512/224 or SHA-512/256 checksums, indicate the 58 algorithm explicitly using the -a option, e.g. 59 60 shasum -a 512224 -c checksumfile 61 62 The sums are computed as described in FIPS PUB 180-4. When checking, 63 the input should be a former output of this program. The default 64 mode is to print a line with checksum, a character indicating type 65 (`*' for binary, ` ' for text, `U' for UNIVERSAL, `^' for BITS), 66 and name for each FILE. The line starts with a `\' character if the 67 FILE name contains either newlines or backslashes, which are then 68 replaced by the two-character sequences `\n' and `\\' respectively. 69 70 Report shasum bugs to mshelor@cpan.org 71 72=head1 DESCRIPTION 73 74Running I<shasum> is often the quickest way to compute SHA message 75digests. The user simply feeds data to the script through files or 76standard input, and then collects the results from standard output. 77 78The following command shows how to compute digests for typical inputs 79such as the NIST test vector "abc": 80 81 perl -e "print qq(abc)" | shasum 82 83Or, if you want to use SHA-256 instead of the default SHA-1, simply say: 84 85 perl -e "print qq(abc)" | shasum -a 256 86 87Since I<shasum> mimics the behavior of the combined GNU I<sha1sum>, 88I<sha224sum>, I<sha256sum>, I<sha384sum>, and I<sha512sum> programs, 89you can install this script as a convenient drop-in replacement. 90 91Unlike the GNU programs, I<shasum> encompasses the full SHA standard by 92allowing partial-byte inputs. This is accomplished through the BITS 93option (I<-0>). The following example computes the SHA-224 digest of 94the 7-bit message I<0001100>: 95 96 perl -e "print qq(0001100)" | shasum -0 -a 224 97 98=head1 AUTHOR 99 100Copyright (C) 2003-2018 Mark Shelor <mshelor@cpan.org>. 101 102=head1 SEE ALSO 103 104I<shasum> is implemented using the Perl module L<Digest::SHA>. 105 106=cut 107 108END_OF_POD 109 110my $VERSION = "6.02"; 111 112sub usage { 113 my($err, $msg) = @_; 114 115 $msg = "" unless defined $msg; 116 if ($err) { 117 warn($msg . "Type shasum -h for help\n"); 118 exit($err); 119 } 120 my($USAGE) = $POD =~ /SYNOPSIS(.+?)^=/sm; 121 $USAGE =~ s/^\s*//; 122 $USAGE =~ s/\s*$//; 123 $USAGE =~ s/^ //gm; 124 print $USAGE, "\n"; 125 exit($err); 126} 127 128 129 ## Sync stdout and stderr by forcing a flush after every write 130 131select((select(STDOUT), $| = 1)[0]); 132select((select(STDERR), $| = 1)[0]); 133 134 135 ## Collect options from command line 136 137my ($alg, $binary, $check, $text, $status, $quiet, $warn, $help); 138my ($version, $BITS, $UNIVERSAL, $tag, $strict, $ignore_missing); 139 140eval { Getopt::Long::Configure ("bundling") }; 141GetOptions( 142 'b|binary' => \$binary, 'c|check' => \$check, 143 't|text' => \$text, 'a|algorithm=i' => \$alg, 144 's|status' => \$status, 'w|warn' => \$warn, 145 'q|quiet' => \$quiet, 146 'h|help' => \$help, 'v|version' => \$version, 147 '0|01' => \$BITS, 148 'U|UNIVERSAL' => \$UNIVERSAL, 149 'tag' => \$tag, 150 'strict' => \$strict, 151 'ignore-missing' => \$ignore_missing, 152) or usage(1, ""); 153 154 155 ## Deal with help requests and incorrect uses 156 157usage(0) 158 if $help; 159usage(1, "shasum: Ambiguous file mode\n") 160 if scalar(grep {defined $_} 161 ($binary, $text, $BITS, $UNIVERSAL)) > 1; 162usage(1, "shasum: --warn option used only when verifying checksums\n") 163 if $warn && !$check; 164usage(1, "shasum: --status option used only when verifying checksums\n") 165 if $status && !$check; 166usage(1, "shasum: --quiet option used only when verifying checksums\n") 167 if $quiet && !$check; 168usage(1, "shasum: --ignore-missing option used only when verifying checksums\n") 169 if $ignore_missing && !$check; 170usage(1, "shasum: --strict option used only when verifying checksums\n") 171 if $strict && !$check; 172usage(1, "shasum: --tag does not support --text mode\n") 173 if $tag && $text; 174usage(1, "shasum: --tag does not support Universal Newlines mode\n") 175 if $tag && $UNIVERSAL; 176usage(1, "shasum: --tag does not support BITS mode\n") 177 if $tag && $BITS; 178 179 180 ## Default to SHA-1 unless overridden by command line option 181 182my %isAlg = map { $_ => 1 } (1, 224, 256, 384, 512, 512224, 512256); 183$alg = 1 unless defined $alg; 184usage(1, "shasum: Unrecognized algorithm\n") unless $isAlg{$alg}; 185 186my %Tag = map { $_ => "SHA$_" } (1, 224, 256, 384, 512); 187$Tag{512224} = "SHA512/224"; 188$Tag{512256} = "SHA512/256"; 189 190 191 ## Display version information if requested 192 193if ($version) { 194 print "$VERSION\n"; 195 exit(0); 196} 197 198 199 ## Try to figure out if the OS is DOS-like. If it is, 200 ## default to binary mode when reading files, unless 201 ## explicitly overridden by command line "--text" or 202 ## "--UNIVERSAL" options. 203 204my $isDOSish = ($^O =~ /^(MSWin\d\d|os2|dos|mint|cygwin)$/); 205if ($isDOSish) { $binary = 1 unless $text || $UNIVERSAL } 206 207my $modesym = $binary ? '*' : ($UNIVERSAL ? 'U' : ($BITS ? '^' : ' ')); 208 209 210 ## Read from STDIN (-) if no files listed on command line 211 212@ARGV = ("-") unless @ARGV; 213 214 215 ## sumfile($file): computes SHA digest of $file 216 217sub sumfile { 218 my $file = shift; 219 220 my $mode = $binary ? 'b' : ($UNIVERSAL ? 'U' : ($BITS ? '0' : '')); 221 my $digest = eval { Digest::SHA->new($alg)->addfile($file, $mode) }; 222 if ($@) { warn "shasum: $file: $errmsg\n"; return } 223 $digest->hexdigest; 224} 225 226 227 ## %len2alg: maps hex digest length to SHA algorithm 228 229my %len2alg = (40 => 1, 56 => 224, 64 => 256, 96 => 384, 128 => 512); 230$len2alg{56} = 512224 if $alg == 512224; 231$len2alg{64} = 512256 if $alg == 512256; 232 233 234 ## unescape: convert backslashed filename to plain filename 235 236sub unescape { 237 $_ = shift; 238 s/\\\\/\0/g; 239 s/\\n/\n/g; 240 s/\0/\\/g; 241 return $_; 242} 243 244 245 ## verify: confirm the digest values in a checksum file 246 247sub verify { 248 my $checkfile = shift; 249 my ($err, $fmt_errs, $read_errs, $match_errs) = (0, 0, 0, 0); 250 my ($num_fmt_OK, $num_OK) = (0, 0); 251 my ($bslash, $sum, $fname, $rsp, $digest, $isOK); 252 253 local *FH; 254 $checkfile eq '-' and open(FH, '< -') 255 and $checkfile = 'standard input' 256 or sysopen(FH, $checkfile, O_RDONLY) 257 or die "shasum: $checkfile: $!\n"; 258 while (<FH>) { 259 next if /^#/; 260 if (/^[ \t]*\\?SHA/) { 261 $modesym = '*'; 262 ($bslash, $alg, $fname, $sum) = 263 /^[ \t]*(\\?)SHA(\S+) \((.+)\) = ([\da-fA-F]+)/; 264 $alg =~ tr{/}{}d if defined $alg; 265 } 266 else { 267 ($bslash, $sum, $modesym, $fname) = 268 /^[ \t]*(\\?)([\da-fA-F]+)[ \t]([ *^U])(.+)/; 269 $alg = defined $sum ? $len2alg{length($sum)} : undef; 270 } 271 if (grep { ! defined $_ } ($alg, $sum, $modesym, $fname) or 272 ! $isAlg{$alg}) { 273 warn("shasum: $checkfile: $.: improperly " . 274 "formatted SHA checksum line\n") if $warn; 275 $fmt_errs++; 276 $err = 1 if $strict; 277 next; 278 } 279 $num_fmt_OK++; 280 $fname = unescape($fname) if $bslash; 281 next if $ignore_missing && ! -e $fname; 282 $rsp = "$fname: "; 283 ($binary, $text, $UNIVERSAL, $BITS) = 284 map { $_ eq $modesym } ('*', ' ', 'U', '^'); 285 $isOK = 0; 286 unless ($digest = sumfile($fname)) { 287 $rsp .= "FAILED open or read\n"; 288 $err = 1; $read_errs++; 289 } 290 elsif (lc($sum) eq $digest) { 291 $rsp .= "OK\n"; 292 $isOK = 1; 293 $num_OK++; 294 } 295 else { $rsp .= "FAILED\n"; $err = 1; $match_errs++ } 296 print $rsp unless ($status || ($quiet && $isOK)); 297 } 298 close(FH); 299 if (! $num_fmt_OK) { 300 warn("shasum: $checkfile: no properly formatted " . 301 "SHA checksum lines found\n"); 302 $err = 1; 303 } 304 elsif (! $status) { 305 warn("shasum: WARNING: $fmt_errs line" . ($fmt_errs>1? 306 's are':' is') . " improperly formatted\n") if $fmt_errs; 307 warn("shasum: WARNING: $read_errs listed file" . 308 ($read_errs>1?'s':'') . " could not be read\n") if $read_errs; 309 warn("shasum: WARNING: $match_errs computed checksum" . 310 ($match_errs>1?'s':'') . " did NOT match\n") if $match_errs; 311 } 312 if ($ignore_missing && ! $num_OK && $num_fmt_OK) { 313 warn("shasum: $checkfile: no file was verified\n") 314 unless $status; 315 $err = 1; 316 } 317 return($err == 0); 318} 319 320 321 ## Verify or compute SHA checksums of requested files 322 323my($file, $digest); 324my $STATUS = 0; 325for $file (@ARGV) { 326 if ($check) { $STATUS = 1 unless verify($file) } 327 elsif ($digest = sumfile($file)) { 328 if ($file =~ /[\n\\]/) { 329 $file =~ s/\\/\\\\/g; $file =~ s/\n/\\n/g; 330 print "\\"; 331 } 332 unless ($tag) { print "$digest $modesym$file\n" } 333 else { print "$Tag{$alg} ($file) = $digest\n" } 334 } 335 else { $STATUS = 1 } 336} 337exit($STATUS); 338