Skip to content

Instantly share code, notes, and snippets.

@pa-0
Forked from anonymous1184/HashFile.ahk
Last active December 24, 2023 15:23
Show Gist options
  • Save pa-0/2b2c08ac58ea3ff24375bf8be61d0b96 to your computer and use it in GitHub Desktop.
Save pa-0/2b2c08ac58ea3ff24375bf8be61d0b96 to your computer and use it in GitHub Desktop.
Quick File Hashing

; Version: 2023.04.20.1
; Usages and examples: https://redd.it/m0kzdy
; Configuration ↓
wide := true ; Display the full hashes
; Enables/disabled algorithms
use := {}
use.MD2 := false
use.MD4 := false
use.MD5 := true
use.SHA1 := true
use.SHA256 := true
use.SHA384 := false
use.SHA512 := false
useLower := true ; Hashes in lowercase
; Configuration ↑
;
; Do not edit beyond this point
;
; Some speed-related settings
ListLines Off
SetBatchLines -1
Process Priority,, High
/* A user reported that for some (unknown) reason in one of his drives, the file
being sent to be hashed threw the "No file was selected" message if the filename
contained spaces, this fixes the issue even when is not supposed to happen.
*/
if (A_Args.Count() > 1) {
file := ""
for _, arg in A_Args
file .= arg " "
A_Args[1] := Trim(file)
}
; Verify if a file (not a directory) was sent
if (FileExist(A_Args[1]) ~= "^$|D") {
MsgBox 0x40010, Error, No file was selected.
ExitApp 1
}
/* To avoid issues with spaces the shell works with files in the old 8.3 format,
this expands it to a full long path.
*/
loop Files, % A_Args[1]
A_Args[1] := A_LoopFileLongPath
; Super-globals to avoid declaring global usage
global RadioGroup := "", RefValue, CurrentHash := 0, AlgData := [], AlgDataCount
/* The map of the algorithms it's created in the index zero given that AHK uses
1-based arrays. It has the pertinent data for the GUI to display and stored.
*/
AlgData[0] := []
AlgData[0].Push({ name: "md2", active: false, len: -1, hash: ""})
AlgData[0].Push({ name: "md4", active: false, len: -1, hash: ""})
AlgData[0].Push({ name: "md5", active: false, len: 32, hash: ""})
AlgData[0].Push({ name: "sha1", active: false, len: 40, hash: ""})
AlgData[0].Push({ name: "sha256", active: false, len: 64, hash: ""})
AlgData[0].Push({ name: "sha384", active: false, len: 96, hash: ""})
AlgData[0].Push({ name: "sha512", active: false, len: 128, hash: ""})
/* Loop through the selected algorithms, copying the objects inside the 0-index
to a consecutive index, is always a good measure to delete what's not needed.
*/
for _, active in use {
if (active)
AlgData.Push(AlgData[0, A_Index]) ; Copying an object from [0]
}
AlgData.Delete(0) ; Delete the map to recover memory.
AlgDataCount := AlgData.Count()
init := false
clipHash := ParseClipboard() ; Checking the clipboard for a hash-like string.
; New GUI, fixed-width font.
Gui New, AlwaysOnTop ToolWindow
Gui Font, q5 s10, Consolas
/* In order to work with Radios as a group, only the first one created needs to
have the a variable assigned, all subsequent radios created belong to the group.
This is the variable for the radios, it will be used on the first iteration of -
the `for` loop and it will be cleared (at the end of the first iteration).
*/
v := " vRadioGroup"
for i, alg in AlgData {
/* It uses the properties in the map. `alg.active` is used to check the
radio and provide visual feedback of which hash is in display.
*/
isChecked := (alg.active ? " Checked" : "")
Gui Add, Radio, % "gRadioChanged h20" isChecked v, % alg.name ":"
v := ""
}
Gui Add, Text, h20, Reference:
/* The `newSection` option allows the Gui stacking elements automatically within
another column, same as the variable in the Radio Group it will be used once and
it will be cleared on the first iteration.
*/
newSection := " ys"
for i, alg in AlgData {
/* The variable names of the Edits matches their algorithm counterpart,
which is consecutive in order to be able to identify the algorithm when
updating the hash / showing the tooltip.
For them to have a consistent width are filled with spaces, to later get
the last Edit (will be the longest) and update the width of all others.
*/
val := Format("{: " (wide ? alg.len : 21) "}", "")
Gui Add, Edit, % "ReadOnly -Tabstop h20 vEdit" A_Index newSection, % val
newSection := ""
}
Gui Add, Edit, Uppercase h20 vRefValue gValidate
/* In wide mode, use the next-to-last size as it will be the biggest and update
all of the Edit boxes with the same size.
*/
if (wide) {
GuiControlGet largest, Pos, % "Edit" AlgDataCount
loop % AlgDataCount + 1
GuiControl Move, % "Edit" A_Index, % "w" largestW + 2
}
/* We don't put the reference value when creating the Edit even if something was
found on the Clipboard because it will alter the sizes, we do now:
*/
GuiControl Text, % "Edit" AlgDataCount + 1, % clipHash
; Display the GUI
Gui Show,, % A_Args[1]
if (init) ; Init might hold an array if a hash is found in the Clipboard.
GetHash(init*)
if (!wide) ; When clipping the hashes enable a tooltip with the full hash.
OnMessage(0x200, "MouseMove")
Process Priority,, Normal
return ; End of auto-execute
; Grab the hash from the specified algorithm
GetHash(Alg, Field) {
global wide, useLower
/* To put the gui in "stand by": clear the text in the fields, place a -
"Working" label where appropriate, disable the radio buttons to avoid --
weird behaviors and disable the reference field.
*/
loop % AlgDataCount {
txt := A_Index = Field ? "Working..." : ""
GuiControl Text, % "Edit" A_Index, % txt
GuiControl Disable, % "Button" A_Index
}
ControlFocus Static1, A
GuiControl +ReadOnly, % "Edit" AlgDataCount + 1
if (Alg["hash"] = "") { ; Only hash if we haven't
hash := Hash_File(A_Args[1], Alg["name"])
Alg["hash"] := Format("{:" (useLower ? "L" : "U") "}", hash)
}
; Set the hash value
ControlGet CurrentHash, Hwnd,, % "Edit" Field, % A_Args[1]
hash := wide ? Alg["hash"] : SubStr(Alg["hash"], 1, 8) " ... "
. SubStr(Alg["hash"], -7)
GuiControl Text, % "Edit" Field, % hash
Validate() ; Validate the hash / update the color of the reference Edit
loop % AlgDataCount ; Enable the radios
GuiControl Enable, % "Button" A_Index
; Enable the reference field.
GuiControl -ReadOnly, % "Edit" AlgDataCount + 1
ControlFocus % "Button" Field, A
}
; Display a ToolTip when hover the current hash.
MouseMove(_wParam, _lParam, _Message, hWnd) {
if (hWnd != CurrentHash) {
ToolTip
} else {
GuiControlGet varName, Name, % hWnd
ToolTip % AlgData[SubStr(varName, 5), "hash"]
}
}
/* Check the clipboard contents for hash-like data, if found, trigger at init a
hashing based on the characteristics of the data found.
*/
ParseClipboard() {
global init
txt := Trim(Clipboard, "`t`n`r ")
if (txt = "")
return
if txt is not xdigit
return
lengths := ""
for _, alg in AlgData
lengths .= alg.len ","
lengths := RTrim(lengths, ",")
len := StrLen(txt)
if len not in % lengths
return
for i, alg in AlgData {
if (alg.len = len) {
init := [alg, A_Index]
AlgData[i, "active"] := true
return Format("{:U}", txt)
}
}
}
RadioChanged() { ; Every time a radio changes trigger the proper hashing.
Gui Submit, NoHide
for _, alg in AlgData {
if (RadioGroup = A_Index)
GetHash(alg, RadioGroup)
}
}
Validate() { ; Compare the reference value and change the color of the edit.
Gui Submit, NoHide
for _, alg in AlgData {
if (RadioGroup = A_Index) {
Gui Font, % "c" (alg.hash = RefValue ? "Green" : "Red")
GuiControl Font, % "Edit" AlgDataCount + 1
return
}
}
}
; Gui Labels
GuiClose:
GuiEscape:
ExitApp
return
; Standard directives
#NoEnv
#NoTrayIcon
#SingleInstance Ignore
/*
`Hash_File()` is compatible with `LC_CalcFileHash()` from libcrypt.ahk.
If you are already using libcrypt.ahk, you can remove the code below and use the
`LC_CalcFileHash()` instead.
*/
Hash_File(Path, AlgId) {
out := "ERROR"
, PROV_RSA_AES := 24
, CRYPT_VERIFYCONTEXT := 0xF0000000
;@ahk-neko-ignore-fn 7 line
, MD2 := 0x00008001 ; CALG_MD2
, MD4 := 0x00008002 ; CALG_MD4
, MD5 := 0x00008003 ; CALG_MD5
, SHA1 := 0x00008004 ; CALG_SHA1
, SHA256 := 0x0000800C ; CALG_SHA_256
, SHA384 := 0x0000800D ; CALG_SHA_384
, SHA512 := 0x0000800E ; CALG_SHA_512
, map := StrSplit("0123456789abcdef")
try {
if !(oFile := FileOpen(Path, 0x700))
throw
oFile.Seek(0)
if (!DllCall("advapi32\CryptAcquireContext", "Ptr*", hProv := 0, "Ptr", 0, "Ptr", 0, "UInt", PROV_RSA_AES, "UInt", CRYPT_VERIFYCONTEXT))
throw
if (!DllCall("advapi32\CryptCreateHash", "Ptr", hProv, "UInt", %AlgId%, "UInt", 0, "UInt", 0, "Ptr*", hHash := 0))
throw
VarSetCapacity(pbData, 0x4000000, 0) ; 64 Mb
while (!oFile.AtEOF) {
cbData := oFile.RawRead(&pbData, 0x4000000)
if (!DllCall("advapi32\CryptHashData", "Ptr", hHash, "Ptr", &pbData, "UInt", cbData, "UInt", 0))
throw
}
if (!DllCall("advapi32\CryptGetHashParam", "Ptr", hHash, "UInt", 2, "Ptr", 0, "UInt*", HashLen := 0, "UInt", 0))
throw
VarSetCapacity(Hash, HashLen)
if (!DllCall("advapi32\CryptGetHashParam", "Ptr", hHash, "UInt", 2, "Ptr", &Hash, "UInt*", HashLen, "UInt", 0))
throw
out := ""
loop % HashLen {
val := NumGet(Hash, A_Index - 1, "UChar")
out .= map[(val >> 0x4) + 1] map[(val & 0xF) + 1]
}
}
if (hHash)
DllCall("advapi32\CryptDestroyHash", "Ptr", hHash)
if (hProv)
DllCall("advapi32\CryPtReleaseContext", "Ptr", hProv, "UInt", 0)
return out
}

