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