hashvrfy - Shell script to verify the hash

This page publishes the shell script to check multiple checksum files at once, also the Python script to create the featured image.

(There is the Japanese(日本語) page.)

(Last updated date: February 21, 2021)

Preface

When downloading a large file such as an OS ISO image file from a website, I check the hash value of all provided checksum files just in case. However, it is troublesome to manipulate the checksum files one by one, so I wrote the shell script to check multiple checksum files at once. The file is published below.

Moreover, the featured image placed at the top of this page is created with the Python script. The script, also, is published in the last half of this page.

Shell script to verify the hash

Files

I wrote the following four files. These files are licensed under the Creative Commons Zero 1.0 Universal License (CC0 1.0).

  • hashvrfy (Shell script to verify the hash)
  • hashvrfy-en.md (English manual in Pandoc’s Markdown format)
  • hashvrfy-ja.md (Japanese manual in Pandoc’s Markdown format)
  • convert-md.sh (Shell script to convert the above Pandoc’s Markdown files)

The following are files converted from the above Pandoc’s Markdown files with the shell script.

Source code of hashvrfy

Taking the usability into account, the source code of the above hashvrfy shell script file is shown below.

#!/bin/sh

# @(#) Shell script to verify the hash. Version 0.1

# This shell script and its associated files are licensed under
# the Creative Commons Zero 1.0 Universal License (CC0 1.0).
# https://creativecommons.org/publicdomain/zero/1.0/

# Get this command name (file name)
cmd_name=$(basename "$0")

