【问题标题】:How can I hash a file on iOS using swift 3?如何使用 swift 3 在 iOS 上散列文件?
【发布时间】:2021-08-03 12:45:57
【问题描述】:

我有许多文件将保存在服务器上。用户可以在设备上创建这些类型的文件(plist),然后将其上传到所述服务器(CloudKit)。我想通过内容来区分它们(独特的方法应该能够适应创建日期的变化)。我的理解是我应该对这些文件进行哈希处理,以便为它们获取唯一的文件名。我的问题是:

  1. 我的理解是否正确,我想要的是一个哈希函数?
  2. 我应该使用哪个函数(来自 CommonCrypto)。
  3. 我需要的是摘要吗?
  4. 我将如何在代码中处理它? (我认为这应该在 NSData 实例上进行散列?)。我通过谷歌搜索的理解是我需要一个桥接头,但除此之外,CommonCrypto 的使用让我感到困惑。如果有使用第一方 API(Apple)的更简单方法,我会全力以赴(我想尽可能避免使用第三方代码)。

非常感谢!

【问题讨论】:

  • 大警告。哈希函数不会生成唯一标识符。碰撞是可能的,你应该处理它。
  • @Sulthan 虽然确实可以安全地使用加密哈希来识别文件,但请参阅 Git。
  • @zaph 非常正确。多次讨论了碰撞的可能性。例如,请注意最近的示例,当两个具有相同 SHA1 的不同 PDF 文件破坏了多个 SVN 存储库时。我知道,它是 SHA1 和 SHA256 发生此类事情的可能性要低得多,但它仍然可能发生。如果系统很关键,后果可能很严重。此外,解决冲突通常是微不足道的。在谈到性能时,在大多数情况下计算整个文件(尤其是大二进制文件)的哈希或计算复杂的加密哈希是不可行的。
  • 确实,如果哈希不同:完成,如果哈希匹配:将文件与第一个比较失败或结束进行比较。

标签: ios swift hash commoncrypto


【解决方案1】:

为每个文件创建一个加密哈希,您可以将其用于唯一性比较。 SHA-256 是当前的哈希函数,在带有 Common Crypto 的 iOS 上非常快,在 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)")

输出:
测试哈希: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())")

输出:
测试数据:74657374537472696e67
测试哈希:4acf0b39d9c4766709a3689f553ac01ab550545ffa4544dfc0b2cea82fba02a3

另见 Martin 的链接。

【讨论】:

  • 感谢灯火快速回复!我对您的代码的阅读是该函数对一个字符串进行哈希处理,我将如何对 plist 进行哈希处理(我假设是一个 Data 对象)?另外,您对 base64EncodedString 的调用只是截断的一个例子吗? (对不起,我对密码学一无所知......)
  • 向 UTF-8 的转换不能失败——我们之前有讨论过吗?
  • @Martin,啊,是的,我正在复制我的旧代码,正在更正。由于我纠正了另一个人,因此倍尴尬。
  • 您确定这适用于大文件吗?因为它要求整个数据都在内存中,对吧? @zaph
  • 要处理大文件,您可能需要使用 Stream 类文件分段加密。您将不得不使用 SHA256_Init 后跟多个调用,所以 CC_SHA256_Update 并以 CC_SHA256_Fina 结尾。
【解决方案2】:

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

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

与先前创建的名称为 fileURLURL 类型的实例一起使用:

if let digestData = sha256(url: fileURL) {
    let calculatedHash = digestData.map { String(format: "%02hhx", $0) }.joined()
    DDLogDebug(calculatedHash)
}

【讨论】:

    【解决方案3】:

    截至 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)
    }
    

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 2012-09-16
        • 1970-01-01
        • 1970-01-01
        • 2017-12-20
        • 1970-01-01
        • 1970-01-01
        • 2017-03-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多