Last week a post regarding hashing1 from u/S34nfa gave me the last push I needed to stop procrastinating on this.

Currently my son is pretty invested in learning AHK to "help a friend" (I'm guessing it has nothing to do the fact the friend is the girl he's obviously into). Yesterday's lesson was dynamic elements and how to work with a flexible dataset. We did this script2, hope you find it useful or at very least serves you to see the concepts applied.

  • It has a "wide" mode: full contents of the hashes are shown (weird looking when displaying sha512).
  • "Non-wide" mode: shows the first and last 8 characters of the hash with an ellipsis in the middle (full hash on mouse over).
  • Upon start, Clipboard is scanned for text in the form of a hash; if found, it'll automatically start the hashing with the proper algorithm (32 hex chars: MD5, 40: SHA1, 64: SHA256...).
  • It can display any combination between the usual suspects from CryptCreateHash3
  • Gui elements are created/positioned dynamically and don't have fixed sizing (the size is based on content).
  • It has no buttons, keyboard arrows provide navigation between algorithms (all within a radio group).
  • The hash verification field changes color (green/red) for the comparison (making irrelevant not seeing the whole hash).

Screens

  • Wide mode, all algorithms, error image4.
  • Regular mode, all algorithms, match, Tooltip image5.
  • Wide mode, useful algorithms, error image6.
  • Regular mode, useful algorithms, match, Tooltip image7.

