Reputation: 1098
I'm having the weirdest problem with the Keychain. I've got an existing app which is using an encrypted Realm database, and the encryption key is being saved to the Keychain, as per Realm's code example here.
- (NSData *)getKey {
// Identifier for our keychain entry - should be unique for your application
static const uint8_t kKeychainIdentifier[] = "io.Realm.EncryptionExampleKey";
NSData *tag = [[NSData alloc] initWithBytesNoCopy:(void *)kKeychainIdentifier
length:sizeof(kKeychainIdentifier)
freeWhenDone:NO];
// First check in the keychain for an existing key
NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassKey,
(__bridge id)kSecAttrApplicationTag: tag,
(__bridge id)kSecAttrKeySizeInBits: @512,
(__bridge id)kSecReturnData: @YES};
CFTypeRef dataRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataRef);
if (status == errSecSuccess) {
return (__bridge NSData *)dataRef;
}
// No pre-existing key from this application, so generate a new one
uint8_t buffer[64];
status = SecRandomCopyBytes(kSecRandomDefault, 64, buffer);
NSAssert(status == 0, @"Failed to generate random bytes for key");
NSData *keyData = [[NSData alloc] initWithBytes:buffer length:sizeof(buffer)];
// Store the key in the keychain
query = @{(__bridge id)kSecClass: (__bridge id)kSecClassKey,
(__bridge id)kSecAttrApplicationTag: tag,
(__bridge id)kSecAttrKeySizeInBits: @512,
(__bridge id)kSecValueData: keyData};
status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
NSAssert(status == errSecSuccess, @"Failed to insert new key in the keychain");
return keyData;
}
I'm working on converting this app to Swift, and I'm trying to retrieve the encryption key stored in the Keychain using Realm's swift code example here
func getKey() -> NSData {
// Identifier for our keychain entry - should be unique for your application
let keychainIdentifier = "io.Realm.EncryptionExampleKey"
let keychainIdentifierData = keychainIdentifier.data(using: String.Encoding.utf8, allowLossyConversion: false)!
// First check in the keychain for an existing key
var query: [NSString: AnyObject] = [
kSecClass: kSecClassKey,
kSecAttrApplicationTag: keychainIdentifierData as AnyObject,
kSecAttrKeySizeInBits: 512 as AnyObject,
kSecReturnData: true as AnyObject
]
// To avoid Swift optimization bug, should use withUnsafeMutablePointer() function to retrieve the keychain item
// See also: http://stackoverflow.com/questions/24145838/querying-ios-keychain-using-swift/27721328#27721328
var dataTypeRef: AnyObject?
var status = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) }
if status == errSecSuccess {
return dataTypeRef as! NSData
}
// No pre-existing key from this application, so generate a new one
let keyData = NSMutableData(length: 64)!
let result = SecRandomCopyBytes(kSecRandomDefault, 64, keyData.mutableBytes.bindMemory(to: UInt8.self, capacity: 64))
assert(result == 0, "Failed to get random bytes")
// Store the key in the keychain
query = [
kSecClass: kSecClassKey,
kSecAttrApplicationTag: keychainIdentifierData as AnyObject,
kSecAttrKeySizeInBits: 512 as AnyObject,
kSecValueData: keyData
]
status = SecItemAdd(query as CFDictionary, nil)
assert(status == errSecSuccess, "Failed to insert the new key in the keychain")
return keyData
}
Now the problem is that the Swift code can't return the value stored in the Keychain by the Objective-C code. I've tried modifying the Swift code to use NSData instead of Data when creating the keychainIdentifierData, but I just can't get it to work.
In my actual project, the SecItemCopyMatching call returns a successful status, but the dataTypeRef is always nil, which crashes at the force cast to Data. In a small test project I created for this, the key is not found, which seems to indicate that the problem lies in converting the keychainIdentifier into the keychainIdentifierData, but I can't find any information now how to do this consistently between languages.
A clue I've discovered is in printing out the tag/keychainIdentifierData to the console, in Obj-c it is
<696f2e52 65616c6d 2e456e63 72797074 696f6e45 78616d70 6c654b65 7900>
where in Swift it's
<696f2e52 65616c6d 2e456e63 72797074 696f6e45 78616d70 6c654b65 79>
Which indicates that the keys don't match between languages, but then I don't understand why SecItemCopyMatching returns success.
Does anybody have any insight on how to retrieve my encryption key? Thanks in advance :)
Upvotes: 4
Views: 2490
Reputation: 1098
Creating an answer based off ncke's answer/comment:
The key was appending a null terminator to the end of my Swift keychainIdentifierData like so
keychainIdentifierData.append(0)
Upvotes: 0
Reputation: 1094
The Objective-C keychainIdentifier
is being created as a C-string, which are always null terminated. The null terminator is encoded along with the characters which causes the trailing '00' that you noticed is appended to the result. Swift strings are not null terminated.
To achieve parity, omit the last character when creating the Objective-C identifier:
static const uint8_t kKeychainIdentifier[] = "io.Realm.EncryptionExampleKey";
NSData *tag = [[NSData alloc] initWithBytesNoCopy:(void *)kKeychainIdentifier
length:sizeof(kKeychainIdentifier) - 1 // <- Truncate last char
freeWhenDone:NO];
Alternatively, use an NSString to represent your identifier in Objective-C:
NSString *keychainIdentifier = @"io.Realm.EncryptionExampleKey";
NSData *tag = [keychainIdentifier dataUsingEncoding:NSUTF8StringEncoding];
Upvotes: 7