12

Strange enough, but I can't find a portable application for calculating one distinct hash (type, like SHA256, SHA512, Whirlpool, SHA3?) for a selected file! This is all I've found:

  1. RapidCRCUnicodePortable - it has a very user-unfriendly behavior. What do I see? I open this app, click "Open Files" button and select my file. I see the filename in the list and a strange message at the right: "No hash found". Okay, I click "SHA256 file" button and see this message: "You have to calculate the sha256 checksums first. Click OK to do that now." I click OK. The filename is now duplicated in the list with the same "No hash found" text to the right, but now – congrats! – I can finally copy the found SHA256 hash to clipboard. And what is strange is that this app crashes in my system when I remove files from the list. I can't understand how is this bug allowed to exist. It makes me think that this software is unstable.
  2. ChecksumControlPortable is only for creating SFV/MD5 files. And by the way, it can't even select a separate file via Explorer, it uses drag-and-drop interface, but I hate this type of interface. I never use drag and drop.
  3. HashGenerator is wasting a lot of time and CPU cycles calculating all types of hashes at once. It makes this app extremely user-unfriendly when dealing with huge files.

There is Multihasher, but it does not support some important hash types, such as SHA-3 and Whirlpool.
HashTab has all what I need, but... what a pity! It's an installable dll for system integration without portable application.
Actually, I'm a bit shocked by this situation. I never expected I wouldn't be able to find the appropriate piece of software for such a common problem.

4
  • WinMD5 only calculates MD5. Commented Dec 9, 2015 at 23:08
  • HashMyFiles calculates only CRC32, MD5 and SHA1 Commented Dec 9, 2015 at 23:22
  • For Windows? If yes, may it require .NET? What hashes should it at least support?
    – wb9688
    Commented Dec 10, 2015 at 15:01
  • @wb9688: "may it require .NET?" No. .NET dependency means that the user will be forced to install the framework before using the app. This is not always possible. And, as far as I understand, there is no portable .NET. "What hashes should it at least support?" SHA-2 is required, I suppose. SHA-3 and Whirlpool would be nice to have, I think. Commented Dec 14, 2015 at 8:45

2 Answers 2

1

Some time ago I've made a little command line utility for myself. Download it if you find it useful.

The program was never published before (or signed) so it may be detected as malware (at least VirusTotal says so).

It's written in Python 3. This is the source:

# -*- coding: utf-8 -*-

import os.path
import pprint
import time
import json
import sys
import re
import hashlib
import zlib
import sha3 # This adds sha_3 functions to the hashlib module, install with 'pip install pysha3'

class crc32(object):
    name = 'crc32'
    digest_size = 4
    block_size = 1

    def __init__(self, arg=''):
        arg = arg.encode()
        self.__digest = 0
        self.update(arg)

    def copy(self):
        copy = super(self.__class__, self).__new__(__class__)
        copy.__digest = self.__digest
        return copy

    def digest(self):
        return self.__digest

    def hexdigest(self):
        return '{:08x}'.format(self.__digest)

    def update(self, arg):
        self.__digest = zlib.crc32(arg, self.__digest) & 0xffffffff

hashlib.crc32 = crc32

def md4File(path, block_size=256*128):
    md4 = hashlib.new("md4")
    with open(path,'rb') as f: 
        for chunk in iter(lambda: f.read(block_size), b''): 
            md4.update(chunk)
    return md4.hexdigest()

def md5File(path, block_size=256*128):
    md5 = hashlib.md5()
    with open(path,'rb') as f: 
        for chunk in iter(lambda: f.read(block_size), b''): 
            md5.update(chunk)
    return md5.hexdigest()

def sha1File(path, block_size=256*128):
    sha1 = hashlib.sha1()
    with open(path,'rb') as f: 
        for chunk in iter(lambda: f.read(block_size), b''): 
            sha1.update(chunk)
    return sha1.hexdigest()

def crc32File(path, block_size=256*128):
    crc32 = hashlib.crc32()
    with open(path,'rb') as f: 
        for chunk in iter(lambda: f.read(block_size), b''): 
            crc32.update(chunk)
    return crc32.hexdigest()

def sha224File(path, block_size=256*128):
    sha224 = hashlib.sha224()
    with open(path,'rb') as f: 
        for chunk in iter(lambda: f.read(block_size), b''): 
            sha224.update(chunk)
    return sha224.hexdigest()

def sha256File(path, block_size=256*128):
    sha256 = hashlib.sha224()
    with open(path,'rb') as f: 
        for chunk in iter(lambda: f.read(block_size), b''): 
            sha256.update(chunk)
    return sha256.hexdigest()

def sha384File(path, block_size=256*128):
    sha384 = hashlib.sha384()
    with open(path,'rb') as f: 
        for chunk in iter(lambda: f.read(block_size), b''): 
            sha384.update(chunk)
    return sha384.hexdigest()

def sha512File(path, block_size=256*128):
    sha512 = hashlib.sha512()
    with open(path,'rb') as f: 
        for chunk in iter(lambda: f.read(block_size), b''): 
            sha512.update(chunk)
    return sha512.hexdigest()

def ripemd160File(path, block_size=256*128):
    rmd160 = hashlib.new("ripemd160")
    with open(path,'rb') as f: 
        for chunk in iter(lambda: f.read(block_size), b''): 
            rmd160.update(chunk)
    return rmd160.hexdigest()

def dsaFile(path, block_size=256*128):
    dsa = hashlib.new("dsa")
    with open(path,'rb') as f: 
        for chunk in iter(lambda: f.read(block_size), b''): 
            dsa.update(chunk)
    return dsa.hexdigest()

def whirlpoolFile(path, block_size=256*128):
    wrp = hashlib.new("whirlpool")
    with open(path,'rb') as f: 
        for chunk in iter(lambda: f.read(block_size), b''): 
            wrp.update(chunk)
    return wrp.hexdigest()

def sha3_224File(path, block_size=256*128):
    sha3_224 = hashlib.sha3_224()
    with open(path,'rb') as f: 
        for chunk in iter(lambda: f.read(block_size), b''): 
            sha3_224.update(chunk)
    return sha3_224.hexdigest()

def sha3_256File(path, block_size=256*128):
    sha3_256 = hashlib.sha3_256()
    with open(path,'rb') as f: 
        for chunk in iter(lambda: f.read(block_size), b''): 
            sha3_256.update(chunk)
    return sha3_256.hexdigest()

def sha3_384File(path, block_size=256*128):
    sha3_384 = hashlib.sha3_384()
    with open(path,'rb') as f: 
        for chunk in iter(lambda: f.read(block_size), b''): 
            sha3_384.update(chunk)
    return sha3_384.hexdigest()

def sha3_512File(path, block_size=256*128):
    sha3_512 = hashlib.sha3_512()
    with open(path,'rb') as f: 
        for chunk in iter(lambda: f.read(block_size), b''): 
            sha3_512.update(chunk)
    return sha3_512.hexdigest()


def getJson(results):
    jdump = json.dumps(results, ensure_ascii=False)
    return re.sub('[^\s!-~]', '?', jdump)

def getString(results):
    text = ""
    for i in results:
        text += "File: " + i + "\n"

        for j in results[i]:
            text += j[0] + ": " + j[1] + "\n"

        text += "\n"

    return re.sub('[^\s!-~]', '?', text[:-2])


def showHelp(name):
    nameT = name.replace("\\","/").split("/")[-1]
    helpText = """
//////////////////////
/ File/Folder hasher /
//////////////////////

Usage: %s  path [-md4]       [-md5]
            [-sha1]      [-crc32]
            [-sha224]    [-sha256]
            [-sha384]    [-sha512]
            [-ripemd160] [-whirlpool]
            [-sha3_224]  [-sha3_256]
            [-sha3_384]  [-sha3_512]

            [-all]
            [-file <file name>]
            [-json]

Options:
    path    Path to file or directory to hash

    -all    Use all hash algorithms.
    -file   Save hash report to a text file
    -json   Format output in json

Hash algorithm options:
    -md4    
    -md5
    -sha1
    -crc32
    -sha224
    -sha256
    -sha384
    -sha512
    -ripemd160
    -whirlpool
    -sha3_224
    -sha3_256
    -sha3_384
    -sha3_512
"""
    print(helpText % nameT)

