Extracting and Comparing MD5 and SHA256 Hashes

Intro

This post sprung up due to the nature of the unofficial macOS Rtools toolchain being an “online-based” installer. Meaning, it will attempt to download the appropriate binaries from CRAN, the official gfortran site, and Apple. In particular, Simon Urbanek, who maintains the official CRAN macOS R binary installer, remarked on R-SIG-Mac that the installer is “potentially unsafe and dangerous.”

Unfortunately, there was one flaw in the initial “online” installer: it didn’t check whether the binary downloaded was really the official variant. That is, the old approach taken allowed for a “man-in-the-middle attack.” This kind of attack arises since the request could be intercepted and re-routed by a nefarious actor monitoring web traffic. The actor could then supply a modified binary and the installer wouldn’t know. Thus, the user at the end of the process had the potential to receive software that may be malicious in nature.

With this being said, there are multiple ways to combat a “man-in-the-middle” attack. One way of the common approaches is to check a pre-defined hash against the downloaded file hash. If the hashes are the same, allow the installation to continue. Otherwise, stop the installation and throw an error.

Below are two bash functions that implement the necessary logic to check hashes. These should work on all macOS installations regardless of whether or not Xcode Command Line Tools are installed.

Hash Check Functions

The hash functions perform a string comparison check on a supplied hash (e.g. built into the installer) and the generated file hash obtained from the downloaded file. If the hashes match, return true (e.g. 0). Otherwise, throw an error with exit 1.

Checking an MD5 Hash

The first function is geared to checking an MD5 hash. It uses md5 and awk to extract the hash value. The awk formatting assumes a hash of:

# MD5 (path/to/file.pkg) = c29700c4e7b2914073ef7e741eb105bc
#  1 %         2        %3%      4 
MD5 Check Implementation
#1 Path to File
#2 md5 Hash to Check Against
check_md5_hash() {
	
    # Obtain file hash
    FILE_HASH=$(md5 $1 | awk '{print $4}')
    
    # Check against hash
    if [ "$FILE_HASH" = "$2" ]; then
       echo 0
    else 
       exit 1
    fi
}

Checking an SHA 256 Hash

The second function is geared to checking an SHA 256 hash. It uses shasum and awk to extract the hash value. The awk formatting assumes a hash of:

# 687d2cf2d665614daa45005b1d1ca2942c73575e3372f94d3b60d4d7e6a8f732  path/to/file.pkg
#                               1                                 %       2  
SHA 256 Check Implementation
#1 Path to File
#2 SHA 256 Hash to Check Against
check_sha256_hash() {
	
    # Obtain file hash
    FILE_HASH=$(shasum -a 256 $1 | awk '{print $1}')

    # Check against hash
    if [ "$FILE_HASH" = "$2" ]; then
       echo 0
    else 
       exit 1
    fi
}