# If the first argument is "--", skip it
if [ $# -ge 1 ] && [ "$1" = "--" ] ; then
  shift
fi

# Check the number of remaining arguments
if [ $# -lt 2 ] ; then
  echo "Usage: ${cmd_name} [--] target_file checksum_file [...]" 1>&2
  exit 2
fi

# Get the target file name (pathname)
target_file="$1"
shift

# Check the target file
if [ ! -r "$target_file" ] ; then
  echo "Cannot read \"${target_file}\" file" 1>&2
  exit 2
fi

# Get the target basename
target_base=$(basename "$target_file")

# Process each checksum file name (pathname)
for checksum_file ; do

  # Check the checksum file
  if [ ! -r "$checksum_file" ] ; then
    echo "Cannot read \"${checksum_file}\" file" 1>&2
    exit 2
  fi

  # Get the checksum basename
  checksum_base=$(basename "$checksum_file")

  # Make a hint to search a type of hash algorithm
  #
  # It is made from the checksum basename.
  # If it includes the target basename,
  # the target basename will be deleted.
  #
  # Examples:
  #                 Example 1   Example 2
  # target_base     os.iso      os.iso
  # checksum_base   MD5SUMS     os.iso.sha256
  # algo_hint       MD5SUMS     .sha256
  #
  algo_hint=$(echo "$checksum_base" | sed -e "s/$target_base//")

  # Search the hint for the type of hash algorithm
  #
  # If the type is found, a hash command will be made from it.
  # If the type is not found, the hash command will be left empty string.
  #
  # Examples:
  #                 Example 1   Example 2
  # algo_hint       MD5SUMS     .sha256
  # algo_type       md5         sha256
  # hash_cmd        md5sum      sha256sum
  #
  hash_cmd= # Set empty string
  for algo_type in md5 sha1 sha224 sha256 sha384 sha512 ; do
    if echo "$algo_hint" | grep -q -i "$algo_type" ; then
      hash_cmd="${algo_type}sum"
      break
    fi
  done

  # Check whether the type of hash algorithm is found or not
  if [ -z "$hash_cmd" ] ; then
    echo "Cannot decide the hash algorithm of \"${checksum_file}\" file" 1>&2
    exit 2
  fi

  # Check whether the hash command is found or not
  if ! type "$hash_cmd" > /dev/null 2>&1 ; then
    echo "Cannot find the \"${hash_cmd}\" command" 1>&2
    exit 2
  fi

  # Get a count of the lines including the target pathname
  # from the checksum file
  #
  # Format examples of the checksum file:
  #
  #   Default-style
  #     123...def  readme.txt    (for text file)
  #     123...def *os-amd64.iso  (for binary file)
  #
  #   BSD-style
  #     SHA256 (os-amd64.iso) = 123...def
  #
  target_dot=$(echo "$target_file" | sed -e "s/\./[.]/g")
  target_ptrn="[[:blank:]][*]?${target_dot}\$|\\(${target_dot}\\)"
  target_cnt=$(grep -E -c "$target_ptrn" "$checksum_file")

  # Check the count
  #
  # Continue processing only if the count is 1.
  # Otherwise, it is an error.
  #
  if [ "$target_cnt" -lt 1 ] ; then
    echo \
      "Not find the line including \"${target_file}\" string in \"${checksum_file}\" file" \
      1>&2
    exit 2
  elif [ "$target_cnt" -gt 1 ] ; then
    echo \
      "found the multiple lines including \"${target_file}\" string in \"${checksum_file}\" file" \
      1>&2
    exit 2
  fi

  # Extract the line including the target pathname
  # from the checksum file
  target_line=$(grep -E "$target_ptrn" "$checksum_file")

  # Execute the hash command with the line including the target pathname
  if echo "$target_line" | $hash_cmd --check --status ; then
    echo "${checksum_file}: OK"
  else
    echo "${checksum_file}: Failed"
    exit 1
  fi

# Correspond to "for checksum_file ; do" line
done

# Exit successfully
exit 0

Examples

In the examples below, multiple lines are entered using a backslash (\) to prevent the line from becoming too long. However, even if you enter it on a single line, it works without problems.

Ubuntu

$ ls -1
MD5SUMS
SHA1SUMS
SHA256SUMS
ubuntu-18.04.3-desktop-amd64.iso
$ hashvrfy ubuntu-18.04.3-desktop-amd64.iso \
> MD5SUMS SHA1SUMS SHA256SUMS
MD5SUMS: OK
SHA1SUMS: OK
SHA256SUMS: OK
$

FreeBSD

$ ls -1
CHECKSUM.SHA256-FreeBSD-12.0-RELEASE-amd64
CHECKSUM.SHA512-FreeBSD-12.0-RELEASE-amd64
FreeBSD-12.0-RELEASE-amd64-dvd1.iso
$ hashvrfy FreeBSD-12.0-RELEASE-amd64-dvd1.iso \
> CHECKSUM.SHA256-FreeBSD-12.0-RELEASE-amd64 \
> CHECKSUM.SHA512-FreeBSD-12.0-RELEASE-amd64
CHECKSUM.SHA256-FreeBSD-12.0-RELEASE-amd64: OK
CHECKSUM.SHA512-FreeBSD-12.0-RELEASE-amd64: OK
$

Apache HTTP Server

$ ls -1
httpd-2.4.41.tar.gz
httpd-2.4.41.tar.gz.md5
httpd-2.4.41.tar.gz.sha1
httpd-2.4.41.tar.gz.sha256
$ hashvrfy httpd-2.4.41.tar.gz httpd-2.4.41.tar.gz.md5 \
> httpd-2.4.41.tar.gz.sha1 httpd-2.4.41.tar.gz.sha256
httpd-2.4.41.tar.gz.md5: OK
httpd-2.4.41.tar.gz.sha1: OK
httpd-2.4.41.tar.gz.sha256: OK
$

Python script to create the featured image

The featured image placed at the top of this page is created based on the Python script shown below.

#!/usr/bin/env python3

# Python script to make the featured-image (SVG format).

# This script is tested with Python 3.6.9, svgwrite 1.4 and noise 1.2.2
# on Ubuntu 18.04.

# Public Sans 1.008 is licensed under the SIL Open Font License 1.1
# https://public-sans.digital.gov/

import math
import svgwrite
import noise

# Attribute values of image
image_size = (image_width, image_height) = (960, 600) # 16:10

# Drawing object of svgwrite
dwg = svgwrite.Drawing(filename='feature-using-font.svg', \
    size=image_size, profile='full')

# Background rectangle of image
dwg.add(dwg.rect(insert=(0, 0), size=image_size, fill='white'))

# Attribute value of lines
line_interval = 12

# Vertical lines
vlines = dwg.add(dwg.g(stroke=svgwrite.rgb(51, 51, 51)))
for line_x in range(0, (image_width +1), line_interval):
    noise_input = line_x/(line_interval *7)
    noise_value = noise.pnoise1(noise_input, base=9)

    sin_input = (line_x/(line_interval *20)) * (2*math.pi)
    sin_value = math.sin(sin_input + noise_value)

    line_width = round(((4.0 * sin_value) +4.5), 4)

    vlines.add(dwg.line(start=(line_x, 0), end=(line_x, image_height), \
        stroke_width=line_width))

# Horizontal lines
hlines = dwg.add(dwg.g(stroke=svgwrite.rgb(50, 50, 255)))
for line_y in range(0, (image_height +1), line_interval):
    noise_input = line_y/(line_interval *7)
    noise_value = noise.pnoise1(noise_input, base=17)

    sin_input = (line_y/(line_interval *16)) * (2*math.pi)
    sin_value = math.sin(sin_input + noise_value)

    line_width = round(((3.0 * sin_value) +3.5), 4)

    hlines.add(dwg.line(start=(0, line_y), end=(image_width, line_y), \
        stroke_width=line_width))

# Background rectangle of text
dwg.add(dwg.rect(insert=(220, 180), size=(520, 240), fill='white'))

# Text 'hashvrfy'
dwg.add(dwg.text('hashvrfy', fill='black', \
    text_anchor='middle', insert=((image_width /2), 300), \
    font_family='Public Sans', font_weight='bold', font_size=108))

# Text 'shell script'
dwg.add(dwg.text('shell script', fill='black', \
    text_anchor='middle', insert=((image_width /2), 386), \
    font_family='Public Sans', font_weight='bold', font_size=46))

# Save to the SVG file
dwg.save()

This script uses the following two external Python packages:

The geometric pattern in the featured image consists only of lines (SVG lines). The intervals between all lines are equal. The widths of the lines are changed regularly based on the sine function. In the process of calculation, the value of Perlin noise is added to change the width of the line somewhat randomly.

When you run this Python script, it will create the SVG file shown below.

This SVG file uses the Public Sans font. If you view this SVG file in an environment where this font is not installed, the font will not be displayed as intended in the Python script above.

Accordingly, the SVG file whose font is outlined using Inkscape is shown below. With this file, the font part will be displayed as intended even in an environment where the Public Sans font is not installed.

Similarly, the file converted to PNG format using Inkscape is the image placed at the top of this page.

Comments

Popular posts from this blog