Does anyone see what is going wrong in the below code, as the code below works well when encrypting and sending to the server (which means my encryption function is fine), however there seems some problem with my decryption logic which is not decrypting the information correctly.
Note: The server side enc/dec logic works well with other languages e.g. Java
Server-side implementation sample: Nodejs Crypto to Swift commonCrypto
MyEncDec.swift
import Foundation
import CommonCrypto
struct AES256 {
private var key: Data
private var iv: Data
public init(key: Data, iv: Data) throws {
guard key.count == kCCKeySizeAES256 else {
throw Error.badKeyLength
}
guard iv.count == kCCBlockSizeAES128 else {
throw Error.badInputVectorLength
}
self.key = key
self.iv = iv
}
enum Error: Swift.Error {
case keyGeneration(status: Int)
case cryptoFailed(status: CCCryptorStatus)
case badKeyLength
case badInputVectorLength
}
func encrypt(_ digest: Data) throws -> Data {
return try crypt(input: digest, operation: CCOperation(kCCEncrypt))
}
func decrypt(_ encrypted: Data) throws -> Data {
return try crypt(input: encrypted, operation: CCOperation(kCCDecrypt))
}
private func crypt(input: Data, operation: CCOperation) throws -> Data {
var outLength = Int(0)
var outBytes = [UInt8](repeating: 0, count: input.count + kCCBlockSizeAES128)
var status: CCCryptorStatus = CCCryptorStatus(kCCSuccess)
input.withUnsafeBytes { rawBufferPointer in
let encryptedBytes = rawBufferPointer.baseAddress!
iv.withUnsafeBytes { rawBufferPointer in
let ivBytes = rawBufferPointer.baseAddress!
key.withUnsafeBytes { rawBufferPointer in
let keyBytes = rawBufferPointer.baseAddress!
status = CCCrypt(operation,
CCAlgorithm(kCCAlgorithmAES128), // algorithm
CCOptions(kCCOptionPKCS7Padding), // options
keyBytes, // key
key.count, // keylength
ivBytes, // iv
encryptedBytes, // dataIn
input.count, // dataInLength
&outBytes, // dataOut
outBytes.count, // dataOutAvailable
&outLength) // dataOutMoved
}
}
}
guard status == kCCSuccess else {
throw Error.cryptoFailed(status: status)
}
return Data(bytes: &outBytes, count: outLength)
}
static func createKey(password: Data, salt: Data) throws -> Data {
let length = kCCKeySizeAES256
var status = Int32(0)
var derivedBytes = [UInt8](repeating: 0, count: length)
password.withUnsafeBytes { rawBufferPointer in
let passwordRawBytes = rawBufferPointer.baseAddress!
let passwordBytes = passwordRawBytes.assumingMemoryBound(to: Int8.self)
salt.withUnsafeBytes { rawBufferPointer in
let saltRawBytes = rawBufferPointer.baseAddress!
let saltBytes = saltRawBytes.assumingMemoryBound(to: UInt8.self)
status = CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), // algorithm
passwordBytes, // password
password.count, // passwordLen
saltBytes, // salt
salt.count, // saltLen
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1), // prf
10000, // rounds
&derivedBytes, // derivedKey
length) // derivedKeyLen
}
}
guard status == 0 else {
throw Error.keyGeneration(status: Int(status))
}
return Data(bytes: &derivedBytes, count: length)
}
static func randomIv() -> Data {
return randomData(length: kCCBlockSizeAES128)
}
static func iV() -> Data {
let arr: [UInt8] = [0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1]
return Data(arr)
}
static func randomSalt() -> Data {
return randomData(length: 8)
}
static func randomData(length: Int) -> Data {
var data = Data(count: length)
var mutableBytes: UnsafeMutableRawPointer!
data.withUnsafeMutableBytes { rawBufferPointer in
mutableBytes = rawBufferPointer.baseAddress!
}
let status = SecRandomCopyBytes(kSecRandomDefault, length, mutableBytes)
assert(status == Int32(0))
return data
}
}
I need to take a guess since you have not provided the code that runs encrypt and decrypt. But I think that you provide iv using randomIV() on both encryption and decryption side and this will be the problem.
You need to provide same iv for decryption and encryption side. It means that you need to send your random iv from encryption side to decryption side and use that iv in decryption process.
I'm not experienced in Swift, But I think you need to use your code like this in order to works correctly:
// Encryption side
let keyData = Data("KEY01234567890123456789012345678".utf8)
let data = Data("TEST123".utf8)
let iv = AES256.randomIv()
if let aes = try? AES256.init(key: keyData, iv: iv) {
if let encryptedData = try? aes.encrypt(data) {
var resultData = iv
resultData.append(encryptedData)
// use resultData here
}
}
// Decryption side
let keyData = Data("KEY01234567890123456789012345678".utf8)
let data = resultData
let iv = data.subdata(in: ..<kCCBlockSizeAES128)
let encryptedPart = data.subdata(in: kCCBlockSizeAES128...)
if let aes = try? AES256.init(key: keyData, iv: iv) {
if let decryptedData = try? aes.decrypt(encryptedPart) {
// use decryptedData here
}
}
I used cristallo code as base for this code.
I've just try to run your client side code encrypting and decrypting a sample data and it seems to work properly. Is the issue present only when you are trying to decrypt a data buffer coming from the server?
I've tested your code in the following way:
let keyData = Data("KEY01234567890123456789012345678".utf8)
let data = Data("TEST123".utf8)
let iv = AES256.randomIv()
if let aes = try? AES256.init(key: keyData, iv: iv), let aes2 = try? AES256.init(key: keyData, iv: iv) {
if let encriptedData = try? aes.encrypt(data) {
if let decryptedData = try? aes2.decrypt(encriptedData) {
let decryptedString = String(decoding: decryptedData, as: UTF8.self)
print(decryptedString)
}
}
}
I am not sure about how you are using the AES256 class but taking a look to the server side code:
The encryption function generates a string composition of the iv and the data using ":" as separator.
let final_encrypted = iv.toString('hex') + ':' + encrypted.toString('hex');
So the string coming from the server has to be parsed before decrypting it in order to retrieve iv and data.
func parseAndDecrypt(encryptedString: String) {
let keyData = Data("KEY01234567890123456789012345678".utf8)
let substrings = encryptedString.split(separator: ":")
if let ivString = substrings.first, let dataString = substrings.last {
let iv = Data(ivString.utf8)
let encryptedData = Data(dataString.utf8)
if let aes = try? AES256.init(key: keyData, iv: iv) {
if let decryptedData = try? aes.decrypt(encryptedData) {
let decryptedString = String(decoding: decryptedData, as: UTF8.self)
print(decryptedString)
}
}
}
}
Issue: "Type HASH256 has no member hash"
Background: Trying to implement Apple sign in with Firebase on Swift
Tried to resolve the issue with the following:
-all pods update
-import CommonCrypto + import CryptoKit
-clean build folder / build
The error is still present
// Adapted from https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-nonce
private func randomNonceString(length: Int = 32) -> String {
precondition(length > 0)
let charset: Array<Character> =
Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
var result = ""
var remainingLength = length
while remainingLength > 0 {
let randoms: [UInt8] = (0 ..< 16).map { _ in
var random: UInt8 = 0
let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random)
if errorCode != errSecSuccess {
fatalError("Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)")
}
return random
}
randoms.forEach { random in
if length == 0 {
return
}
if random < charset.count {
result.append(charset[Int(random)])
remainingLength -= 1
}
}
}
return result
}
//Start Apple's sign-in flow
// Unhashed nonce.
fileprivate var currentNonce: String?
#available(iOS 13, *)
func startSignInWithAppleFlow() {
let nonce = randomNonceString()
currentNonce = nonce
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
request.nonce = sha256(nonce)
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self as! ASAuthorizationControllerDelegate
authorizationController.presentationContextProvider = self as! ASAuthorizationControllerPresentationContextProviding
authorizationController.performRequests()
}
#available(iOS 13, *)
private func sha256(_ input: String) -> String {
let inputData = Data(input.utf8)
let hashedData = SHA256.hash(data: inputData)
let hashString = hashedData.compactMap {
return String(format: "%02x", $0)
}.joined()
return hashString
}
// func SHA256() -> String {
//
// let data = self.data(using: String.Encoding.utf8)
// let res = NSMutableData(length: Int(CC_SHA256_DIGEST_LENGTH))
// CC_SHA256(((data! as NSData)).bytes, CC_LONG(data!.count), res?.mutableBytes.assumingMemoryBound(to: UInt8.self))
// let hashedString = "\(res!)".replacingOccurrences(of: "", with: "").replacingOccurrences(of: " ", with: "")
// let badchar: CharacterSet = CharacterSet(charactersIn: "\"<\",\">\"")
// let cleanedstring: String = (hashedString.components(separatedBy: badchar) as NSArray).componentsJoined(by: "")
// return cleanedstring
//
// }
}
//Apple extension
#available(iOS 13.0, *)
extension AuthViewController: ASAuthorizationControllerDelegate {
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
guard let nonce = currentNonce else {
fatalError("Invalid state: A login callback was received, but no login request was sent.")
}
guard let appleIDToken = appleIDCredential.identityToken else {
print("Unable to fetch identity token")
return
}
guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
print("Unable to serialize token string from data: \(appleIDToken.debugDescription)")
return
}
// Initialize a Firebase credential.
let credential = OAuthProvider.credential(withProviderID: "apple.com",
idToken: idTokenString,
accessToken: nonce)
// Sign in with Firebase.
Auth.auth().signIn(with: credential) { (authResult, error) in
if (error != nil) {
// Error. If error.code == .MissingOrInvalidNonce, make sure
// you're sending the SHA256-hashed nonce as a hex string with
// your request to Apple.
print(error?.localizedDescription)
return
}
// User is signed in to Firebase with Apple.
// ...
}
}
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
// Handle error.
print("Sign in with Apple errored: \(error)")
}
}
Image of error
I encountered the same problem, I spend two days figured it out!
The reason is we mistaken installed 'CryptoKit' in our Podfile. which apple also has a build-in 'CryptoKit' for iOS version 13+.
Solution :
1.deleted pod ''CryptoKit' in our pod file.
2. pod install
after that, we will use apple build in 'CryptoKit' which has the build-in method hash.
This should work: add this outside of your class and then instead of request.nonce = sha256(nonce), type request.nonce = nonce.sha256()
extension String {
func sha256() -> String{
if let stringData = self.data(using: String.Encoding.utf8) {
return hexStringFromData(input: digest(input: stringData as NSData))
}
return ""
}
private func digest(input : NSData) -> NSData {
let digestLength = Int(CC_SHA256_DIGEST_LENGTH)
var hash = [UInt8](repeating: 0, count: digestLength)
CC_SHA256(input.bytes, UInt32(input.length), &hash)
return NSData(bytes: hash, length: digestLength)
}
private func hexStringFromData(input: NSData) -> String {
var bytes = [UInt8](repeating: 0, count: input.length)
input.getBytes(&bytes, length: input.length)
var hexString = ""
for byte in bytes {
hexString += String(format:"%02x", UInt8(byte))
}
return hexString
}
}
credit
I'm trying to hash a password with SHA256 method:
class CryptoHandler {
static func sha256(_ str: String) -> String? {
let data = str.data(using: String.Encoding.utf8)
let shaData = sha256(data!)
let rc = String(data: shaData, encoding: String.Encoding.utf8) as String?
return rc
}
static func sha256(_ data: Data) -> Data { var res = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)); data.withUnsafeBytes { _ = CC_SHA256($0, CC_LONG(data.count), &res) }; return Data(bytes: res) }
static func getHashedPassword (pwd: String) -> String{
let hash = sha256(pwd)
return hash!
}
}
When I try to execute getHashedPassword ("0123456789") I have the following error in return hash! line:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an
Optional value
Why this happens? How I can solve it?
The crash occurs because you cannot create a String from encrypted Data. You have to use base64 or hex representation.
This code creates a hex encoded string and doesn't use optionals at all.
class CryptoHandler {
static func sha256(_ str: String) -> String {
let data = Data(str.utf8)
let shaData = sha256(data)
return shaData.hexString
}
static func sha256(_ data: Data) -> Data {
var res = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH));
data.withUnsafeBytes { _ = CC_SHA256($0, CC_LONG(data.count), &res) };
return Data(bytes: res)
}
static func getHashedPassword (pwd: String) -> String{
let hash = sha256(pwd)
return hash
}
}
extension Data {
var hexString : String {
return self.map{ String(format:"%02x", $0) }.joined()
}
}
CryptoHandler.getHashedPassword(pwd:"0123456789")
let rc = String(data: shaData, encoding: String.Encoding.utf8) as String?
will return nil value and you are forcing to compiler a value using return hash! Exclamation mark at the end of hash constant in getHashedPassword method.
You can fix it by modifying getHashedPassword method by accepting an optional value
ex:
static func getHashedPassword (pwd: String) -> String?
{
let hash = sha256(pwd)
return hash
}
I'm working on creating the MD5 Checksum for large video files. I'm currently using the code:
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)
}
}
But that creates a memory buffer, and for large video files would not be ideal. Is there a way in Swift to calculate the MD5 Checksum reading a file stream, so the memory footprint will be minimal?
You can compute the MD5 checksum in chunks, as demonstrated
e.g. in Is there a MD5 library that doesn't require the whole input at the same time?.
Here is a possible implementation using Swift (now updated for 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
}
}
The autorelease pool is needed to release the memory returned by
file.readData(), without it the entire (potentially huge) file
would be loaded into memory. Thanks to Abhi Beckert for noticing that
and providing an implementation.
If you need the digest as a hex-encoded string then change the
return type to String? and replace
return digest
by
let hexDigest = digest.map { String(format: "%02hhx", $0) }.joined()
return hexDigest
Since iOS13
'CC_MD5_Init' was deprecated in iOS 13.0
You may replace the code with CryptoKit
import Foundation
import CryptoKit
extension URL {
func checksumInBase64() -> String? {
let bufferSize = 16*1024
do {
// Open file for reading:
let file = try FileHandle(forReadingFrom: self)
defer {
file.closeFile()
}
// Create and initialize MD5 context:
var md5 = CryptoKit.Insecure.MD5()
// 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 {
md5.update(data: data)
return true // Continue
} else {
return false // End of file
}
}) { }
// Compute the MD5 digest:
let data = Data(md5.finalize())
return data.base64EncodedString()
} catch {
error_log(error)
return nil
}
}
}
Solution (based on Martin R's answer) for SHA256 hash:
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
}
}
Usage with instance of type URL with name fileURL previously created:
if let digestData = sha256(url: fileURL) {
let calculatedHash = digestData.map { String(format: "%02hhx", $0) }.joined()
DDLogDebug(calculatedHash)
}
I want to use sha256 in my project, but I had some troubles rewriting objC code to swift code. Help me please. I used this answer: How can I compute a SHA-2 (ideally SHA 256 or SHA 512) hash in iOS?
Here's my code
var hash : [CUnsignedChar]
CC_SHA256(data.bytes, data.length, hash)
var res : NSData = NSData.dataWithBytes(hash, length: CC_SHA256_DIGEST_LENGTH)
it gives me error everything because swift cannot convert Int to CC_LONG, for example.
You have to convert explicitly between Int and CC_LONG, because Swift does not
do implicit conversions, as in (Objective-)C.
You also have to define hash as an array of the required size.
func sha256(data : NSData) -> NSData {
var hash = [UInt8](count: Int(CC_SHA256_DIGEST_LENGTH), repeatedValue: 0)
CC_SHA256(data.bytes, CC_LONG(data.length), &hash)
let res = NSData(bytes: hash, length: Int(CC_SHA256_DIGEST_LENGTH))
return res
}
Alternatively, you can use NSMutableData to allocate the needed buffer:
func sha256(data : NSData) -> NSData {
let res = NSMutableData(length: Int(CC_SHA256_DIGEST_LENGTH))
CC_SHA256(data.bytes, CC_LONG(data.length), UnsafeMutablePointer(res.mutableBytes))
return res
}
Update for Swift 3 and 4:
func sha256(data : Data) -> Data {
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes {
_ = CC_SHA256($0, CC_LONG(data.count), &hash)
}
return Data(bytes: hash)
}
Update for Swift 5:
func sha256(data : Data) -> Data {
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes {
_ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
}
return Data(hash)
}
Updated for Swift 5.
Put this extension somewhere in your project and use it on a string like this: mystring.sha256(), or on data with data.sha256()
import Foundation
import CommonCrypto
extension Data{
public func sha256() -> String{
return hexStringFromData(input: digest(input: self as NSData))
}
private func digest(input : NSData) -> NSData {
let digestLength = Int(CC_SHA256_DIGEST_LENGTH)
var hash = [UInt8](repeating: 0, count: digestLength)
CC_SHA256(input.bytes, UInt32(input.length), &hash)
return NSData(bytes: hash, length: digestLength)
}
private func hexStringFromData(input: NSData) -> String {
var bytes = [UInt8](repeating: 0, count: input.length)
input.getBytes(&bytes, length: input.length)
var hexString = ""
for byte in bytes {
hexString += String(format:"%02x", UInt8(byte))
}
return hexString
}
}
public extension String {
func sha256() -> String{
if let stringData = self.data(using: String.Encoding.utf8) {
return stringData.sha256()
}
return ""
}
}
With CryptoKit added in iOS13, we now have native Swift API:
import Foundation
import CryptoKit
// CryptoKit.Digest utils
extension Digest {
var bytes: [UInt8] { Array(makeIterator()) }
var data: Data { Data(bytes) }
var hexStr: String {
bytes.map { String(format: "%02X", $0) }.joined()
}
}
func example() {
guard let data = "hello world".data(using: .utf8) else { return }
let digest = SHA256.hash(data: data)
print(digest.data) // 32 bytes
print(digest.hexStr) // B94D27B9934D3E08A52E52D7DA7DABFAC484EFE37A5380EE9088F7ACE2EFCDE9
}
Because utils are defined for protocol Digest, you can use it for all digest type in CryptoKit, like SHA384Digest, SHA512Digest, SHA1Digest, MD5Digest...
Functions giving the SHA from NSData & String (Swift 3):
func sha256(_ data: Data) -> Data? {
guard let res = NSMutableData(length: Int(CC_SHA256_DIGEST_LENGTH)) else { return nil }
CC_SHA256((data as NSData).bytes, CC_LONG(data.count), res.mutableBytes.assumingMemoryBound(to: UInt8.self))
return res as Data
}
func sha256(_ str: String) -> String? {
guard
let data = str.data(using: String.Encoding.utf8),
let shaData = sha256(data)
else { return nil }
let rc = shaData.base64EncodedString(options: [])
return rc
}
Include in your bridging header:
#import "CommonCrypto/CommonCrypto.h"
A version for Swift 5 that uses CryptoKit on iOS 13 and falls back to CommonCrypto otherwise:
import CommonCrypto
import CryptoKit
import Foundation
private func hexString(_ iterator: Array<UInt8>.Iterator) -> String {
return iterator.map { String(format: "%02x", $0) }.joined()
}
extension Data {
public var sha256: String {
if #available(iOS 13.0, *) {
return hexString(SHA256.hash(data: self).makeIterator())
} else {
var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
self.withUnsafeBytes { bytes in
_ = CC_SHA256(bytes.baseAddress, CC_LONG(self.count), &digest)
}
return hexString(digest.makeIterator())
}
}
}
Usage:
let string = "The quick brown fox jumps over the lazy dog"
let hexDigest = string.data(using: .ascii)!.sha256
assert(hexDigest == "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592")
Also available via Swift package manager:
https://github.com/ralfebert/TinyHashes
I researched many answers and I summarized it:
import CryptoKit
import CommonCrypto
extension String {
func hash256() -> String {
let inputData = Data(utf8)
if #available(iOS 13.0, *) {
let hashed = SHA256.hash(data: inputData)
return hashed.compactMap { String(format: "%02x", $0) }.joined()
} else {
var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
inputData.withUnsafeBytes { bytes in
_ = CC_SHA256(bytes.baseAddress, UInt32(inputData.count), &digest)
}
return digest.makeIterator().compactMap { String(format: "%02x", $0) }.joined()
}
}
}
import CommonCrypto
public extension String {
var sha256: String {
let data = Data(utf8)
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes { buffer in
_ = CC_SHA256(buffer.baseAddress, CC_LONG(buffer.count), &hash)
}
return hash.map { String(format: "%02hhx", $0) }.joined()
}
}
Here's my simple 3-line Swift 4 function for this using the Security Transforms API, which is part of Foundation on macOS. (Unfortunately iOS programmers cannot use this technique.)
import Foundation
extension Data {
public func sha256Hash() -> Data {
let transform = SecDigestTransformCreate(kSecDigestSHA2, 256, nil)
SecTransformSetAttribute(transform, kSecTransformInputAttributeName, self as CFTypeRef, nil)
return SecTransformExecute(transform, nil) as! Data
}
}
Here's a method that uses the CoreFoundation Security Transforms API, so you don't even need to link to CommonCrypto. For some reason in 10.10/Xcode 7 linking to CommmonCrypto with Swift is drama so I used this instead.
This method reads from an NSInputStream, which you can either get from a file, or you can make one that reads an NSData, or you can make bound reader/writer streams for a buffered process.
// digestType is from SecDigestTransform and would be kSecDigestSHA2, etc
func digestForStream(stream : NSInputStream,
digestType type : CFStringRef, length : Int) throws -> NSData {
let transform = SecTransformCreateGroupTransform().takeRetainedValue()
let readXform = SecTransformCreateReadTransformWithReadStream(stream as CFReadStreamRef).takeRetainedValue()
var error : Unmanaged<CFErrorRef>? = nil
let digestXform : SecTransformRef = try {
let d = SecDigestTransformCreate(type, length, &error)
if d == nil {
throw error!.takeUnretainedValue()
} else {
return d.takeRetainedValue()
}
}()
SecTransformConnectTransforms(readXform, kSecTransformOutputAttributeName,
digestXform, kSecTransformInputAttributeName,
transform, &error)
if let e = error { throw e.takeUnretainedValue() }
if let output = SecTransformExecute(transform, &error) as? NSData {
return output
} else {
throw error!.takeUnretainedValue()
}
}
Tested in Swift5.
In case you want to get the hash in String,
this is how I did.
private func getHash(_ phrase:String) -> String{
let data = phrase.data(using: String.Encoding.utf8)!
let length = Int(CC_SHA256_DIGEST_LENGTH)
var digest = [UInt8](repeating: 0, count: length)
data.withUnsafeBytes {
_ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &digest)
}
return digest.map { String(format: "%02x", $0) }.joined(separator: "")
}
For Swift 5:
guard let data = self.data(using: .utf8) else { return nil }
var sha256 = Data(count: Int(CC_SHA256_DIGEST_LENGTH))
sha256.withUnsafeMutableBytes { sha256Buffer in
data.withUnsafeBytes { buffer in
let _ = CC_SHA256(buffer.baseAddress!, CC_LONG(buffer.count), sha256Buffer.bindMemory(to: UInt8.self).baseAddress)
}
}
return sha256
The other answers will have performance problems for calculating digests from large amounts of data (e.g. large files). You will not want to load all data into memory at once. Consider the following approach using update/finalize:
final class SHA256Digest {
enum InputStreamError: Error {
case createFailed(URL)
case readFailed
}
private lazy var context: CC_SHA256_CTX = {
var shaContext = CC_SHA256_CTX()
CC_SHA256_Init(&shaContext)
return shaContext
}()
private var result: Data? = nil
init() {
}
func update(url: URL) throws {
guard let inputStream = InputStream(url: url) else {
throw InputStreamError.createFailed(url)
}
return try update(inputStream: inputStream)
}
func update(inputStream: InputStream) throws {
guard result == nil else {
return
}
inputStream.open()
defer {
inputStream.close()
}
let bufferSize = 4096
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
defer {
buffer.deallocate()
}
while true {
let bytesRead = inputStream.read(buffer, maxLength: bufferSize)
if bytesRead < 0 {
//Stream error occured
throw (inputStream.streamError ?? InputStreamError.readFailed)
} else if bytesRead == 0 {
//EOF
break
}
self.update(bytes: buffer, length: bytesRead)
}
}
func update(data: Data) {
guard result == nil else {
return
}
data.withUnsafeBytes {
self.update(bytes: $0, length: data.count)
}
}
func update(bytes: UnsafeRawPointer, length: Int) {
guard result == nil else {
return
}
_ = CC_SHA256_Update(&self.context, bytes, CC_LONG(length))
}
func finalize() -> Data {
if let calculatedResult = result {
return calculatedResult
}
var resultBuffer = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
CC_SHA256_Final(&resultBuffer, &self.context)
let theResult = Data(bytes: resultBuffer)
result = theResult
return theResult
}
}
extension Data {
private static let hexCharacterLookupTable: [Character] = [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"a",
"b",
"c",
"d",
"e",
"f"
]
var hexString: String {
return self.reduce(into: String(), { (result, byte) in
let c1: Character = Data.hexCharacterLookupTable[Int(byte >> 4)]
let c2: Character = Data.hexCharacterLookupTable[Int(byte & 0x0F)]
result.append(c1)
result.append(c2)
})
}
}
You could use it as follows:
let digest = SHA256Digest()
try digest.update(url: fileURL)
let result = digest.finalize().hexString
print(result)
I prefer to use:
extension String {
var sha256:String? {
guard let stringData = self.data(using: String.Encoding.utf8) else { return nil }
return digest(input: stringData as NSData).base64EncodedString(options: [])
}
private func digest(input : NSData) -> NSData {
let digestLength = Int(CC_SHA256_DIGEST_LENGTH)
var hash = [UInt8](repeating: 0, count: digestLength)
CC_SHA256(input.bytes, UInt32(input.length), &hash)
return NSData(bytes: hash, length: digestLength)
}
}
The hasded String is base64 encoded.