def main():
    args = sys.argv

    if len(args) < 3:
        print("Not enough arguments!")
        showHelp(args[0])
        sys.exit(1)

    allowed = [
    "-file",
    "-all",
    "-md4",
    "-md5",
    "-sha1",
    "-crc32",
    "-sha224",
    "-sha256",
    "-sha384",
    "-sha512",
    "-ripemd160",
    "-whirlpool",
    "-json",
    "-raw",
    "-sha3_224",
    "-sha3_256",
    "-sha3_384",
    "-sha3_512",
    ]

    hashes = [
    "-md4",
    "-md5",
    "-sha1",
    "-crc32",
    "-sha224",
    "-sha256",
    "-sha384",
    "-sha512",
    "-ripemd160",
    "-whirlpool",
    "-sha3_224",
    "-sha3_256",
    "-sha3_384",
    "-sha3_512",
    ]

    dataContain = [
    "-file",
    ]

    b = 2
    for i in args[b:]:
        if args.count(i) > 1:
            print("Repeating options: " + i)
            showHelp(args[0])
            sys.exit(1)

        if not i in allowed and args[b-1] not in dataContain:
            print("Invalid option: " + i)
            print(args[b+1])
            showHelp(args[0])
            sys.exit(1)

        b += 1
    else:
        if args[-1] in dataContain:
            print("'%s' option must have a file path next to it." % i)
            showHelp(args[0])
            sys.exit(1) 

    for i in args[2:]:
        if i in hashes + ["-all"]:
            break

    else:
        print("No hashes specified.")
        showHelp(args[0])
        sys.exit(1)         

    collisions = {
    "-all" : ["-md4", "-md5", "-sha1", 
              "-crc32", "-sha224", "-sha256", 
              "-sha384", "-sha512", "-ripemd160", 
              "-whirlpool", "-sha3_224", "-sha3_256", 
              "-sha3_384", "-sha3_512",]
    }

    for i in collisions:
        if i in args:
            for j in collisions[i]:
                if j in args:
                    print("Colliding options. (" + i + " and " + j + ")")
                    showHelp(args[0])
                    sys.exit(1)

    fileName = args[1]
    fileNameT = fileName.replace("\\", "/").split("/")[-1]
    if os.path.isfile(fileName):
        ftype = "file"

    elif os.path.isdir(fileName):
        ftype = "directory"

    else:
        print("File or directory does not exits or you don't have permission to read it.")
        showHelp(args[0])
        sys.exit(1) 

    funcTable = {
    "-md4"        : ("MD4", md4File),
    "-md5"        : ("MD5", md5File),
    "-sha1"       : ("SHA1", sha1File),
    "-crc32"      : ("CRC32", crc32File),
    "-sha224"     : ("SHA224", sha224File),
    "-sha256"     : ("SHA256", sha256File),
    "-sha384"     : ("SHA384", sha384File),
    "-sha512"     : ("SHA512", sha512File),
    "-ripemd160"  : ("Ripemd160", ripemd160File),
    "-whirlpool"  : ("Whirlpool", whirlpoolFile),
    "-sha3_224"   : ("SHA3-224", sha3_224File),
    "-sha3_256"   : ("SHA3-256", sha3_256File),
    "-sha3_384"   : ("SHA3-384", sha3_384File),
    "-sha3_512"   : ("SHA3-512", sha3_512File),
    }
    funcList = []

    if "-all" in args:
        for i in hashes:
            funcList.append(funcTable[i])
    else:
        for i in hashes:
            if i in args:
                funcList.append(funcTable[i])

    try:
        if ftype == "file":
            print("Hashing...\n")

            results = {}
            results[fileNameT] = []
            st = time.time()
            try:
                for function in funcList:
                    hash = function[1](fileName)
                    results[fileNameT].append([function[0], hash])

            except KeyboardInterrupt:
                print("Hashing canceled.")
                sys.exit(2)

            except PermissionError:
                print("You don't have permission to access the file.")
                showHelp(args[0])
                sys.exit(2)         

            et = time.time()

            if "-json" in args:
                strResults = getJson(results)

            elif "-raw" in args:
                strResults = results

            else:
                strResults = getString(results)

            if "-file" in args:
                print("Writing file...")
                f = open(args[args.index("-file") + 1], "w")
                f.write(strResults)
                f.close()
                print("File saved sucessfully in %s seconds." % str("{0:.2f}".format(et-st)))

            else:
                print(strResults)
                print("\nCalculated in %s seconds." % str("{0:.2f}".format(et-st)))

            sys.exit(0)

        else:
            filenum = 0
            print("Scanning files...")

            st = time.time()
            try:
                for subdir, dirs, files in os.walk(fileName):
                    for file in files:
                        filenum += 1        

            except KeyboardInterrupt:
                print("Files not scanned. Progress cannot be tracked.")
                filenum = -1

            if filenum == 0:
                print("Directory is empty or you don't have permission to read it.")
                showHelp(args[0])
                sys.exit(1)

            print("Number of files: " + str(filenum))
            print("Hashing...\n")

            results = {}

            onfile = 1
            passed = 0
            try:
                if filenum == -1:
                    print(" |     Hashing...     | ???%", end="\r")

                for subdir, dirs, files in os.walk(fileName):
                    for file in files:
                        results[os.path.join(subdir, file)] = []

                        if filenum != -1:
                            perc = onfile/filenum
                            percnum = "{0:.2f}".format(perc*100)
                            toprint = " |" + ("#" * int((20*perc))) + " " * int(20-int(20*perc))  + "| " + percnum + "%"

                            print(toprint, end="\r")

                        for function in funcList:
                            try:
                                try:
                                    hash = function[1](os.path.join(subdir, file))
                                    results[os.path.join(subdir, file)].append([function[0], hash])

                                except PermissionError:
                                    results[os.path.join(subdir, file)].append(["Error", "Access denied"])
                                    passed += 1

                                except FileNotFoundError:
                                    results[os.path.join(subdir, file)].append(["Error", "File not found"])
                                    passed += 1
                            except Exception as e:
                                results[os.path.join(subdir, file)].append(["Error", str(e)])
                                passed += 1

                        onfile += 1

                else:
                    print(" |####################| 100%       \n")



            except KeyboardInterrupt:
                print("Hashing canceled.                 ")
                sys.exit(2)

            et = time.time()
            print("Passed %s files" % str(passed))

            if "-json" in args:
                strResults = getJson(results)

            elif "-raw" in args:
                strResults = results

            else:
                strResults = getString(results)

            if "-file" in args:
                print("Writing file...")
                f = open(args[args.index("-file") + 1], "wb")
                f.write(strResults.encode())
                f.close()
                print("\nFile saved sucessfully in %s seconds." % str("{0:.2f}".format(et-st)))

            else:
                print(strResults)
                print("\nCalculated in %s seconds." % str("{0:.2f}".format(et-st)))

            sys.exit(0)

    except MemoryError:
        print("Maximum memory reached. Please consider using an x64 version of the program and upgrading your RAM.\n")

if __name__ == "__main__":
    main()

It can hash a file or a folder with these algorithms:

  • md4

  • md5

  • sha1
  • crc32
  • sha224
  • sha256
  • sha384
  • sha512
  • ripemd160
  • whirlpool
  • sha3_224
  • sha3_256
  • sha3_384
  • sha3_512

  • You can also store the hashed values in a file or format them with json for easy parsing.

The nice thing with this is that you can easily make a .bat file to automate it.

If you actually need this program, but trust your antivirus more than me, compile the above source with pyinstaller.

  1. Install python 3 (watch out for 32bit/64bit versions)
  2. Install pyinstaller: pip install pyinstaller
  3. Save source to a file called hasher.py
  4. Run the command: pyinstaller hasher.py 4.5 Opotionally run: pyinstaller hasher.py --onefile if you want just a single file executable
  5. Profit

Note that the compatibility of the compiled exe was not tested because I don't have time.

Click me for download.

0

rhash is a cmdline tool that can compute individual hashes.

You can use my shellex.bat script to add an entry in the explorer context menu that shows the hash in a console window.

For instance, this will add an "CRC32" command to every file (paste it and execute from a batch file):

call shellex * CRC32 ""%~d0\PortableApps\ConEmu\ConEmu.exe" /cmd "%PROGRAMFILES%\Rhash\rhash.exe" -C --simple ""%%%%1""" 

Another option using certuil is here.

Btw, for small files i actually prefer HashMyFiles, because of the more user-friendly GUI.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.