SHA256 in swift
Asked Answered
M

13

122

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.

Mucky answered 19/8, 2014 at 16:42 Comment(7)
You can call ObjectiveC methods from swift directly, where exactly are you stuck?Obsolesce
Questions about translating from one language to another are off topic? Since when?Taxdeductible
@BenjaminGruenbaum problem is in string "unsigned char hash[CC_SHA1_DIGEST_LENGTH];"Mucky
@ЮрикАлександров CUnsignedChar[] ?Obsolesce
the other problem is that Int do not convertible to CC_LONGMucky
@ЮрикАлександров please add this extra info to your question above, and clearly describe your problem. Help us help you.Avon
@AlexWayne that'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.Mucky
M
159

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)
}
Mitten answered 19/8, 2014 at 18:59 Comment(10)
how can i convert this nsdata to string as it is when i am trying to convert its give me wrong valueOppression
Great answer! Just FYI API is now repeatElement... instead of repeating... on Int as of Xcode 8.2.1 for those coming across this more recently.Kalin
@iOSGamer: I double-checked that the Swift 3 version above is correct and compiles in Xcode 8.2.1 :)Mitten
@MartinR So I double-checked and indeed even though it didn't show up in autocompletion it does exist, compiles and works. Being a newbie, a defer to your version. Sorry for the noise, and thanks once again!Kalin
As an addition to this solution, to make CC_SHA256_DIGEST_LENGTH, CC_SHA256, and CC_LONGwork in Swift, you have to add #import <CommonCrypto/CommonDigest.h> to the bridging header file.Bosco
Your Swift 5 example is out of date.Mikimikihisa
@ClausJørgensen: What is the problem? It compiles and runs in my Xcode 10.2 with Swift 5.Mitten
Ok, it works if you do exactly as you wrote it. Xcode seems to have some issues with the $0.baseAddress part at random. I think it depends on how it infers the other arguments. If it can't infer the type to be UInt8 for the pointer, you can't access the .baseAddress property. (If you say, used a Data pointer instead of [UInt8].Mikimikihisa
@ClausJørgensen: Yes, for the other argument you would have to bind the raw data pointer to an UInt8 pointer. Passing the address of an array is easier. It has been suggested to improve the interoperability with C functions, compare forums.swift.org/t/withunsafebytes-data-api-confusion/22142/16.Mitten
@ClausJørgensen I have tested on Xcode 11.3.1 and Martins solution worked fine. I confirm it wont work in Xcode 11.Zetta
D
111

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 ""
    }
}
Demented answered 5/8, 2016 at 11:43 Comment(4)
Works like a charm @Andi. Only one correction that Xcode wants: This line: return hexStringFromData(input: digest(input: stringData)) Change by: return hexStringFromData(input: digest(input: stringData as NSData))Attainable
Can add this extension into Framework Project? How can create Objective-C Bridging Header into Framework Project ?Sylvestersylvia
Can I use this function to NSData instance? let data = NSData(contentsOfFile: "/Users/danila/metaprogramming-ruby-2.pdf") data.sha256() Lulualaba
This one is a life sawer, literally.Wilhelmstrasse
A
71

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

Alisealisen answered 29/7, 2019 at 14:4 Comment(6)
Good answer, but this needs the target version to be mni 10 iOS13. I had to use both this solution and manual computing depending on iOS version.Soche
Any differences? var hexString: String { self.map { String(format: "%02hhx", $0) }.joined() }Coypu
The solution does work, but it is impossible to compile in Release configuration with target lower than iOS 11 because of this issue in Xcode: openradar.appspot.com/7495817Joan
Excellent, concise answer. Works in MacOS 11 CLI app.Globoid
So, just to confirm I'm not crazy - Digest is happy to calculate the hex string into the debug description (with extraneous text preventing it from being immediately usable) - but to get the hex string alone, you have to extend the class manually?Widespread
Works in Linux UbuntuBittern
E
18

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"
Enlistment answered 31/5, 2016 at 15:26 Comment(4)
I got this error on this portion [let data = str.data(using: String.Encoding.utf8)] -> Error : Cannot convert value of type 'Data' to expected argument type 'String'. My I please know what I am doing wrongYe
Did you add to the bridging header? This code builds for me unchanged from Swift 3-ish to 4.1. (Xcode 9.3 builds for me).Enlistment
This does not give a correct Hash. Check with an online SHA generator to see for yourself.Condescendence
Perhaps your online generators perform the operation including a terminating zero? Are you checking an online SHA256, or maybe SHA-1 or SHA-2?Enlistment
M
17

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()
        }
    }
}
Moorman answered 16/10, 2020 at 9:11 Comment(0)
H
15

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

Harelip answered 16/10, 2019 at 14:44 Comment(3)
Won't the import CryptoKit break on iOS 12 though? It's an iOS 13.0+ only framework.Aarika
@KevinRenskers Use can use #if canImport(CryptoKit) for conditional import. Don't forget to set set -weak_framework CryptoKit in Other Linker FlagsSoche
Not working for me on iOS12 and below, I followed the above suggestion but I'm still getting "Library not loaded: /System/Library/Frameworks/CryptoKit.framework/CryptoKit" when the app starts.Carbaugh
Z
9
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()
  }
}
Zima answered 19/12, 2019 at 12:52 Comment(1)
If you need to have backward compatibility this is will works. Importing CryptoKit as the other solutions suggest, will crash the app on iOS12 and below with this error "Library not loaded: /System/Library/Frameworks/CryptoKit.framework/CryptoKit" when the app starts.Carbaugh
H
5

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
    }
}
Haversine answered 24/11, 2017 at 13:3 Comment(1)
This looked great until I saw no iOS love.Mangosteen
L
4

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()
    }
}
Luminous answered 1/8, 2015 at 3:24 Comment(1)
From what I understand this is only available on OSX, not iOS.Disadvantageous
B
4

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: "")
}
Bryon answered 8/4, 2020 at 6:18 Comment(0)
M
3

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
Mernamero answered 24/4, 2019 at 10:45 Comment(0)
I
3

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)
Insubstantial answered 5/7, 2019 at 10:44 Comment(1)
This is exactly what I was looking for! It was sadly incompatible with Swift 5, but with a few simple fixes it worked: gist.github.com/yspreen/95627d72bedcd47f1c0d29271b5600eaShipman
Y
0

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.

Yemane answered 25/4, 2018 at 8:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.