Swift Calculate MD5 Checksum для больших файлов

Я работаю над созданием контрольной суммы MD5 для больших видеофайлов. В настоящее время я использую код:

extension NSData {
func MD5() -> NSString {
    let digestLength = Int(CC_MD5_DIGEST_LENGTH)
    let md5Buffer = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: digestLength)

    CC_MD5(bytes, CC_LONG(length), md5Buffer)
    let output = NSMutableString(capacity: Int(CC_MD5_DIGEST_LENGTH * 2))
    for i in 0..<digestLength {
        output.appendFormat("%02x", md5Buffer[i])
    }

    return NSString(format: output)
    }
}

Но это создает буфер памяти, и для больших видео файлов было бы не идеально. Есть ли способ в Swift рассчитать контрольную сумму MD5 при чтении файлового потока, чтобы объем памяти был минимальным?


person Mike Walker    schedule 21.03.2017    source источник
comment
Попробуйте использовать правильную комбинацию CC_MD5_Init, CC_MD5_Update и CC_MD5_Final.   -  person rmaddy    schedule 21.03.2017


Ответы (2)


Вы можете вычислить контрольную сумму MD5 по частям, как показано, например, в есть ли библиотека MD5, которая не требует одновременного ввода всего ввода?.

Вот возможная реализация с использованием Swift (теперь обновлена ​​для Swift 5)

import CommonCrypto

func md5File(url: URL) -> Data? {

    let bufferSize = 1024 * 1024

    do {
        // Open file for reading:
        let file = try FileHandle(forReadingFrom: url)
        defer {
            file.closeFile()
        }

        // Create and initialize MD5 context:
        var context = CC_MD5_CTX()
        CC_MD5_Init(&context)

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

        // Compute the MD5 digest:
        var digest: [UInt8] = Array(repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
        _ = CC_MD5_Final(&digest, &context)

        return Data(digest)

    } catch {
        print("Cannot open file:", error.localizedDescription)
        return nil
    }
}

Пул автозапуска необходим для освобождения памяти, возвращаемой file.readData(), без него весь (потенциально огромный) файл был бы загружен в память. Спасибо Abhi Beckert за то, что заметили это и предоставили реализацию.

Если вам нужен дайджест в виде строки в шестнадцатеричной кодировке, измените тип возвращаемого значения на String? и замените

return digest

by

let hexDigest = digest.map { String(format: "%02hhx", $0) }.joined()
return hexDigest
person Martin R    schedule 21.03.2017
comment
Для всех, кто использует этот код, вы захотите выполнить обновление, чтобы оно соответствовало только что сделанному мной редактированию, поскольку он хранил весь файл в текущем пуле автозапуска, потенциально потребляя десятки гигабайт памяти. - person Abhi Beckert; 27.08.2017
comment
@AbhiBeckert: Действительно, это имеет огромное значение. Спасибо за обновления! Я немного изменил код, чтобы избавиться от дополнительной переменной выхода, но это исключительно вопрос личного выбора. - person Martin R; 10.09.2017
comment
Отличный ответ! Есть ли простой способ получить прогресс во время работы? Скажем, обновить пользователя в UI. - person Aaron; 16.12.2019

Решение (на основе ответа Мартина R) для хэша SHA256:

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 с именем fileURL, созданным ранее:

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