patterngoMinor
Comparing md5.Sum to text from file
Viewed 0 times
filecomparingtextmd5sumfrom
Problem
In learning golang, I wrote a small CLI utility that will take paths as arguments and list out their md5 hash as hex strings. Included are two flags that alter functionality,
The repository is here and code is below:
```
package main
import (
"bytes"
"crypto/md5"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
)
var checkSum, textFile string
// build Flags
func init() {
const (
checkSumDefault = ""
checkSumUsage = "File to check against"
textFileDefault = ""
textFileUsage = "File that has the md5 hash as its only content"
)
flag.StringVar(&checkSum, "check", checkSumDefault, checkSumUsage)
flag.StringVar(&checkSum, "c", checkSumDefault, checkSumUsage)
flag.StringVar(&textFile, "text", textFileDefault, textFileUsage)
flag.StringVar(&textFile, "t", textFileDefault, textFileUsage)
}
func makeHash(fname string) [16]byte {
data, err := ioutil.ReadFile(fname)
if err != nil {
panic(fmt.Sprintf("Failed to read file %s", fname))
}
return md5.Sum(data)
}
func main() {
flag.Parse()
var result int
if checkSum != "" {
// check mode -- against file
checkSumHash := makeHash(checkSum)
argHash := makeHash(flag.Arg(0))
if checkSumHash == argHash {
fmt.Print("They match!")
result = 0
} else {
fmt.Print("No match")
result = 1
}
} else if textFile != "" {
// check mode -- against text
checkSum, err := ioutil.ReadFile(textFile)
if err != nil {
log.Println(err)
log.Fatalf("Can't read text file %s", textFile)
}
c
--check | -c which takes an additional file, hashes both, and returns whether or not they match (and exits with the proper return code), and --text | -t that takes the path to a text file that is presumed to have exactly the contents of the hexified MD5 sum and checks it (as above).The repository is here and code is below:
```
package main
import (
"bytes"
"crypto/md5"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
)
var checkSum, textFile string
// build Flags
func init() {
const (
checkSumDefault = ""
checkSumUsage = "File to check against"
textFileDefault = ""
textFileUsage = "File that has the md5 hash as its only content"
)
flag.StringVar(&checkSum, "check", checkSumDefault, checkSumUsage)
flag.StringVar(&checkSum, "c", checkSumDefault, checkSumUsage)
flag.StringVar(&textFile, "text", textFileDefault, textFileUsage)
flag.StringVar(&textFile, "t", textFileDefault, textFileUsage)
}
func makeHash(fname string) [16]byte {
data, err := ioutil.ReadFile(fname)
if err != nil {
panic(fmt.Sprintf("Failed to read file %s", fname))
}
return md5.Sum(data)
}
func main() {
flag.Parse()
var result int
if checkSum != "" {
// check mode -- against file
checkSumHash := makeHash(checkSum)
argHash := makeHash(flag.Arg(0))
if checkSumHash == argHash {
fmt.Print("They match!")
result = 0
} else {
fmt.Print("No match")
result = 1
}
} else if textFile != "" {
// check mode -- against text
checkSum, err := ioutil.ReadFile(textFile)
if err != nil {
log.Println(err)
log.Fatalf("Can't read text file %s", textFile)
}
c
Solution
To clearly state our problem/task: We want to compare 2 checksums, one supplied as a text being the hexadecimal representation, and the other being an array of the "raw" bytes.
2 forms exist: a hexadecimal representation and raw bytes. To compare them, we need the same representation. So 2 possible paths:
Let's see your proposed solution. If we want to handle leading/trailing spaces, and if the text input may contain lowercased and uppercased hex digits, your solution is as simple as it can be.
A variation may be to convert the text to lowercased, and then we can simply compare strings without
Compare slices (
We may choose to convert the text checksum back to a
We can parse the hex representation simply with
As an extra gain, we don't even have to care about lower or upper case:
And we can convert
Compare arrays (
As a variation of the previous solution, we will use arrays to do the comparison as arrays are comparable.
Since
Compare manually (byte-by-byte)
We can also do the comparison manually, it's relatively easy and straightforward.
But first to do it manually, let's create a simple helper function which tells if a hex digit (the text representation) equals to the raw data:
And with this the solution:
2 forms exist: a hexadecimal representation and raw bytes. To compare them, we need the same representation. So 2 possible paths:
- Convert the second to hex representation
Let's see your proposed solution. If we want to handle leading/trailing spaces, and if the text input may contain lowercased and uppercased hex digits, your solution is as simple as it can be.
A variation may be to convert the text to lowercased, and then we can simply compare strings without
strings.EqualFold(). The lowercase conversion can be done by calling strings.ToLower() or since we already have the input as []byte, by bytes.ToLower():checkSum = bytes.ToLower(bytes.TrimSpace(checkSum))
argHash := makeHash(flag.Arg(0))
if fmt.Sprintf("%x", argHash) == string(checkSum) {
// They match
} else {
// They don't match
}- Convert the first to raw bytes
Compare slices (
[]byte)We may choose to convert the text checksum back to a
[]byte which holds the raw bytes of the checksum (NOT the bytes of the UTF-8 encoded hex representation).We can parse the hex representation simply with
hex.DecodeString(). Or even better: since we have the text checksum as a []byte, we can use hex.Decode() which takes input as a []byte.As an extra gain, we don't even have to care about lower or upper case:
hex.Decode() handles that for us.And we can convert
[16]byte to []byte by simply slicing it. Once we have 2 []byte, we can use bytes.Equal() to compare them (in Go slices are not comparable unlike arrays).checkSum = bytes.TrimSpace(checkSum)
dst := make([]byte, 16)
if _, err := hex.Decode(dst, checkSum); err != nil {
// Invalid input, not hex string or not 16 bytes!
} else {
argHash := makeHash(flag.Arg(0))
if bytes.Equal(argHash[:], dst) {
// They match
} else {
// They don't match
}
}Compare arrays (
[16]byte)As a variation of the previous solution, we will use arrays to do the comparison as arrays are comparable.
Since
makeHash() already returns an array [16]byte, we only need to get the raw bytes of the text checksum into an array. The simplest and fastest is to create an array [16]byte, and pass such a slice to hex.Decode() that shares its backing array with our new array. We can obtain such a slice by simply slicing the array:checkSum = bytes.TrimSpace(checkSum)
dst := [16]byte{}
if _, err := hex.Decode(dst[:], checkSum); err != nil {
// Invalid input, not hex string or not 16 bytes!
} else {
if makeHash(flag.Arg(0)) == dst) {
// They match
} else {
// They don't match
}
}Compare manually (byte-by-byte)
We can also do the comparison manually, it's relatively easy and straightforward.
But first to do it manually, let's create a simple helper function which tells if a hex digit (the text representation) equals to the raw data:
func match(hex, raw byte) bool {
if raw < 10 {
return hex-'0' == raw
}
return hex-'a'+10 == raw || hex-'A'+10 == raw
}And with this the solution:
checkSum = bytes.TrimSpace(checkSum)
argHash := makeHash(flag.Arg(0))
if len(checkSum) != 2*len(argHash) {
// Quick check: length differ, they don't match!
} else {
equal := true
for i, v := range argHash {
if !match(checkSum[i*2], v >> 4) || !match(checkSum[i*2+1], v & 0x0f) {
equal = false
break
}
}
// Now the variable "equal" tells if they are equal
}Code Snippets
checkSum = bytes.ToLower(bytes.TrimSpace(checkSum))
argHash := makeHash(flag.Arg(0))
if fmt.Sprintf("%x", argHash) == string(checkSum) {
// They match
} else {
// They don't match
}checkSum = bytes.TrimSpace(checkSum)
dst := make([]byte, 16)
if _, err := hex.Decode(dst, checkSum); err != nil {
// Invalid input, not hex string or not 16 bytes!
} else {
argHash := makeHash(flag.Arg(0))
if bytes.Equal(argHash[:], dst) {
// They match
} else {
// They don't match
}
}checkSum = bytes.TrimSpace(checkSum)
dst := [16]byte{}
if _, err := hex.Decode(dst[:], checkSum); err != nil {
// Invalid input, not hex string or not 16 bytes!
} else {
if makeHash(flag.Arg(0)) == dst) {
// They match
} else {
// They don't match
}
}func match(hex, raw byte) bool {
if raw < 10 {
return hex-'0' == raw
}
return hex-'a'+10 == raw || hex-'A'+10 == raw
}checkSum = bytes.TrimSpace(checkSum)
argHash := makeHash(flag.Arg(0))
if len(checkSum) != 2*len(argHash) {
// Quick check: length differ, they don't match!
} else {
equal := true
for i, v := range argHash {
if !match(checkSum[i*2], v >> 4) || !match(checkSum[i*2+1], v & 0x0f) {
equal = false
break
}
}
// Now the variable "equal" tells if they are equal
}Context
StackExchange Code Review Q#109942, answer score: 2
Revisions (0)
No revisions yet.