Search code examples
swiftsha1

How to hash NSString with SHA1 in Swift?


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.


Solution

  • 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()
        }
    }