如何使用swift 3在iOS上散列文件?

时间:2017-03-21 17:23:53

标签: ios swift hash commoncrypto

我有许多文件将存在于服务器上。用户可以在设备上创建这些类型的文件(plist),然后上传到所述服务器(CloudKit)。我想通过内容使它们独一无二(唯一的方法应该适应创建日期的变化)。我的理解是我应该散列这些文件以获取它们的唯一文件名。我的问题是:

  1. 我的理解是正确的,我想要的是哈希函数吗?
  2. 我应该使用哪种功能(来自CommonCrypto)。
  3. 我需要的是摘要?
  4. 我将如何在代码中进行操作? (我假设这应该在NSData实例上进行哈希处理?)。我从谷歌搜索的理解是,我需要一个桥接头,但除此之外,使用CommonCrypto困扰我。如果使用第一方API(Apple)有一种更简单的方式,我很满意(我希望尽可能避免使用第三方代码)。
  5. 非常感谢!

4 个答案:

答案 0 :(得分:8)

创建每个文件的加密哈希值,您可以将其用于唯一性比较。 SHA-256是一个当前的哈希函数,在iOS上使用Common Crypto非常快,在iPhone 6S上SHA256将处理大约1GB /秒减去I / O时间。如果您需要更少的字节,只需截断哈希。

使用Common Crypto(Swift3)

的示例

用于散列字符串:

func sha256(string: String) -> Data {
    let messageData = string.data(using:String.Encoding.utf8)!
    var digestData = Data(count: Int(CC_SHA256_DIGEST_LENGTH))

    _ = digestData.withUnsafeMutableBytes {digestBytes in
        messageData.withUnsafeBytes {messageBytes in
            CC_SHA256(messageBytes, CC_LONG(messageData.count), digestBytes)
        }
    }
    return digestData
}
let testString = "testString"
let testHash = sha256(string:testString)
print("testHash: \(testHash.map { String(format: "%02hhx", $0) }.joined())")

let testHashBase64 = testHash.base64EncodedString()
print("testHashBase64: \(testHashBase64)")
  

输出:
  testHash:4acf0b39d9c4766709a3689f553ac01ab550545ffa4544dfc0b2cea82fba02a3
  testHashBase64:Ss8LOdnEdmcJo2ifVTrAGrVQVF / 6RUTfwLLOqC + 6AqM =

注意:添加到您的桥接标题:

#import <CommonCrypto/CommonCrypto.h>

对于散列数据:

func sha256(data: Data) -> Data {
    var digestData = Data(count: Int(CC_SHA256_DIGEST_LENGTH))

    _ = digestData.withUnsafeMutableBytes {digestBytes in
        data.withUnsafeBytes {messageBytes in
            CC_SHA256(messageBytes, CC_LONG(data.count), digestBytes)
        }
    }
    return digestData
}

let testData: Data = "testString".data(using: .utf8)!
print("testData: \(testData.map { String(format: "%02hhx", $0) }.joined())")
let testHash = sha256(data:testData)
print("testHash: \(testHash.map { String(format: "%02hhx", $0) }.joined())")
  

输出:
  testData:74657374537472696e67
  testHash:4acf0b39d9c4766709a3689f553ac01ab550545ffa4544dfc0b2cea82fba02a3

另见Martin的链接。

答案 1 :(得分:3)

解决方案也适用于大型文件,因为它不需要整个文件在内存中:

func sha256(url: URL) -> Data? {
    do {
        let bufferSize = 1024 * 1024
        // Open file for reading:
        let file = try FileHandle(forReadingFrom: url)
        defer {
            file.closeFile()
        }

        // Create and initialize SHA256 context:
        var context = CC_SHA256_CTX()
        CC_SHA256_Init(&context)

        // Read up to `bufferSize` bytes, until EOF is reached, and update SHA256 context:
        while autoreleasepool(invoking: {
            // Read up to `bufferSize` bytes
            let data = file.readData(ofLength: bufferSize)
            if data.count > 0 {
                data.withUnsafeBytes {
                    _ = CC_SHA256_Update(&context, $0, numericCast(data.count))
                }
                // Continue
                return true
            } else {
                // End of file
                return false
            }
        }) { }

        // Compute the SHA256 digest:
        var digest = Data(count: Int(CC_SHA256_DIGEST_LENGTH))
        digest.withUnsafeMutableBytes {
            _ = CC_SHA256_Final($0, &context)
        }

        return digest
    } catch {
        print(error)
        return nil
    }
}

使用先前创建名称为URL的{​​{1}}类型的实例:

fileURL

答案 2 :(得分:0)

从Swift 5开始,@chriswillow's answer基本上还是正确的,但是对withUnsafeBytes / withUnsafeMutableBytes进行了一些更新。这些更新使方法更安全,但使用起来也比较烦人。

对于使用withUnsafeBytes的位,请使用:

_ = data.withUnsafeBytes { bytesFromBuffer -> Int32 in
  guard let rawBytes = bytesFromBuffer.bindMemory(to: UInt8.self).baseAddress else {
    return Int32(kCCMemoryFailure)
  }

  return CC_SHA256_Update(&context, rawBytes, numericCast(data.count))
}

对于生成最终摘要数据的位,请使用:

var digestData = Data(count: Int(CC_SHA256_DIGEST_LENGTH))
_ = digestData.withUnsafeMutableBytes { bytesFromDigest -> Int32 in
  guard let rawBytes = bytesFromDigest.bindMemory(to: UInt8.self).baseAddress else {
    return Int32(kCCMemoryFailure)
  }

  return CC_SHA256_Final(rawBytes, &context)
}

答案 3 :(得分:0)

使用 Apple 的 CryptoKit 的更新: 您可以使用 FileHandle 以块的形式读取数据,并将这些数据传递给散列器:

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()
}