Have your ever wondered how to take advantage of the cryptographic interfaces that Apple provides for you, but just unsure where to start? If you so, then this tutorial will provide you a brief overview on how to implement the CommonCrypto APIs into your iOS or macOS application utilizing Swift 5. In this tutorial I will cover taking a clear text Swift string and creating a MD5 and SHA-256 hashed value from this string. Before we dive into the code though, let's talk about why you might want to use these hashing functions in your application. One reason would be to persist sensitive information that could not easily be decrypted if the device was compromised. For example, sensitive user information could be hashed and then saved into an encrypted enclave, thus double encrypting the data on disk. Another reason could be for sensitive communication strategies. For example, if your client and server use HMAC and JWTs for authentication and validation purposes, utilizing these hashing routines might also be included in this process. These are just a couple examples of utilizing cryptographic interfaces on Apple platforms, but enough about the why, let's jump into the code!
NOTE: This tutorial is not meant to be a complete overview of how CommonCrypto works on Apple platforms, but simply a tutorial on how to use the APIs in your application. If there is a desire to know more about how CommonCrypto and other cryptographic solutions work on Apple platforms, please let me know in the comments and I can certainly write an article about this too. Likewise, if there is a desire to know how SHA-256 and MD5 message digest algorithms work, please let me know. I would be happy to do an article on this too.
MD5 with CommonCrypto
MD5 is a message digest algorithm that creates a 128 bit hash value out of a plain text buffer. In this example the string value is converted to a Swift data type, using UTF-8 encoding. Next an array of 16 8 bit unsigned integers called a digest is created holding zeros at each index. This digest array gives the algorithm the base for the 128 bit (16 ints * 8bits) hash value created. Next the strData variable uses a restructured Swift 5 API to access a pointer to the contiguous bytes held in memory for the data this variable holds. Next the one-shot CC_MD5 method is called with a pointer to the value of strData ($0.baseAddress), the length of the data held in strData (UInt32(strData.count)), and lastly a reference to where the digest is stored so that CC_MD5 can write the 128 bits to that digest value. Once the digest has been written to, a while loop is used to loop through each byte and converts the UInt8 to a hex value with padding if a single digit is present and write that value to a string. The result is the md5String which can be viewed in plain text again: 8D84E6C45CE9044CAE90C064997ACFF1.
import Foundation import CommonCrypto var str = "Agnostic Development" /** * Example MD5 Has using CommonCrypto * CC_MD5 API exposed from CommonCrypto-60118.50.1: * https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60118.50.1/include/CommonDigest.h.auto.html **/ func md5Hash (str: String) -> String { if let strData = str.data(using: String.Encoding.utf8) { /// #define CC_MD5_DIGEST_LENGTH 16 /* digest length in bytes */ /// Creates an array of unsigned 8 bit integers that contains 16 zeros var digest = [UInt8](repeating: 0, count:Int(CC_MD5_DIGEST_LENGTH)) /// CC_MD5 performs digest calculation and places the result in the caller-supplied buffer for digest (md) /// Calls the given closure with a pointer to the underlying unsafe bytes of the strData’s contiguous storage. strData.withUnsafeBytes { // CommonCrypto // extern unsigned char *CC_MD5(const void *data, CC_LONG len, unsigned char *md) --| // OpenSSL | // unsigned char *MD5(const unsigned char *d, size_t n, unsigned char *md) <-| CC_MD5($0.baseAddress, UInt32(strData.count), &digest) } var md5String = "" /// Unpack each byte in the digest array and add them to the md5String for byte in digest { md5String += String(format:"%02x", UInt8(byte)) } // MD5 hash check (This is just done for example) if md5String.uppercased() == "8D84E6C45CE9044CAE90C064997ACFF1" { print("Matching MD5 hash: 8D84E6C45CE9044CAE90C064997ACFF1") } else { print("MD5 hash does not match: \(md5String)") } return md5String } return "" } let md5HashStr = md5Hash(str: str) print(md5HashStr)
SHA-256 with CommonCrypto
SHA-256 with CommonCrypto is almost exactly similar to the routine used for MD5, but instead of a 128 bit output, there is a 256 bit output, hence the name, SHA-256. So compared to the MD5 algorithm you can probably guess that the constant length value used is now 32 instead of 16, giving the digest a based value of 8 unsigned integer bits * 32, which equals 256. Other than the base layout of the digest, the other major difference is the usage of the one-shot function for CC_SHA256 that performs the same routine of taking the pointer to the UTF encoded data and writing it to the fixed width 256 bit digest value. Lastly the bytes are transferred to hex values and written to the sha256String for the clear text result of E8721A6EBEA3B23768D943D075035C7819662B581E487456FDB1A7129C769188.
import Foundation import CommonCrypto var str = "Agnostic Development" /** * Example SHA 256 Hash using CommonCrypto * CC_SHA256 API exposed from from CommonCrypto-60118.50.1: * https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60118.50.1/include/CommonDigest.h.auto.html **/ func sha256(str: String) -> String { if let strData = str.data(using: String.Encoding.utf8) { /// #define CC_SHA256_DIGEST_LENGTH 32 /// Creates an array of unsigned 8 bit integers that contains 32 zeros var digest = [UInt8](repeating: 0, count:Int(CC_SHA256_DIGEST_LENGTH)) /// CC_SHA256 performs digest calculation and places the result in the caller-supplied buffer for digest (md) /// Takes the strData referenced value (const unsigned char *d) and hashes it into a reference to the digest parameter. strData.withUnsafeBytes { // CommonCrypto // extern unsigned char *CC_SHA256(const void *data, CC_LONG len, unsigned char *md) -| // OpenSSL | // unsigned char *SHA256(const unsigned char *d, size_t n, unsigned char *md) <-| CC_SHA256($0.baseAddress, UInt32(strData.count), &digest) } var sha256String = "" /// Unpack each byte in the digest array and add them to the sha256String for byte in digest { sha256String += String(format:"%02x", UInt8(byte)) } if sha256String.uppercased() == "E8721A6EBEA3B23768D943D075035C7819662B581E487456FDB1A7129C769188" { print("Matching sha256 hash: E8721A6EBEA3B23768D943D075035C7819662B581E487456FDB1A7129C769188") } else { print("sha256 hash does not match: \(sha256String)") } return sha256String } return "" } let sha256Str = sha256(str: str) print(sha256Str)
In Summary ⌛️
In Summary CommonCrypto in iOS and macOS has some very interesting use cases and I am always interested to see how Apple is evolving the Security and Cryptographic interfaces under the hood - their teams do a great job. To checkout more about CommonCrypto I would encourage you to look at the open source code that Apple has posted on their Security services developer page. On this page Apple has links to the APIs used in my code above. Also, if you are interested in the Playground I used for this tutorial I encourage you to check it out here on my Github page. Please let me know if you have any questions, comments, or concerns, or would like me to dive in deeper on any of these topics by leaving a comment below.
References:
- CommonCrypto-60118.50.1: https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60118.50.1/
- OpenSSL SHA: https://github.com/openssl/openssl/tree/master/crypto/sha
- OpenSSL MD5: https://github.com/openssl/openssl/tree/master/crypto/md5
- Corecrypto (For security verification purposes only): https://developer.apple.com/security/