Contents

redmadrobot/golden-key

Swift wrapper around CommonCrypto and Security frameworks

Common Digest

Supported algorithms: MD2, MD4, MD5, SHA1, SHA224, SHA256, SHA384, SHA512.

Stream hasher.

let sha = SHA256()
sha.update(data: Data("12".utf8))
sha.update(data: [1, 2])

let degest = sha.finalize()

One shot.

let digest = SHA256.hash(data: Data("123".utf8))

All hash functions return type conform to Digest protocol . You can convert digest to common types like a Data and [UInt8].

let data = Data(digest)
let bytes: [UInt8] = Array(digest)

HMAC

hash-based message authentication code

Stream hasher.

let key = Data("secret_key".utf8)
let hmac = HMAC(algorithm: .md5, key: key)

hmac.update(data: Data("ab".utf8))
hmac.update(data: Data("cd".utf8))
let hash = hmac.finalize()

One shot.

let key = Data("secret_key".utf8)
let data = Data("abcd".utf8)

let hash = HMAC.hash(algorithm: .sha224, data: data, key: key)

Setup for development

$ mkdir gyb
$ cd gyb
$ wget https://github.com/apple/swift/raw/master/utils/gyb
$ wget https://github.com/apple/swift/raw/master/utils/gyb.py
$ chmod +x gyb

Efficient way to calculate hash of a large file

To calculate hash of a large file use DispatchIO.

In the example below DispatchIO reads a file by chunks and process every chunk by calling update method of SHA256 class.

The default chunk size is set to 128 MB to limit maximum memory usage.

[Screenshot]

import Foundation
import GoldenKey

final class FileHash {
    
    private let workQueue: DispatchQueue
    private let dispatchIO: DispatchIO
    
    /// Opens and prepares a file for reading.
    /// - Parameter fileURL: URL of the file.
    /// - Parameter workQueue: DispatchQueue on which to perform work (read and calculating hash).
    /// - Parameter queue: DispatchQueue of the completion handler.
    /// - Parameter completion: Calls when the file closed. Useful when you want to calculate hash of multiple files sequentially.
    init(
        fileURL: URL,
        workQueue: DispatchQueue = .init(label: "work", qos: .userInitiated),
        queue: DispatchQueue = .main,
        completion: (() -> Void)? = nil) throws {
        
        self.workQueue = workQueue
        
        let fileHandle = try FileHandle(forReadingFrom: fileURL)
        
        dispatchIO = DispatchIO(
            type: .stream,
            fileDescriptor: fileHandle.fileDescriptor,
            queue: queue,
            cleanupHandler: { _ in
                fileHandle.closeFile()
                queue.async { completion?() }
            }
        )
        dispatchIO.setLimit(lowWater: Int.max)
    }
    
    /// Calculates hash of the file
    /// - Parameter hashFunctionType: Hash function type. SHA256.self for example.
    /// - Parameter chunkSize: Max memory usage. 128 MB by default.
    /// - Parameter queue: DispatchQueue of the completion handler.
    /// - Parameter completion: Completion handler.
    func calculateHash(
        hashFunctionType: Digest.Type,
        chunkSize: Int = 128 * 1024 * 1024,
        queue: DispatchQueue = .main,
        completion: @escaping (Result<Data, POSIXError>) -> Void) {
        
        let hashFunction = hashFunctionType.init()
        
        func readNextChunk() {
            dispatchIO.read(offset: 0, length: chunkSize, queue: workQueue) { [weak self] (done, data, error) in
                guard let self = self else { return }
                
                guard error == 0 else {
                    let error = POSIXError(POSIXErrorCode(rawValue: error)!)
                    self.dispatchIO.close(flags: .stop)
                    queue.async {
                        completion(.failure(error))
                    }
                    return
                }
                
                guard let data = data else { return }
                
                if data.isEmpty == false {
                    data.regions.forEach { hashFunction.update(data: $0) }
                }
                
                if done, data.isEmpty {
                    self.dispatchIO.close()
                    
                    let digest = hashFunction.finalize()
                    queue.async {
                        completion(.success(digest))
                    }
                }
                
                if done, data.isEmpty == false {
                    readNextChunk()
                }
            }
        }
        
        readNextChunk()
    }
    
}

Usage example


extension Data {
    /// Presents Data in hex format
    var hexDescription: String {
        return reduce("") {$0 + String(format: "%02x", $1)}
    }
}

do {
    let fileURL = URL(string: "absolute_path_to_file")!
    let fileHash = try FileHash(fileURL: fileURL)
    fileHash.calculateHash(hashFunctionType: SHA256.self) { result in
        switch result {
        case .success(let hash):
            print(hash.hexDescription)
        case .failure(let error):
            print(error.localizedDescription)
        }
    }
} catch (let error) {
    print(error.localizedDescription)
}

Package Metadata

Repository: redmadrobot/golden-key

Default branch: master

README: README.md