4

Does anyone know a way to calculate de SHA-256 hash of a file without having to load the entire file on memory?

I would be ideal to use apple's CryptoKit library

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
Santiago Alvarez
  • 167
  • 1
  • 12

2 Answers2

6

Create a hasher:

let hasher = SHA256()

With each chunk you read (in whatever way or size you want to read it), update the hasher:

hasher.update(data: blockOfData)

(or if you have an UnsafeRawBufferPointer, you can pass that)

And when you're done, finalize it:

let hash = haser.finalize()
Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • 1
    I have changed the name "block" to "chunk" in above answer as hash functions have a specific block size (e.g. 512 bit for SHA-256 and 1024 bit for SHA-512). I used "chunk" to indicate a (usually much larger) piece of a file of any size. – Maarten Bodewes Feb 11 '21 at 16:48
  • Can anyone show me an implementation of how to actually read a file in chunks in a memory efficient way. I tried using both `FileHandle` and `InputStream` but my memory usage goes up a lot (800 mb). – Santiago Alvarez Feb 11 '21 at 21:05
  • You need to wrap your loop in an `autoreleasepool{}` block so it'll let go of temporary memory you allocate in the loop. – Rob Napier Feb 11 '21 at 21:10
  • I have tried that and i cant seem to make it work. [This is the post i made about it](https://stackoverflow.com/questions/66147377/read-a-file-by-chunks) – Santiago Alvarez Feb 11 '21 at 21:15
  • 1
    Nevermind, i just found out i was never executing my function and was instead executing anotherone. – Santiago Alvarez Feb 11 '21 at 21:27
3

You could use a FileHandle to read the data in chunks, and pass these into the hasher:

import CryptoKit

func getSHA256(forFile url: URL) throws -> SHA256.Digest {
    let handle = try FileHandle(forReadingFrom: url)
    var hasher = SHA256()
    while autoreleasepool(invoking: {
        let nextChunk = handle.readData(ofLength: SHA256.blockByteCount)
        guard !nextChunk.isEmpty else { return false }
        hasher.update(data: nextChunk)
        return true
    }) { }
    let digest = hasher.finalize()
    return digest

    // Here's how to convert to string form
    //return digest.map { String(format: "%02hhx", $0) }.joined()
}
Apptek Studios
  • 512
  • 6
  • 9