body:
Recently I started to learn a little bit of Swift in an attempt to build ImagrAdmin,
but I ran into a road block pretty quickly. How do I create a sha512 encoded string? Imagr
uses a sha512 encoded string for the password component at the root level of imagr_config.plist
. You can check
out the Imagr wiki on GitHub for more
details about this component.
A lot of organizations use this feature of Imagr since they likely have multiple people using their imaging tool or even to protect their end users from accidentally wiping their computers. As I mentioned before, I had just started to learn Swift so I could build this, but I had literally just started to look at it the week before. Trying to learn how Swift does cryptography was not something I had any idea about how to approach or where to begin. With this in mind I opted to replicate what Imagr instructs you to do with Python in the wiki, which meant using NSTask to run a single line of python and then parse the results.
Ok, now to the fun stuff. So I had seen previously that I might be able to use some the functionality from the CommonCrypto
framework, but Apple has not made that available via Swift. This is where the bridging header can be pretty helpful since
it will allow a Swift developer to call specific functions and constants from C/Objective-C only frameworks. The first thing
that I needed to do is create a bridging header file. I used the name ImagrAdmin-Bridging-Header.h
so you may want to use something in
the form of <ProjectName>-Bridging-Header.h
if you were to create on in your project.
You can name the file pretty much anything you want, but I went with ImagrAdmin-Bridging-Header.h
since
it makes sense to use names that are clear.
When you create that file it'll have the contents below and you can import any other header files
here. All I added for my app was #import <CommonCrypto/CommonCrypto.h>
. There are likely some
limitations to the bridging header, but it has proved helpful for this task at least.
ImagrAdmin-Bridging-Header.h
#ifndef ImagrAdmin_Bridging_Header_h
#define ImagrAdmin_Bridging_Header_h
#import <CommonCrypto/CommonCrypto.h>
#endif /* ImagrAdmin_Bridging_Header_h */
This is the most important step and really the part that actually makes this work. Your application needs to know that the header file that you've created has a specific purpose, otherwise it just looks like a normal header file.
Navigate to the Build Settings and then find the section called Swift Compiler - Code Generation. Here you can add the path to your bridging header and then when the application is compiled it will include frameworks listed in that file. Pretty neat!
This seemed silly, but I figured it worked and all of the examples for accomplishing the same thing in Swift were either unnecessarily complex or just made no sense to me. Luckily, I have one of the best resources I could ask for at my disposal, the #macadmins Slack team and @groob kindly pointed me toward a post on Stack Overflow. I had been hoping that noone would notice my "temporary" workaround, but he noticed this immediately within hours of me posting my code. This was awesome and was yet another reason why open source software and the community around it can be such an amazing thing. My code was visible for others to critique and instead of scoffing at me for taking a shortcut he pointed me in the right direction.
import Foundation
func hashPassword(password: String) -> String {
let task = NSTask()
task.launchPath = "/usr/bin/python"
task.arguments = ["-c", "import hashlib; print hashlib.sha512(\"\(password)\").hexdigest()"]
let stdOutput = NSPipe()
let outputHandler = stdOutput.fileHandleForReading
task.standardOutput = stdOutput
task.launch()
// Process the task stdout
let outputString = outputHandler.availableData
let stringRead = NSString(data: outputString, encoding: NSUTF8StringEncoding) as! String
let output = stringRead.stringByReplacingOccurrencesOfString(
"\n", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
return output
}
extension String {
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("")
}
}
The approach above is far simpler and it uses an extension to the String
class. The usage between the
two approaches does change all that much. Instead of using let hashedPass = hashPassword("supersecretpassword")
I would use let hashedPass = "supersecretpassword".sha512()
. This just feels like a nicer interface.
If there are errors or things you think should be changed about this post, you can open up a PR on Github by clicking the in the navigation bar.