Unique Installation Identifier (UIID) as a replacement of the depreciated UDID. Repository available here: https://github.com/akisute/UIApplication-UIID
// | |
// UIApplication+UIID.h | |
// UIID | |
// | |
// Created by akisute on 11/08/22. | |
// | |
#import <UIKit/UIKit.h> | |
/*! | |
Define UIID_PERSISTENT=1 to enable persistent UIID via Keychain. | |
This feature requires Security.framework to be included to your projects. | |
*/ | |
#ifndef UIID_PERSISTENT | |
#define UIID_PERSISTENT 0 | |
#endif | |
@interface UIApplication (UIApplication_UIID) | |
/*! | |
Returns the unique identifier for this installation of the application. | |
This value will change when: | |
- If UIID_PERSISTENT=1, -resetUniqueInstallationIdentifier is called or the application is installed to the completely new device without using any backups. | |
- If UIID_PERSISTENT=0, -resetUniqueInstallationIdentifier is called or the application is removed from the current device. | |
*/ | |
- (NSString *)uniqueInstallationIdentifier; | |
/*! Resets the persisted unique identifier for this installation. */ | |
- (void)resetUniqueInstallationIdentifier; | |
@end |
// | |
// UIApplication+UIID.m | |
// UIID | |
// | |
// Created by akisute on 11/08/22. | |
// | |
#import "UIApplication+UIID.h" | |
#if UIID_PERSISTENT | |
// Use keychain as a storage | |
#import <Security/Security.h> | |
#endif | |
static NSString * const UIApplication_UIID_Key = @"uniqueInstallationIdentifier"; | |
@implementation UIApplication (UIApplication_UIID) | |
- (NSString *)uniqueInstallationIdentifier | |
{ | |
// Search for already created UIID | |
// If found, return it | |
// If not found, create a new UUID as a new UIID of this installation and save it | |
NSString *uuidString = nil; | |
#if UIID_PERSISTENT | |
// UIID must be persistent even if the application is removed from devices | |
// Use keychain as a storage | |
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys: | |
(id)kSecClassGenericPassword, (id)kSecClass, | |
UIApplication_UIID_Key, (id)kSecAttrGeneric, | |
UIApplication_UIID_Key, (id)kSecAttrAccount, | |
[[NSBundle mainBundle] bundleIdentifier],(id)kSecAttrService, | |
(id)kSecMatchLimitOne, (id)kSecMatchLimit, | |
(id)kCFBooleanTrue, (id)kSecReturnAttributes, | |
nil]; | |
NSDictionary *attributes = nil; | |
OSStatus result = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *)&attributes); | |
if (result == noErr) { | |
NSMutableDictionary *valueQuery = [NSMutableDictionary dictionaryWithDictionary:attributes]; | |
[attributes release]; | |
[valueQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; | |
[valueQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData]; | |
NSData *passwordData = nil; | |
OSStatus result = SecItemCopyMatching((CFDictionaryRef)valueQuery, (CFTypeRef *)&passwordData); | |
if (result == noErr) { | |
// Assume the stored data is a UTF-8 string. | |
uuidString = [[[NSString alloc] initWithBytes:[passwordData bytes] | |
length:[passwordData length] | |
encoding:NSUTF8StringEncoding] autorelease]; | |
[passwordData release]; | |
} | |
} | |
#else | |
// UIID may not be persistent | |
// Use NSUserDefalt as a storage | |
// WARNING: this could be much more vulnerable since the NSUserDefaults stores values as a plist file. Any jailbroken user can extract values from it. | |
uuidString = [[NSUserDefaults standardUserDefaults] stringForKey:UIApplication_UIID_Key]; | |
#endif | |
if (uuidString == nil) { | |
CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault); | |
uuidString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, uuidRef); | |
[uuidString autorelease]; | |
CFRelease(uuidRef); | |
#if UIID_PERSISTENT | |
// UIID must be persistent even if the application is removed from devices | |
// Use keychain as a storage | |
NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys: | |
(id)kSecClassGenericPassword, (id)kSecClass, | |
UIApplication_UIID_Key, (id)kSecAttrGeneric, | |
UIApplication_UIID_Key, (id)kSecAttrAccount, | |
[[NSBundle mainBundle] bundleIdentifier], (id)kSecAttrService, | |
@"", (id)kSecAttrLabel, | |
@"", (id)kSecAttrDescription, | |
nil]; | |
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 4) { | |
// kSecAttrAccessible is iOS 4 or later only | |
// Current device is running on iOS 3.X, do nothing here | |
} else { | |
// Set kSecAttrAccessibleAfterFirstUnlock so that background applications are able to access this key. | |
// Keys defined as kSecAttrAccessibleAfterFirstUnlock will be migrated to the new devices/installations via encrypted backups. | |
// If you want different UIID per device, use kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly instead. | |
// Keep in mind that keys defined as kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly will be removed after restoring from a backup. | |
[query setObject:(id)kSecAttrAccessibleAfterFirstUnlock forKey:(id)kSecAttrAccessible]; | |
} | |
[query setObject:[uuidString dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData]; | |
OSStatus result = SecItemAdd((CFDictionaryRef)query, NULL); | |
if (result != noErr) { | |
NSLog(@"[ERROR] Couldn't add the Keychain Item. result = %ld query = %@", result, query); | |
return nil; | |
} | |
#else | |
// UIID may not be persistent | |
// Use NSUserDefalt as a storage | |
// WARNING: this could be much more vulnerable since the NSUserDefaults stores values as a plist file. Any jailbroken user can extract values from it. | |
[[NSUserDefaults standardUserDefaults] setObject:uuidString forKey:UIApplication_UIID_Key]; | |
#endif | |
} | |
return uuidString; | |
} | |
- (void)resetUniqueInstallationIdentifier | |
{ | |
#if UIID_PERSISTENT | |
// UIID must be persistent even if the application is removed from devices | |
// Use keychain as a storage | |
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys: | |
(id)kSecClassGenericPassword, (id)kSecClass, | |
UIApplication_UIID_Key, (id)kSecAttrGeneric, | |
UIApplication_UIID_Key, (id)kSecAttrAccount, | |
[[NSBundle mainBundle] bundleIdentifier],(id)kSecAttrService, | |
nil]; | |
OSStatus result = SecItemDelete((CFDictionaryRef)query); | |
if (result == noErr) { | |
NSLog(@"[INFO} Unique Installation Identifier is successfully reset."); | |
} else if (result == errSecItemNotFound) { | |
NSLog(@"[INFO} Unique Installation Identifier is successfully reset."); | |
} else { | |
NSLog(@"[ERROR] Coudn't delete the Keychain Item. result = %ld query = %@", result, query); | |
} | |
#else | |
// UIID may not be persistent | |
// Use NSUserDefalt as a storage | |
// WARNING: this could be much more vulnerable since the NSUserDefaults stores values as a plist file. Any jailbroken user can extract values from it. | |
[[NSUserDefaults standardUserDefaults] removeObjectForKey:UIApplication_UIID_Key]; | |
NSLog(@"[INFO} Unique Installation Identifier is successfully reset."); | |
#endif | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment