I am trying to get MD5 hash of my data (image downloaded from the interweb). Unfortunately I have upgraded the framework to swift 3 and the method I have been using doesn't work now.
I have converted most of it but I am unable to get bytes out of the data:
import Foundation
import CommonCrypto
struct MD5 {
static func get(data: Data) -> String {
var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
CC_MD5(data.bytes, CC_LONG(data.count), &digest)
var digestHex = ""
for index in 0..<Int(CC_MD5_DIGEST_LENGTH) {
digestHex += String(format: "%02x", digest[index])
}
return digestHex
}
}
the CommonCrypto is already imported as a custom module. Problem is I am getting 'bytes' is unavailable: use withUnsafeBytes instead
on CC_MD5(data.bytes,...
So the question really is, how do I get the bytes out of the data and will this solution work?
Here's a one liner:
import CryptoKit
let md5String = Insecure.MD5.hash(data: data).map { String(format: "%02hhx", $0) }.joined()
And for anyone that's interested, here's an example that you could build upon to support different Algorithms:
Usage:
Checksum.hash(data: data, using: .md5) == "MyMD5Hash"
Code Snippet:
import Foundation
import CommonCrypto
struct Checksum {
private init() {}
static func hash(data: Data, using algorithm: HashAlgorithm) -> String {
/// Creates an array of unsigned 8 bit integers that contains zeros equal in amount to the digest length
var digest = [UInt8](repeating: 0, count: algorithm.digestLength())
/// Call corresponding digest calculation
data.withUnsafeBytes {
algorithm.digestCalculation(data: $0.baseAddress, len: UInt32(data.count), digestArray: &digest)
}
var hashString = ""
/// Unpack each byte in the digest array and add them to the hashString
for byte in digest {
hashString += String(format:"%02x", UInt8(byte))
}
return hashString
}
/**
* Hash using CommonCrypto
* API exposed from CommonCrypto-60118.50.1:
* https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60118.50.1/include/CommonDigest.h.auto.html
**/
enum HashAlgorithm {
case md5
case sha256
func digestLength() -> Int {
switch self {
case .md5:
return Int(CC_MD5_DIGEST_LENGTH)
case .sha256:
return Int(CC_SHA256_DIGEST_LENGTH)
}
}
/// CC_[HashAlgorithm] performs a digest calculation and places the result in the caller-supplied buffer for digest
/// Calls the given closure with a pointer to the underlying unsafe bytes of the data's contiguous storage.
func digestCalculation(data: UnsafeRawPointer!, len: UInt32, digestArray: UnsafeMutablePointer<UInt8>!) {
switch self {
case .md5:
CC_MD5(data, len, digestArray)
case .sha256:
CC_SHA256(data, len, digestArray)
}
}
}
}
CC_MD5(data.bytes, CC_LONG(data.count), &digest)
As noted, bytes
is unavailable because it's dangerous. It's a raw pointer into memory than can vanish. The recommended solution is to use withUnsafeBytes
which promises that the target cannot vanish during the scope of the pointer. From memory, it would look something like this:
data.withUnsafeBytes { bytes in
CC_MD5(bytes, CC_LONG(data.count), &digest)
}
The point is that the bytes
pointer can't escape into scopes where data
is no longer valid.
For an example of this with CCHmac
, which is pretty similar to MD5
, see RNCryptor.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With