How to hash NSString with SHA1 in Swift?

SwiftSha1

Swift Problem Overview


In objective-c it looks like this:

#include <sys/xattr.h>

@implementation NSString (reverse)

-(NSString*)sha1
{
    NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
    uint8_t digest[CC_SHA1_DIGEST_LENGTH];
    CC_SHA1(data.bytes, (int)data.length, digest);
    NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
    for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++)
        [output appendFormat:@"%02x", digest[i]];
    return output;
}

@end

I need something like this with Swift, is it possible?

Please, show work example.

Swift Solutions


Solution 1 - Swift

Your Objective-C code (using a NSString category) can be directly translated to Swift (using a String extension).

First you have to create a "bridging header" and add

#import <CommonCrypto/CommonCrypto.h>

Then:

extension String {
    func sha1() -> String {
        let data = self.dataUsingEncoding(NSUTF8StringEncoding)!
        var digest = [UInt8](count:Int(CC_SHA1_DIGEST_LENGTH), repeatedValue: 0)
        CC_SHA1(data.bytes, CC_LONG(data.length), &digest)
        let output = NSMutableString(capacity: Int(CC_SHA1_DIGEST_LENGTH))
        for byte in digest {
            output.appendFormat("%02x", byte)
        }
        return output as String
    }
}

println("Hello World".sha1())

This can be written slightly shorter and Swifter as

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

Update for Swift 2:

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

To return a Base-64 encoded string instead of a hex encoded string, just replace

		let hexBytes = digest.map { String(format: "%02hhx", $0) }
		return hexBytes.joinWithSeparator("")

with

        return NSData(bytes: digest, length: digest.count).base64EncodedStringWithOptions([])

Update for Swift 3:

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

To return a Base-64 encoded string instead of a hex encoded string, just replace

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

by

        return Data(bytes: digest).base64EncodedString()

Update for Swift 4:

The bridging header file is no longer needed, one can import CommonCrypto instead:

import CommonCrypto

extension String {
	func sha1() -> String {
		let data = Data(self.utf8)
		var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
		data.withUnsafeBytes { 
			_ = CC_SHA1($0, CC_LONG(data.count), &digest)
		}
		let hexBytes = digest.map { String(format: "%02hhx", $0) }
		return hexBytes.joined()
	}
}

Update for Swift 5:

The Data.withUnsafeBytes() method now calls the closure with an UnsafeRawBufferPointer to, and baseAddress is used to pass the initial address to the C function:

import CommonCrypto

extension String {
	func sha1() -> String {
		let data = Data(self.utf8)
		var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
		data.withUnsafeBytes { 
			_ = CC_SHA1($0.baseAddress, CC_LONG(data.count), &digest)
		}
		let hexBytes = digest.map { String(format: "%02hhx", $0) }
		return hexBytes.joined()
	}
}

Solution 2 - Swift

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 = Insecure.SHA1.hash(data: data)
    print(digest.data) // 20 bytes
    print(digest.hexStr) // 2AAE6C35C94FCFB415DBE95F408B9CE91EE846ED
}

Solution 3 - Swift

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 sha1: String {
        if #available(iOS 13.0, *) {
            return hexString(Insecure.SHA1.hash(data: self).makeIterator())
        } else {
            var digest = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH))
            self.withUnsafeBytes { bytes in
                _ = CC_SHA1(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)!.sha1
assert(hexDigest == "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12")

Also available via Swift package manager:
https://github.com/ralfebert/TinyHashes

Solution 4 - Swift

Yes, it's possible, copy this class into your project. https://github.com/idrougge/sha1-swift

And it will be easy like:

 SHA1.hexString(from: "myPhrase" )!

Tested for swift 3 and swift 4.

Solution 5 - Swift

To get the result as NSData, provided that you included <CommonCrypto/CommonCrypto.h> in your bridging header:

extension NSData {

    func sha1() -> NSData? {
        let len = Int(CC_SHA1_DIGEST_LENGTH)
        let digest = UnsafeMutablePointer<UInt8>.alloc(len)
        CC_SHA1(bytes, CC_LONG(length), digest)
        return NSData(bytesNoCopy: UnsafeMutablePointer<Void>(digest), length: len)
    }
}

Also uses proper pointer allocation. Invoke it like this:

myString.dataUsingEncoding(NSUTF8StringEncoding)?.sha1()

If you need a hex representation of NSData have a look at my other answer.

Solution 6 - Swift

We can extract logic for encrypting string using sha1 for three steps:

  1. Convert string to Data object
  2. Encrypt data using SHA1 function to Data
  3. Convert data object to hex string

IMHO it's much more readable and this version doesn't require NSData.

    extension String {

        var sha1: String {
            guard let data = data(using: .utf8, allowLossyConversion: false) else {
                // Here you can just return empty string or execute fatalError with some description that this specific string can not be converted to data
            }
            return data.digestSHA1.hexString
        }

    }

    fileprivate extension Data {

        var digestSHA1: Data {
            var bytes: [UInt8] = Array(repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH))

            withUnsafeBytes {
                _ = CC_SHA1($0, CC_LONG(count), &bytes)
            }

            return Data(bytes: bytes)
        }

        var hexString: String {
            return map { String(format: "%02x", UInt8($0)) }.joined()
        }

    }

Solution 7 - Swift

Yes, it's possible: make that objective-c code accessible from swift

See documentation.

I would avoid rewriting it in swift if you won't get any benefit (such as using swift-specific features).

Also, in a project I am working on I used some objective-c code similar to yours to handle hashes. At beginning I started writing it in swift, then I realized that it was just easier and better to reuse old good obj-c.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionimikeView Question on Stackoverflow
Solution 1 - SwiftMartin RView Answer on Stackoverflow
Solution 2 - SwiftduanView Answer on Stackoverflow
Solution 3 - SwiftRalf EbertView Answer on Stackoverflow
Solution 4 - SwiftBenjamin RDView Answer on Stackoverflow
Solution 5 - SwiftEraView Answer on Stackoverflow
Solution 6 - SwiftMateusz SzklarekView Answer on Stackoverflow
Solution 7 - SwiftAntonioView Answer on Stackoverflow