3

I'm experimenting with encrypting data with Core Data and CommonCrypto. I am trying to use a NSValueTransformer to lazily encrypt and decrypt.

However when I'm now trying save the encrypted data to the persistent store coordinator, it fails. Every time im trying to save my data to the database, it gives me:

-[__NSCFString bytes]: unrecognized selector sent to instance

I'm sure it's some sort of database and NSManagedObject mismatch, but I can't figure it out. I feel it's probably rather simple, but I can't find the solution. My code:

NSValueTransformer

class TryHardEncryption: NSValueTransformer {

override class func transformedValueClass() -> AnyClass {
    return NSString.self
}

override class func allowsReverseTransformation() -> Bool {
    return true
}


override func reverseTransformedValue(value: AnyObject?) -> AnyObject? {
    if let message = value as? NSString {
        let keyString        = "12345678901234567890123456789012"
        let keyData: NSData! = (keyString as NSString).dataUsingEncoding(NSUTF8StringEncoding) as NSData!
        let keyBytes         = UnsafeMutablePointer<Void>(keyData.bytes)
        print("keyLength   = \(keyData.length), keyData   = \(keyData)")

        let data: NSData! = (message as NSString).dataUsingEncoding(NSUTF8StringEncoding) as NSData!
        let dataLength    = size_t(data.length)
        let dataBytes     = UnsafeMutablePointer<Void>(data.bytes)
        print("dataLength  = \(dataLength), data      = \(data)")

        let cryptData    = NSMutableData(length: Int(dataLength) + kCCBlockSizeAES128)
        let cryptPointer = UnsafeMutablePointer<Void>(cryptData!.mutableBytes)
        let cryptLength  = size_t(cryptData!.length)

        let keyLength              = size_t(kCCKeySizeAES256)
        let operation: CCOperation = UInt32(kCCDecrypt)
        let algoritm:  CCAlgorithm = UInt32(kCCAlgorithmAES128)
        let options:   CCOptions   = UInt32(kCCOptionPKCS7Padding + kCCOptionECBMode)

        var numBytesEncrypted :size_t = 0

        let cryptStatus = CCCrypt(operation,
            algoritm,
            options,
            keyBytes, keyLength,
            nil,
            dataBytes, dataLength,
            cryptPointer, cryptLength,
            &numBytesEncrypted)

        if UInt32(cryptStatus) == UInt32(kCCSuccess) {
            //  let x: UInt = numBytesEncrypted
            cryptData!.length = Int(numBytesEncrypted)
            print("DecryptcryptLength = \(numBytesEncrypted), Decrypt = \(cryptData)")

            // Not all data is a UTF-8 string so Base64 is used
            let base64cryptString = cryptData!.base64EncodedStringWithOptions(.Encoding64CharacterLineLength)
            print("base64DecryptString = \(base64cryptString)")
            print( "utf8 actual string = \(NSString(data: cryptData!, encoding: NSUTF8StringEncoding))");
            return base64cryptString
        } else {
            print("Error: \(cryptStatus)")
        }
    }
    return nil
}

override func transformedValue(value: AnyObject?) -> AnyObject? {
    if let message = value as? NSString {
        let keyString        = "12345678901234567890123456789012"
        let keyData: NSData! = (keyString as NSString).dataUsingEncoding(NSUTF8StringEncoding) as NSData!
        let keyBytes         = UnsafePointer<UInt8>(keyData.bytes)
        print("keyLength   = \(keyData.length), keyData   = \(keyData)")

        let data: NSData! = message.dataUsingEncoding(NSUTF8StringEncoding) as NSData!
        let dataLength    = Int(data.length)
        let dataBytes     = UnsafePointer<UInt8>(data.bytes)
        print("dataLength  = \(dataLength), data      = \(data)")

        let cryptData    = NSMutableData(length: Int(dataLength) + kCCBlockSizeAES128)!
        let cryptPointer = UnsafeMutablePointer<UInt8>(cryptData.mutableBytes)
        let cryptLength  = size_t(cryptData.length)

        let keyLength              = size_t(kCCKeySizeAES256)
        let operation: CCOperation = UInt32(kCCEncrypt)
        let algoritm:  CCAlgorithm = UInt32(kCCAlgorithmAES128)
        let options:   CCOptions   = UInt32(kCCOptionECBMode + kCCOptionPKCS7Padding)

        var numBytesEncrypted :size_t = 0

        let cryptStatus = CCCrypt(operation,
            algoritm,
            options,
            keyBytes, keyLength,
            nil,
            dataBytes, dataLength,
            cryptPointer, cryptLength,
            &numBytesEncrypted)

        if UInt32(cryptStatus) == UInt32(kCCSuccess) {
            cryptData.length = Int(numBytesEncrypted)
            print("cryptLength = \(numBytesEncrypted), cryptData = \(cryptData)")

            // Not all data is a UTF-8 string so Base64 is used
            let base64cryptString = cryptData.base64EncodedStringWithOptions(.Encoding64CharacterLineLength)
            print("base64cryptString = \(base64cryptString)")
            return NSString(string: base64cryptString) as NSObject

        } else {
            print("Error: \(cryptStatus)")
        }
    }
    return nil
}

}