Usage

  • Open the "Send To" folder
    • Click Start, select Run, type shell:SendTo
  • Place a shortcut to the script in there
  • In any Explorer use the "Send To" menu with any file.
    • If a hash is already in the clipboard the process is automatic.

Example

Any file will do, AutoHotkey version check helper8 is commonly (ab)used as example. Right click, save as... The hashes below correspond to the version 1.1.33.10.

Algorithm Hash
md2 f7cb776fbad83e5886925aa6ec11d447
md4 7c2784a93fdec04f15cd8c75f47c1f4f
md5 35d44339f9e887425e084c370e1bd957
sha1 e17f981ba7a00534b7fc8d7d10627597f34dbe65
sha256 6a628ad6385ad0db86b52bc29b081683b2b2cb57f97aaab663808641bdf1de2d
sha384 dee3eb24ce137c4468b62e6a47f5da7d932b05f4a99dff38c3c8c69ebd5e591c03630f8e83afae8b20fea1be5c9a4651
sha512 ac4caebe660dd99562b5f23a5b22fb5c819a90b6145e8eb6946ed56ce3c947a9b18a36718ea3a5bcaeb865b7b528906ba359e7b5cb569c9d1460e04bc0e32740

1: The hashing function can be swapped for libcrypt.ahk9's LC_CalcFileHash() if you already have it in your function library.

EDIT: As pointed out by /u/PotatoInBrackets10 the code had no comments and could be confusing, the gist is updated but I totally suck at comments.


Footnotes

  1. https://redd.it/lxlj1a

  2. https://git.io/JXhbO

  3. https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptcreatehash

  4. https://i.imgur.com/4wbCCYM.png

  5. https://i.imgur.com/euyHj3M.png

  6. https://i.imgur.com/UyNFKwB.png

  7. https://i.imgur.com/WnFwDzn.png

  8. https://autohotkey.com/download/1.1/version.txt

  9. https://github.com/ahkscript/libcrypt.ahk#libcryptahk

  10. https://www.reddit.com/u/PotatoInBrackets

MIT License
Copyright (c) 2020 anonymous1184
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment