How to hash a string to SHA512 in Swift?
Asked Answered
G

5

7

I am building a social media application and I would like some help encoding a password string to SHA512 in Swift. I found the CryptoSwift library on GitHub but I am having a hard time loading it into my Swift project and linking it to my project files. Does anyone know how to accomplish this relatively easily? Thanks in advance, Kyle

Gamp answered 17/5, 2015 at 5:37 Comment(5)
I beliebe CryptoSwift is the easy way :)Audwen
What problems do you have exactly with using CryptoSwift? – You don't necessarily need a 3rd party framework, you can also use Apple's CommonCrypto with thin Swift wrappers. Examples: #25023876 (your own question :), #6228592, #25761844, https://mcmap.net/q/180583/-sha256-in-swift.Hildy
@MartinR Thanks for sharing! This answer really helped! This is the article that answered my question (#25761844). All I had to do was change out SHA1 to SHA512.Gamp
See MD2, MD4, MD5, SHA1, SHA224, SHA256, SHA384, SHA512 in the Documentations sectionCatherine
It is best to avoid using CryptoSwift, amoung other things it is 500 to 1000 times slower than Common Crypto based implementations. Apple's Common Crypto is FIPS certified and as such has been well vetted, using CryptoSwift is taking a chance on correctness and security.Catherine
Z
8

Solution for Swift 3:

extension String {

    func sha512() -> String {
        let data = self.data(using: .utf8)!
        var digest = [UInt8](repeating: 0, count: Int(CC_SHA512_DIGEST_LENGTH))
        data.withUnsafeBytes({
            _ = CC_SHA512($0, CC_LONG(data.count), &digest)
        })
        return digest.map({ String(format: "%02hhx", $0) }).joined(separator: "")
    }

}
Zippy answered 3/3, 2017 at 12:37 Comment(7)
Wondering how self.data(using: .utf8) could ever fail given that self must be a valid String.Catherine
Yes, you are right. Swift strings are always unicode strings, so self.data(using: .utf8) can never fail. I adapted my answer accordingly.Zippy
In a playground, what must I import to get access to CC_ funcs? Foundation is not enough.Mckeon
It is not possible to use CommonCrypto directly in a playground because you cannot add a bridging header. But I read that it is possible by adding a framework with CommonCrypto in a bridging header: https://mcmap.net/q/1477833/-is-there-a-way-to-use-common-crypto-in-a-swift-playgroundZippy
Hello @sundance, Thanks for your answer. I don't quite understand what "%02hhx" means in the String(format:) call; I was wondering if you could you please explain that briefly. Also, why would you need to iterate over the bytes and convert them to a string using String(format:) instead of just using a base64EncodedString()? Thanks in advanceMountie
Typically, hashes are not encoded as base64 string but as hexadecimal string, so this is what my function returns. The digest consists of 512 bit, which is stored as 64 byte array and returned as 128 character hexadecimal string. The digest is here mapped to a string array, where each byte is formatted to a hexadecimal string. The format %02hhx comes from printf formatting in C++ (see cplusplus.com/reference/cstdio/printf). % is the starting symbol, 02 enforces two output characters, hh defines the input as byte and x sets the output to hexadecimal.Zippy
@Zippy please help here #66758204Curule
A
3

I find all of the answer ok, but if we should have a true universal solution I think we need to step it up a level.

CC_LONG is just an UInt32 and will not support really large data structures.

This is my solution in Swift 3:

First we create a foundation:

struct Sha512 {
    let context = UnsafeMutablePointer<CC_SHA512_CTX>.allocate(capacity:1)

    init() {
        CC_SHA512_Init(context)
    }

    func update(data: Data) {
        data.withUnsafeBytes { (bytes: UnsafePointer<Int8>) -> Void in
            let end = bytes.advanced(by: data.count)
            for f in sequence(first: bytes, next: { $0.advanced(by: Int(CC_LONG.max)) }).prefix(while: { (current) -> Bool in current < end})  {
                _ = CC_SHA512_Update(context, f, CC_LONG(Swift.min(f.distance(to: end), Int(CC_LONG.max))))
            }
        }
    }

    func final() -> Data {
        var digest = [UInt8](repeating: 0, count:Int(CC_SHA512_DIGEST_LENGTH))
        CC_SHA512_Final(&digest, context)

        return Data(bytes: digest)
    }
}

For convenience we do an extension for Data:

extension Data {
    func sha512() -> Data {
        let s = Sha512()
        s.update(data: self)
        return s.final()
    }
}

And last an extension for String:

extension String {
    func sha512() -> Data {
        return self.data(using: .utf8)!.sha512()
    }
}

This solution can be used for Sha256, MD5 etc. to get a good true universal solutions with Apple's CommonCrypto.

Attorn answered 30/6, 2017 at 22:0 Comment(0)
B
2

Swift 3

func sha512() -> String {
    let data = self.data(using: .utf8)!
    var digest = [UInt8](repeating: 0, count: Int(CC_SHA512_DIGEST_LENGTH))
    data.withUnsafeBytes({
        _ = CC_SHA512($0, CC_LONG(data.count), &digest)
    })

    return digest.map({ String(format: "%02hhx", $0) }).joined(separator: "")
}

Swift 2.3

func sha512() -> String {
    let data = self.dataUsingEncoding(NSUTF8StringEncoding)!
    var digest = [UInt8](count:Int(CC_SHA512_DIGEST_LENGTH), repeatedValue: 0)
    CC_SHA512(data.bytes, CC_LONG(data.length), &digest)
    let hexBytes = digest.map { String(format: "%02hhx", $0) }

    return hexBytes.joinWithSeparator("")
}
Basilicata answered 18/5, 2016 at 13:41 Comment(0)
A
2

You need to import C library CommonCrypto. You cannot just import CommonCrypto to your swift file since it's not a standalone module.

If you have a bridging header file, you are lucky! Just add this to that file

#import <CommonCrypto/CommonCrypto.h>

There are some articles about different ways to do that.

Then you can use this piece of code to have sha512 available for any string of your choice.

swift 5

extension String {

    public var sha512: String {
        let data = self.data(using: .utf8) ?? Data()
        var digest = [UInt8](repeating: 0, count: Int(CC_SHA512_DIGEST_LENGTH))
        data.withUnsafeBytes {
            _ = CC_SHA512($0.baseAddress, CC_LONG(data.count), &digest)
        }
        return digest.map({ String(format: "%02hhx", $0) }).joined(separator: "")
    }
}
Anlage answered 8/3, 2017 at 12:51 Comment(0)
T
0

Swift 5

for string this method works for me

func SHA512(string: String) -> String {
    let length = Int(CC_SHA512_DIGEST_LENGTH)
    let messageData = string.data(using:.utf8)!
    var digestData = Data(count: length)
    
    _ = digestData.withUnsafeMutableBytes { digestBytes -> UInt8 in
        messageData.withUnsafeBytes { messageBytes -> UInt8 in
            if let messageBytesBaseAddress = messageBytes.baseAddress, let digestBytesBlindMemory = digestBytes.bindMemory(to: UInt8.self).baseAddress {
                let messageLength = CC_LONG(messageData.count)
                CC_SHA512(messageBytesBaseAddress, messageLength, digestBytesBlindMemory)
            }
            return 0
        }
    }
    return digestData.map { String(format: "%02hhx", $0) }.joined()
}

if you only need Data remove .map { String(format: "%02hhx", $0):

func SHA512(string: String) -> Data {
    let length = Int(CC_SHA512_DIGEST_LENGTH)
    let messageData = string.data(using:.utf8)!
    var digestData = Data(count: length)

    _ = digestData.withUnsafeMutableBytes { digestBytes -> UInt8 in
        messageData.withUnsafeBytes { messageBytes -> UInt8 in
            if let messageBytesBaseAddress = messageBytes.baseAddress, let digestBytesBlindMemory = digestBytes.bindMemory(to: UInt8.self).baseAddress {
                let messageLength = CC_LONG(messageData.count)
                CC_SHA512(messageBytesBaseAddress, messageLength, digestBytesBlindMemory)
            }
            return 0
        }
    }
    return digestData
}
Theorize answered 4/2, 2022 at 18:16 Comment(1)
you need to import CommonCryptoValuator

© 2022 - 2024 — McMap. All rights reserved.