What I had to do to get the NSValueTransformer to work is:

let transformer: TryHardEncryption = TryHardEncryption()
    NSValueTransformer.setValueTransformer(transformer, forName: "TryHardEncryption")

Without the above code, the NSValueTransformer was never called.

I've marked the database field as the Transformable type and named it: TryHardEncryption. Do you guys know what is wrong here?

EDIT The property it's regarding is:

@NSManaged var establishmentDescription: String?

Both the encrypt and decrypt functions return a String when I debug them.

Orion
  • 1,258
  • 2
  • 14
  • 32
  • Might be a simple case of updating the model and not generating new files for the modified entities, there might be a warning for that, not sure, but you might as well just go through your model classes and check them if your app is a small experimental app. – A-Live Oct 16 '15 at 14:09
  • I reverted all the changes in the NSManagedObject I made before the Transformable type change, but no luck again :( Thanks for the info though. – Orion Oct 16 '15 at 14:21
  • Have you tried to put an exception breakpoint and run the app with it ? It might show you the exact line where the issue is happening. Also regarding the fact you had to use `NSValueTransformer.setValueTransformer`, have you entered the transformer type name at the Data Model inspector (the new text field should appear below attribute type dropdown when you select transformable) ? – A-Live Oct 16 '15 at 14:40
  • When debugging I see the code going into the transformedValue method. I even print the result and all seems normal. I made sure the names were equal by copying the name field value that appeared in the data model inspector to the setvaluetransformer method. – Orion Oct 16 '15 at 14:45
  • Then your functions don't seem to be the problem, at the bottom of breakpoints navigator you can find a "+" button and then add a generic Exception Breakpoint from the opened menu, such breakpoint should show you the exact line where exception happens (it isn't always very effective but is helpful most of the time), you'll need to run the app with breakpoints enabled to let it work. Have you tried that ? – A-Live Oct 16 '15 at 15:20
  • Hmm the breakpoint does inform me of a new exception, but I get no additional information for my current error. I reverted the type back to string and everything seems normal. It has to be a type mismatch. – Orion Oct 19 '15 at 08:05
  • Do you get the line at your code, if so which line is it ? Anyway, you can try to enter "bt" at the console view when the app is stopped at breakpoint, debugger should print the backstack and this should make the source more clear even if this is a system framework. – A-Live Oct 19 '15 at 10:07
  • I finally figured it out... I was confused as to what type very object had to be and what my nsvaluetransformer had to return. I wasnt casting the value to the proper type in the nsvaluetransformer and thus I was returning nil in the code. However it wasnt crashing in a way that I would easily figure this out. Secondly I changed the entity column back to an NSObject. Returned an NSData object in the transformedValue method and a NSString in the reverseTransformedValue method. This was actually all i needed to make it work. Thanks a lot for you help. It indeed was a type error. – Orion Oct 19 '15 at 15:00

2 Answers2

0

I finally figured it out... I was confused as to what type very object had to be and what my nsvaluetransformer had to return. I wasnt casting the value to the proper type in the nsvaluetransformer and thus I was returning nil in the code. However it wasnt crashing in a way that I would easily figure this out. Secondly I changed the entity column back to an NSObject. Returned an NSData object in the transformedValue method and a NSString in the reverseTransformedValue method. This was actually all i needed to make it work. Thanks a lot for your help. It indeed was a type error.

Orion
  • 1,258
  • 2
  • 14
  • 32
0

To help anyone who would like to see the corrected code sample, replace the return line of transformedValue() in the question with this: return base64cryptString.dataUsingEncoding(NSUTF8StringEncoding)

r590
  • 689
  • 1
  • 10
  • 15