10

This is my function. First println print correct hash to console but in the next row program crashes. Can you help me?

func sha256(string: NSString) -> NSString {
    var data : NSData! = string.dataUsingEncoding(NSUTF8StringEncoding)
    var hash = [UInt8](count: Int(CC_SHA256_DIGEST_LENGTH), repeatedValue: 0)
    CC_SHA256(data.bytes, CC_LONG(data.length), &hash)
    let res = NSData(bytes: hash, length: Int(CC_SHA256_DIGEST_LENGTH))
    println(res)
    let resstr = NSString(data: res, encoding: NSUTF8StringEncoding)
    println(resstr)
    return resstr
}
NSNoob
  • 5,548
  • 6
  • 41
  • 54
Yury Alexandrov
  • 2,368
  • 5
  • 17
  • 17
  • 1
    Not all data can be represented by a string. The possibility that a hash (SHA-256) is representable is essentially to zero. This is because the result of SHA-256 (hashs in general) is binary data. Even in the unlikely event that it it could it would not be "reasonable" text. – zaph Aug 21 '14 at 11:59

1 Answers1

41
let resstr = NSString(data: res, encoding: NSUTF8StringEncoding)

returns nil if the data does not represent a valid UTF-8 sequence (which is very likely). Then the following println() crashes.

A possible string representation for arbitrary binary data would be a hex string or a Base-64 encoded string.

A Base-64 encoded string can simply be obtained with

let resstr = res.base64EncodedStringWithOptions(nil)

There is (as far as I know) no built-in method to convert binary data to a hex string. A possible implementation in Swift (inspired by the lots of available Objective-C solutions) is

extension NSData {
    func hexString() -> NSString {
        var str = NSMutableString()
        let bytes = UnsafeBufferPointer<UInt8>(start: UnsafePointer(self.bytes), count:self.length)
        for byte in bytes {
            str.appendFormat("%02hhx", byte)
        }
        return str
    }
}

But you could integrate that into your hash method directly, without using an intermediate NSData object:

func sha256(string: NSString) -> NSString {
    let data = string.dataUsingEncoding(NSUTF8StringEncoding)!
    var hash = [UInt8](count: Int(CC_SHA256_DIGEST_LENGTH), repeatedValue: 0)
    CC_SHA256(data.bytes, CC_LONG(data.length), &hash)
    let resstr = NSMutableString()
    for byte in hash {
        resstr.appendFormat("%02hhx", byte)
    }
    return resstr
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • how can i convert data to UTF-8? console shows res as "74696c22 2d3233cf c5dc040e 70ec9f5b dbcea3cf 53946368 65bb4382 64c782e5", which is exactly what i need, but your method gives "dGlsIi0yM8/F3AQOcOyfW9vOo89TlGNoZbtDgmTHguU=", which is wrong – Yury Alexandrov Aug 21 '14 at 11:55
  • @ЮрикАлександров: `<74696c22 2d3233cf …>` is the *description* of the NSData object. There is no built-in method to convert NSData to a hex string, but you will find lot's of solutions if you search for "NSData to hex string", for example http://stackoverflow.com/questions/1305225/best-way-to-serialize-a-nsdata-into-an-hexadeximal-string or http://stackoverflow.com/questions/7520615/how-to-convert-an-nsdata-into-an-nsstring-hex-string. – Martin R Aug 21 '14 at 12:10
  • 1
    @ЮрикАлександров: I have added some Swift code for your convenience :) – Martin R Aug 21 '14 at 12:29
  • "Then the following println() crashes." `println()` does not crash. `println()` can print optionals with value `nil`. The crash happens on the previous line, because the compiler infers `resstr` to have type `NSString`, which unwraps the optional. If he had explicitly declared `resstr` to have type `NSString!` or `NSString?`, it would not crash. – newacct Aug 21 '14 at 18:47
  • @newacct: Well, I have single-stepped it, and it *definitely* crashes at `println(resstr)`. The reason is probably that the initializer `init(data: NSData, encoding: UInt)` does *not* return an optional, see "Swift does not support object initializers that fail by returning null" in the beta 6 release notes. - If you change the assignment to `let resstr : NSString? = NSString(data: res, encoding: NSUTF8StringEncoding)` then `println(resstr)` prints "nil". – Martin R Aug 21 '14 at 19:04
  • @MartinR: Hmm, you are right. That is very interesting. It looks like `foo` really has the value `nil` (it seems that they don't check when assigning the result of an initializer), but it is of a type that doesn't allow `nil`, causing undefined behavior. In my opinion this is a hole in the type system that shouldn't be there. – newacct Aug 21 '14 at 20:35
  • @MartinR: I had originally thought that initializers return optional but the compiler infers non-optional when you assign it to a variable unless you explicitly specify optional, thus causing an unwrapping error. But now I finally realize that the compiler thinks that initializers return non-optional, and if it returns `nil`, it's actually an invalid value of that non-optional type. The reason the "workaround" of assigning to optional works is because assignment from non-optional object pointer to optional object pointer is a simple assignment between object pointers at runtime. – newacct Aug 21 '14 at 22:28