3

I need unique ID in my app and i know, we cannot use UDID anymore so as per my research, using UUID as device unique ID and saving it in keychain will ensure the unique id remain same even if user reinstalls my app.

I found the below code from one of the answer to similar question here on stackoverflow however, i need to know how to access keychainUtils and IDManager? Appreciate your help in advance.

+ (NSString *) getUniqueUUID {
    NSError * error;
    NSString * uuid = [KeychainUtils getPasswordForUsername:USER_NAME   andServiceName:SERVICE_NAME error:&error];
    if (error) {
        NSLog(@"Error geting unique UUID for this device! %@", [error localizedDescription]);
        return nil;
    }
    if (!uuid) {
        DLog(@"No UUID found. Creating a new one.");
        uuid = [IDManager GetUUID];
        uuid = [Util md5String:uuid];
        [KeychainUtils storeUsername:kBuyassUser andPassword:uuid forServiceName:kIdOgBetilngService updateExisting:YES error:&error];
        if (error) {
            NSLog(@"Error geting unique UUID for this device! %@", [error localizedDescription]);
            return nil;
        }
    }
    return uuid;
}
Simon Goldeen
  • 9,080
  • 3
  • 36
  • 45
DevCali
  • 398
  • 6
  • 20

2 Answers2

4

I'd like to offer this as a solution. It requires no third party code. It reads or writes to the KeyChain if it absolutely needs to, so it's more efficient that the solutions provided so far. Also, the UUID is written to a keychain access group so that you can share this UUID between your apps. Simply change kKeyChainVendorIDAccessGroup at the start of the method.

+(NSUUID *)persistentIdentifierForVendor
{
    static NSString * const kKeyChainVendorID = @"co.cwbrn.PersistentIdentifier";
    static NSString * const kKeyChainVendorIDAccessGroup = @"<AppIdentifier>.<keychain-access-group-identifier>";

    // First, check NSUserDefaults so that we're not hitting the KeyChain every single time
    NSString *uuidString = [[NSUserDefaults standardUserDefaults] stringForKey:kKeyChainVendorIDGroup];
    BOOL vendorIDMissingFromUserDefaults = (uuidString == nil || uuidString.length == 0);

    if (vendorIDMissingFromUserDefaults) {
        // Check to see if a UUID is stored in the KeyChain
        NSDictionary *query = @{
                                (__bridge id)kSecClass:             (__bridge id)kSecClassGenericPassword,
                                (__bridge id)kSecAttrAccount:       kKeyChainVendorID,
                                (__bridge id)kSecAttrService:       kKeyChainVendorID,
                                (__bridge id)kSecAttrAccessGroup:   kKeyChainVendorIDAccessGroup,
                                (__bridge id)kSecMatchLimit:        (__bridge id)kSecMatchLimitOne,
                                (__bridge id)kSecReturnAttributes:  (__bridge id)kCFBooleanTrue
                               };
        CFTypeRef attributesRef = NULL;
        OSStatus result = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attributesRef);
        if (result == noErr) {
            // There is a UUID, so try to retrieve it
            NSDictionary *attributes = (__bridge_transfer NSDictionary *)attributesRef;
            NSMutableDictionary *valueQuery = [NSMutableDictionary dictionaryWithDictionary:attributes];

            [valueQuery setObject:(__bridge id)kSecClassGenericPassword  forKey:(__bridge id)kSecClass];
            [valueQuery setObject:(__bridge id)kCFBooleanTrue            forKey:(__bridge id)kSecReturnData];

            CFTypeRef passwordDataRef = NULL;
            OSStatus result = SecItemCopyMatching((__bridge CFDictionaryRef)valueQuery, &passwordDataRef);
            if (result == noErr) {
                NSData *passwordData = (__bridge_transfer NSData *)passwordDataRef;
                uuidString = [[NSString alloc] initWithBytes:[passwordData bytes]
                                                      length:[passwordData length]
                                                    encoding:NSUTF8StringEncoding];
            }
        }
    }

    // Failed to read the UUID from the KeyChain, so create a new UUID and store it
    if (uuidString == nil || uuidString.length == 0) {
        // Generate the new UIID
        CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
        uuidString = (__bridge_transfer NSString *)CFUUIDCreateString(kCFAllocatorDefault, uuidRef);
        CFRelease(uuidRef);

        // Now store it in the KeyChain
        NSDictionary *query = @{    (__bridge id)kSecClass:             (__bridge id)kSecClassGenericPassword,
                                    (__bridge id)kSecAttrAccount:       kKeyChainVendorID,
                                    (__bridge id)kSecAttrService:       kKeyChainVendorID,
                                    (__bridge id)kSecAttrAccessGroup:   kKeyChainVendorIDAccessGroup,
                                    (__bridge id)kSecAttrLabel:         @"",
                                    (__bridge id)kSecAttrDescription:   @"",
                                    (__bridge id)kSecAttrAccessible:    (__bridge id)kSecAttrAccessibleAfterFirstUnlock,
                                    (__bridge id)kSecValueData:         [uuidString dataUsingEncoding:NSUTF8StringEncoding]
                                };

        OSStatus result = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
        if (result != noErr) {
            NSLog(@"ERROR: Couldn't add to the Keychain. Result = %ld; Query = %@", result, query);
            return nil;
        }
    }

    // Save UUID to NSUserDefaults so that we can avoid the KeyChain next time
    if (vendorIDMissingFromUserDefaults) {
        [[NSUserDefaults standardUserDefaults] setObject:uuidString forKey:kKeyChainVendorIDGroup];
    }

    return [[NSUUID alloc] initWithUUIDString:uuidString];
}
neilco
  • 7,964
  • 2
  • 36
  • 41
  • Does this persist backup restores to the same device? Or more importantly does it persist backup restores to a new devices? – malhal Jan 15 '14 at 03:29
  • I think you should have used kSecAttrAccessibleAlways – malhal Jan 15 '14 at 03:33
  • 1
    `kSecAttrAccessibleAlwaysThisDeviceOnly`? – Markus Rautopuro Dec 08 '14 at 12:07
  • What is the problem (penalty?) with "hitting the keychain every single time"? In my case, I would only need to read the identifier **once** on startup, then cache it in RAM... – Nicolas Miari Jul 28 '15 at 06:57
  • Also, why do you call `SecItemCopyMatching()` twice in a row (first to check for existence, then to retrieve the actual data)? Couldn't you call it just once, with `kSecReturnData` set to `true`, and check the return value against `errSecItemNotFound`? – Nicolas Miari Jul 28 '15 at 07:35
