I've created the following module to allow me to generate a SHA256 signature for a file. (In real life this is used to verify an image file hasn't been amended). The cut down code looks like this:
open System.IO
open System.Text
open System.Security.Cryptography;
// Get the SHA256 hash of a file
let SHA256 (file:FileInfo) =
let FileSHA256Wrap (hashFile : FileStream) (sha256Hash : SHA256) : byte[] =
sha256Hash.ComputeHash(hashFile)
let FileWrap (hashFile : FileStream) : byte[] =
using (SHA256Managed.Create()) (FileSHA256Wrap hashFile)
using (file.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) FileWrap
// Convert the byte[] version of the hash to a printable encoded HEX string
let HexEncoded (hash:byte[]) :string =
let sb = new StringBuilder(hash.Length * 2)
hash |> Array.map (fun c -> sb.AppendFormat("{0:X2}",c)) |> ignore
sb.ToString()
// Get the file hash and convert it to a HEX string
let HexEncodedSHA256 (file:FileInfo) =
file |> SHA256 |> HexEncoded
let fi = new FileInfo("somefile.tif")
HexEncodedSHA256 fi
I've two questions here, with the double using
statements I find the wrap functions help me work out what is going on - but is there a neater and more succinct way to write this without making it hard to work out what is happening?
The implementation of HexEncoded uses a string builder, is there a more functional way to do this.
2 Answers 2
The use
binding is usually better than the using
function. The object is disposed when leaving the scope of the use
binding (when leaving the SHA256
function).
let SHA256 (file:FileInfo) =
use hashFile = file.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
use sha256Hash = SHA256Managed.Create()
sha256Hash.ComputeHash(hashFile)
You can avoid the use of a string builder by using String.concat
. You can also use the F# sprintf
function, which has slightly different format specifier syntax.
let HexEncoded (hash:byte[]) :string =
hash
|> Array.map (sprintf "%02X")
|> String.concat ""
This might be slightly slower than your string builder because it requires an intermediate array. (Note that your implementation could remove the intermediate array by using Array.iter
instead of Array.map
and moving the ignore
to inside the lambda.)
-
\$\begingroup\$ The use binding is clearer, I was sort of trying to re-invent it with my wrap functions. \$\endgroup\$Jackson– Jackson2019εΉ΄05ζ02ζ₯ 12:59:36 +00:00Commented May 2, 2019 at 12:59
I can only agree with TheQuickBrownFox.
You can make even more dedicated functions and then compose them like:
let computeHash (dataStream: Stream) (hasher: HashAlgorithm) = hasher.ComputeHash(dataStream)
let openFile (fileInfo: FileInfo) = fileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
let getHash algorithmFactory fileInfo =
use hasher = algorithmFactory()
use stream = openFile fileInfo
computeHash stream hasher
let hexEncode hash = String.Join ("", hash |> Array.map (sprintf "%02X"))
let fromAlgorithm algorithmFactory fileInfo = fileInfo |> getHash algorithmFactory |> hexEncode
let fromSHA256 = fromAlgorithm SHA256Managed.Create
let fromSHA512 = fromAlgorithm SHA512Managed.Create
let fromMD5 = fromAlgorithm MD5.Create
As shown, in this way it's easy to change the hash algorithm.
let test () =
let fi = new FileInfo(fileName)
printfn "%A" (fromSHA256 fi)
printfn "%A" (fromSHA512 fi)
printfn "%A" (fromMD5 fi)
-
1\$\begingroup\$ Note that F#'s
String.concat
calls .NET'sString.Join
, so you can useString.concat
and maintain the pipeline style like in my answer, without changing behaviour or performance π \$\endgroup\$TheQuickBrownFox– TheQuickBrownFox2019εΉ΄05ζ02ζ₯ 13:57:13 +00:00Commented May 2, 2019 at 13:57 -
\$\begingroup\$ @TheQuickBrownFox: OK, thanks. I just thought I had to do something different than you :-) \$\endgroup\$user73941– user739412019εΉ΄05ζ02ζ₯ 14:23:24 +00:00Commented May 2, 2019 at 14:23
Explore related questions
See similar questions with these tags.