SvUDID实现设备唯一标示
//SvUDIDTools : https://github.com/smileEvday/SvUDID //将生成的UDID保存到钥匙串中,用户卸载app再重新安装UDID也不会改变. /* 用法1:(摘于网上的使用方法) 在工程目录下新建一个KeychainAccessGroups.plist文件,该文件的结构中最顶层的节点必须是一个名为“keychain-access-groups”的Array, 并且该Array中每一项都是一个描述分组的NSString。yourAppID.com.yourCompany.whatever就是你要起的公共区名称,除了whatever字段可以随便定之外, 其 他的都必须如实填写。这个文件的路径要配置 在 Project->build setting->Code Signing Entitlements里,否则公共区无效,配置好后, 须用你正式的证书签名编译才可通过, 否则xcode会弹框告诉你code signing有问题。所以,苹果限制了你只能同公司的产品共享 KeyChain数据,别的公司访问不了你公司产品的KeyChain。 如果拷贝:则将UID文件夹下面的KeychainAccessGroups.plist拷贝到同级于工程目录,并拖曳到xcode工程窗口修改响应的yourAppID.com.yourCompany.whatever 追加Build Phases->Compile Sources下面的SvUDIDTools.m文件配置信息字符串“-fno-objc-arc” 修改Build Setting->Code Signing->Code Signing Entitlements属性为KeychainAccessGroups.plist */ /*用法2:自己实际操作 Project -> Capabilities -> Keychain Sharing -> on, 会弹出提示框请求Fetching list of teams from the Developer Portal....,选择开发账号,确认. 生成entitlements文件, 网络不好时,可手动添加 KeychainAccessGroups.plist 用法类似 用法1. 自动生成的entitlements 已经将Keychain Access Groups 补充好, 不需再更改,Keychain Access Groups分组中会出现 $(AppIdentifierPrefix)com.moule.Utils-iOS ( $(AppIdentifierPrefix)开头的字段 ), Build Phases->Compile Sources下面的SvUDIDTools.m文件配置信息字符串“-fno-objc-arc” //获取开发账号的appID + (NSString *)bundleSeedID { NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:kSecClassGenericPassword, kSecClass,@"bundleSeedID", kSecAttrAccount,@"", kSecAttrService,(id)kCFBooleanTrue, kSecReturnAttributes,nil]; CFDictionaryRef result = nil; OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *)&result); if (status == errSecItemNotFound) status = SecItemAdd((CFDictionaryRef)query, (CFTypeRef *)&result); if (status != errSecSuccess) return nil; NSString *accessGroup = [(__bridge NSDictionary *)result objectForKey:kSecAttrAccessGroup]; NSArray *components = [accessGroup componentsSeparatedByString:@"."]; NSString *bundleSeedID = [[components objectEnumerator] nextObject]; CFRelease(result); return bundleSeedID; } kKeyChainUDIDAccessGroup 是应用的bundle ID */
.h文件 #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> @interface SvUDIDTools : NSObject /* * @brief obtain Unique Device Identity */ + (NSString*)UDID; @end
.m文件 // // SvUDIDTools.m // SvUDID // // Created by maple on 8/18/13. // Copyright (c) 2013 maple. All rights reserved. // #import "SvUDIDTools.h" #import <Security/Security.h> #include <sys/socket.h> #include <sys/sysctl.h> #include <net/if.h> #include <net/if_dl.h> // replace the identity with your company's domain static const char kKeychainUDIDItemIdentifier[] = "UUID"; static NSString * kKeyChainUDIDAccessGroup = @"com.moule.Utils-iOS"; @implementation SvUDIDTools + (NSString *)bundleSeedID { NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys: kSecClassGenericPassword, kSecClass, @"bundleSeedID", kSecAttrAccount, @"", kSecAttrService, (id)kCFBooleanTrue, kSecReturnAttributes, nil]; CFDictionaryRef result = nil; OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *)&result); if (status == errSecItemNotFound) status = SecItemAdd((CFDictionaryRef)query, (CFTypeRef *)&result); if (status != errSecSuccess) return nil; NSString *accessGroup = [(__bridge NSDictionary *)result objectForKey:kSecAttrAccessGroup]; NSArray *components = [accessGroup componentsSeparatedByString:@"."]; NSString *bundleSeedID = [[components objectEnumerator] nextObject]; CFRelease(result); return bundleSeedID; } + (NSString*)UDID { NSString *udid = [SvUDIDTools getUDIDFromKeyChain]; if (!udid) { NSString *sysVersion = [UIDevice currentDevice].systemVersion; CGFloat version = [sysVersion floatValue]; if (version >= 7.0) { udid = [SvUDIDTools _UDID_iOS7]; } else if (version >= 2.0) { udid = [SvUDIDTools _UDID_iOS6]; } [SvUDIDTools settUDIDToKeyChain:udid]; } return udid; } /* * iOS 6.0 * use wifi's mac address */ + (NSString*)_UDID_iOS6 { return [SvUDIDTools getMacAddress]; } /* * iOS 7.0 * Starting from iOS 7, the system always returns the value 02:00:00:00:00:00 * when you ask for the MAC address on any device. * use identifierForVendor + keyChain * make sure UDID consistency atfer app delete and reinstall */ + (NSString*)_UDID_iOS7 { return [[UIDevice currentDevice].identifierForVendor UUIDString]; } #pragma mark - #pragma mark Helper Method for Get Mac Address // from http://stackoverflow.com/questions/677530/how-can-i-programmatically-get-the-mac-address-of-an-iphone + (NSString *)getMacAddress { int mgmtInfoBase[6]; char *msgBuffer = NULL; size_t length; unsigned char macAddress[6]; struct if_msghdr *interfaceMsgStruct; struct sockaddr_dl *socketStruct; NSString *errorFlag = nil; // Setup the management Information Base (mib) mgmtInfoBase[0] = CTL_NET; // Request network subsystem mgmtInfoBase[1] = AF_ROUTE; // Routing table info mgmtInfoBase[2] = 0; mgmtInfoBase[3] = AF_LINK; // Request link layer information mgmtInfoBase[4] = NET_RT_IFLIST; // Request all configured interfaces // With all configured interfaces requested, get handle index if ((mgmtInfoBase[5] = if_nametoindex("en0")) == 0) errorFlag = @"if_nametoindex failure"; else { // Get the size of the data available (store in len) if (sysctl(mgmtInfoBase, 6, NULL, &length, NULL, 0) < 0) errorFlag = @"sysctl mgmtInfoBase failure"; else { // Alloc memory based on above call if ((msgBuffer = malloc(length)) == NULL) errorFlag = @"buffer allocation failure"; else { // Get system information, store in buffer if (sysctl(mgmtInfoBase, 6, msgBuffer, &length, NULL, 0) < 0) errorFlag = @"sysctl msgBuffer failure"; } } } // Befor going any further... if (errorFlag != NULL) { NSLog(@"Error: %@", errorFlag); if (msgBuffer) { free(msgBuffer); } return errorFlag; } // Map msgbuffer to interface message structure interfaceMsgStruct = (struct if_msghdr *) msgBuffer; // Map to link-level socket structure socketStruct = (struct sockaddr_dl *) (interfaceMsgStruct + 1); // Copy link layer address data in socket structure to an array memcpy(&macAddress, socketStruct->sdl_data + socketStruct->sdl_nlen, 6); // Read from char array into a string object, into traditional Mac address format NSString *macAddressString = [NSString stringWithFormat:@"%02X:%02X:%02X:%02X:%02X:%02X", macAddress[0], macAddress[1], macAddress[2], macAddress[3], macAddress[4], macAddress[5]]; NSLog(@"Mac Address: %@", macAddressString); // Release the buffer memory free(msgBuffer); return macAddressString; } #pragma mark - #pragma mark Helper Method for make identityForVendor consistency + (NSString*)getUDIDFromKeyChain { NSMutableDictionary *dictForQuery = [[NSMutableDictionary alloc] init]; [dictForQuery setValue:(id)kSecClassGenericPassword forKey:(id)kSecClass]; // set Attr Description for query [dictForQuery setValue:[NSString stringWithUTF8String:kKeychainUDIDItemIdentifier] forKey:kSecAttrDescription]; // set Attr Identity for query NSData *keychainItemID = [NSData dataWithBytes:kKeychainUDIDItemIdentifier length:strlen(kKeychainUDIDItemIdentifier)]; [dictForQuery setObject:keychainItemID forKey:(id)kSecAttrGeneric]; // The keychain access group attribute determines if this item can be shared // amongst multiple apps whose code signing entitlements contain the same keychain access group. NSString *accessGroup =[NSString stringWithFormat:@"%@.%@",[SvUDIDTools bundleSeedID],kKeyChainUDIDAccessGroup]; if (accessGroup != nil) { #if TARGET_IPHONE_SIMULATOR // Ignore the access group if running on the iPhone simulator. // // Apps that are built for the simulator aren't signed, so there's no keychain access group // for the simulator to check. This means that all apps can see all keychain items when run // on the simulator. // // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the // simulator will return -25243 (errSecNoAccessForItem). #else [dictForQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup]; #endif } [dictForQuery setValue:(id)kCFBooleanTrue forKey:(id)kSecMatchCaseInsensitive]; [dictForQuery setValue:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit]; [dictForQuery setValue:(id)kCFBooleanTrue forKey:(id)kSecReturnData]; OSStatus queryErr = noErr; NSData *udidValue = nil; NSString *udid = nil; queryErr = SecItemCopyMatching((CFDictionaryRef)dictForQuery, (CFTypeRef*)&udidValue); NSMutableDictionary *dict = nil; [dictForQuery setValue:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes]; queryErr = SecItemCopyMatching((CFDictionaryRef)dictForQuery, (CFTypeRef*)&dict); if (queryErr == errSecItemNotFound) { NSLog(@"KeyChain Item: %@ not found!!!", [NSString stringWithUTF8String:kKeychainUDIDItemIdentifier]); } else if (queryErr != errSecSuccess) { NSLog(@"KeyChain Item query Error!!! Error code:%d", (int)queryErr); } if (queryErr == errSecSuccess) { NSLog(@"KeyChain Item: %@", udidValue); if (udidValue) { udid = [NSString stringWithUTF8String:udidValue.bytes]; [udidValue release]; } [dict release]; } [dictForQuery release]; return udid; } + (BOOL)settUDIDToKeyChain:(NSString*)udid { NSMutableDictionary *dictForAdd = [[NSMutableDictionary alloc] init]; [dictForAdd setValue:(id)kSecClassGenericPassword forKey:(id)kSecClass]; [dictForAdd setValue:[NSString stringWithUTF8String:kKeychainUDIDItemIdentifier] forKey:kSecAttrDescription]; [dictForAdd setValue:@"UUID" forKey:(id)kSecAttrGeneric]; // Default attributes for keychain item. [dictForAdd setObject:@"" forKey:(id)kSecAttrAccount]; [dictForAdd setObject:@"" forKey:(id)kSecAttrLabel]; // The keychain access group attribute determines if this item can be shared // amongst multiple apps whose code signing entitlements contain the same keychain access group. NSString *accessGroup = [NSString stringWithFormat:@"%@.%@",[SvUDIDTools bundleSeedID],kKeyChainUDIDAccessGroup]; if (accessGroup != nil) { #if TARGET_IPHONE_SIMULATOR // Ignore the access group if running on the iPhone simulator. // // Apps that are built for the simulator aren't signed, so there's no keychain access group // for the simulator to check. This means that all apps can see all keychain items when run // on the simulator. // // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the // simulator will return -25243 (errSecNoAccessForItem). #else [dictForAdd setObject:accessGroup forKey:(id)kSecAttrAccessGroup]; #endif } const char *udidStr = [udid UTF8String]; NSData *keyChainItemValue = [NSData dataWithBytes:udidStr length:strlen(udidStr)]; [dictForAdd setValue:keyChainItemValue forKey:(id)kSecValueData]; OSStatus writeErr = noErr; if ([SvUDIDTools getUDIDFromKeyChain]) { // there is item in keychain [SvUDIDTools updateUDIDInKeyChain:udid]; [dictForAdd release]; return YES; } else { // add item to keychain writeErr = SecItemAdd((CFDictionaryRef)dictForAdd, NULL); if (writeErr != errSecSuccess) { NSLog(@"Add KeyChain Item Error!!! Error Code:%ld", writeErr); [dictForAdd release]; return NO; } else { NSLog(@"Add KeyChain Item Success!!!"); [dictForAdd release]; return YES; } } [dictForAdd release]; return NO; } + (BOOL)removeUDIDFromKeyChain { NSMutableDictionary *dictToDelete = [[NSMutableDictionary alloc] init]; [dictToDelete setValue:(id)kSecClassGenericPassword forKey:(id)kSecClass]; NSData *keyChainItemID = [NSData dataWithBytes:kKeychainUDIDItemIdentifier length:strlen(kKeychainUDIDItemIdentifier)]; [dictToDelete setValue:keyChainItemID forKey:(id)kSecAttrGeneric]; OSStatus deleteErr = noErr; deleteErr = SecItemDelete((CFDictionaryRef)dictToDelete); if (deleteErr != errSecSuccess) { NSLog(@"delete UUID from KeyChain Error!!! Error code:%ld", deleteErr); [dictToDelete release]; return NO; } else { NSLog(@"delete success!!!"); } [dictToDelete release]; return YES; } + (BOOL)updateUDIDInKeyChain:(NSString*)newUDID { NSMutableDictionary *dictForQuery = [[NSMutableDictionary alloc] init]; [dictForQuery setValue:(id)kSecClassGenericPassword forKey:(id)kSecClass]; NSData *keychainItemID = [NSData dataWithBytes:kKeychainUDIDItemIdentifier length:strlen(kKeychainUDIDItemIdentifier)]; [dictForQuery setValue:keychainItemID forKey:(id)kSecAttrGeneric]; [dictForQuery setValue:(id)kCFBooleanTrue forKey:(id)kSecMatchCaseInsensitive]; [dictForQuery setValue:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit]; [dictForQuery setValue:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes]; NSDictionary *queryResult = nil; SecItemCopyMatching((CFDictionaryRef)dictForQuery, (CFTypeRef*)&queryResult); if (queryResult) { NSMutableDictionary *dictForUpdate = [[NSMutableDictionary alloc] init]; [dictForUpdate setValue:[NSString stringWithUTF8String:kKeychainUDIDItemIdentifier] forKey:kSecAttrDescription]; [dictForUpdate setValue:keychainItemID forKey:(id)kSecAttrGeneric]; const char *udidStr = [newUDID UTF8String]; NSData *keyChainItemValue = [NSData dataWithBytes:udidStr length:strlen(udidStr)]; [dictForUpdate setValue:keyChainItemValue forKey:(id)kSecValueData]; OSStatus updateErr = noErr; // First we need the attributes from the Keychain. NSMutableDictionary *updateItem = [NSMutableDictionary dictionaryWithDictionary:queryResult]; [queryResult release]; // Second we need to add the appropriate search key/values. // set kSecClass is Very important [updateItem setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; updateErr = SecItemUpdate((CFDictionaryRef)updateItem, (CFDictionaryRef)dictForUpdate); if (updateErr != errSecSuccess) { NSLog(@"Update KeyChain Item Error!!! Error Code:%ld", updateErr); [dictForQuery release]; [dictForUpdate release]; return NO; } else { NSLog(@"Update KeyChain Item Success!!!"); [dictForQuery release]; [dictForUpdate release]; return YES; } } [dictForQuery release]; return NO; } @end