0

This is what i have used for unique device ID and saving it in keychain using USHFKeychainUtils that is ARC compliant utility. it's a complete code that i hope will save you guys time!!

+ (NSString *) getUniqueUUID {
NSError * error;
NSString * uuid = [SFHFKeychainUtils getPasswordForUsername:USER_NAME andServiceName:SERVICE_NAME error:&error];
if (error) {
    NSLog(@"Error geting unique UUID for this device! %@", [error localizedDescription]);
    return nil;
}
if (!uuid) {
    NSLog(@"No UUID found. Creating a new one.");
    uuid = [self GetUUID];
    uuid = [self md5String:uuid];
    [SFHFKeychainUtils storeUsername:USER_NAME andPassword:uuid forServiceName:SERVICE_NAME updateExisting:YES error:&error];
    if (error) {
        NSLog(@"Error geting unique UUID for this device! %@", [error localizedDescription]);
        return nil;
    }
}
return uuid;
}


+ (NSString *)md5String:(NSString *)plainText

{
    if(plainText == nil || [plainText length] == 0)
        return nil;

    const char *value = [plainText UTF8String];
    unsigned char outputBuffer[CC_MD5_DIGEST_LENGTH];
    CC_MD5(value, strlen(value), outputBuffer);

    NSMutableString *outputString = [[NSMutableString alloc] initWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
    for(NSInteger count = 0; count < CC_MD5_DIGEST_LENGTH; count++){
        [outputString appendFormat:@"%02x",outputBuffer[count]];
    }
    NSString * retString = [NSString stringWithString:outputString];
    return retString;
}


+ (NSString *)GetUUID
{
    CFUUIDRef theUUID = CFUUIDCreate(NULL);
    CFStringRef string = CFUUIDCreateString(NULL, theUUID);
    CFRelease(theUUID);
    return (__bridge NSString *)string;
}
DevCali
  • 398
  • 6
  • 20