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