iOS获取设备唯一标识的实现步骤
作者:余默
1. 常用的UUID
UDID是一个40位十六进制序列(越狱的设备通过某些工具可以改变设备的 UDID),移动网络可以利用 UDID 来识别移动设备。
许多开发者把 UDID 跟用户的个人信息关联起来,网络窥探者会从多个应用收集这些数据,然后顺藤摸瓜得到这个人的许多隐私数据,同时大部分应用确实在频繁传输 UDID 和私人信息。 为了避免集体诉讼,苹果最终决定在 iOS 5 的时候,将这一惯例废除。
获取UUID的方法:
/** 卸载应用重新安装后会不一致*/ + (NSString *)getUUID{ return [UIDevice currentDevice].identifierForVendor.UUIDString;; }
2. MAC 地址
MAC地址,用来表示互联网上每一个站点的标示符,是一个六个字节(48位)的十六进制序列。前三个字节是由 IEEE 的注册管理机构 RA 负责给不同厂家分配的”编制上唯一的标示符,后三个字节由各厂家自行指派给生产的适配器接口。
MAC 地址在网络上用来区分设备的唯一性,接入网络的设备都有一个MAC地址,他们肯定都是唯一的。一部 iPhone 上可能有多个 MAC 地址,包括 WIFI 的、SIM 的等,但是 iTouch 和 iPad 上就有一个 WIFI 的,因此只需获取 WIFI 的 MAC 地址就好了。一般会采取 MD5(MAC 地址 + bundleID)获取唯一标识。
但是 MAC 地址和 UDID 一样,存在隐私问题, iOS 7 之后,所有设备请求 MAC 地址会返回一个固定值,这个方法也不攻自破了。
获取MAC在github找到一个挺好的方法:
2.1 首先导入下面几个库:
2.2 新建一个文件,继承NSObject,在.m文件导入头文件,以及定义一些宏
#import "XWGetMAC.h" #import <ifaddrs.h> #import <resolv.h> #import <arpa/inet.h> #import <net/if.h> #import <netdb.h> #import <netinet/ip.h> #import <net/ethernet.h> #import <net/if_dl.h> #define MDNS_PORT 5353 #define QUERY_NAME "_apple-mobdev2._tcp.local" #define DUMMY_MAC_ADDR @"02:00:00:00:00:00" #define IOS_CELLULAR @"pdp_ip0" #define IOS_WIFI @"en0" #define IOS_VPN @"utun0" #define IP_ADDR_IPv4 @"ipv4" #define IP_ADDR_IPv6 @"ipv6"
+ (NSString *)getMAC:(BOOL)preferIPv4 { return [[XWGetMAC alloc] getIPAddress:preferIPv4]; } /* * 获取设备当前网络IP地址 */ - (NSString *)getIPAddress:(BOOL)preferIPv4 { NSArray *searchArray = preferIPv4 ? @[ IOS_VPN @"/" IP_ADDR_IPv4, IOS_VPN @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6 ] : @[ IOS_VPN @"/" IP_ADDR_IPv6, IOS_VPN @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4 ] ; NSDictionary *addresses = [self getIPAddr]; __block NSString *address; [searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL * _Nonnull stop) { address = addresses[key]; //筛选出IP地址格式 if([self isValidatIP:address]) *stop = YES; }]; return address ? address : @"0.0.0.0"; } - (BOOL)isValidatIP:(NSString *)ipAddress { if (ipAddress.length == 0) { return NO; } NSString *urlRegEx = @"^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"; NSError *error; NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:urlRegEx options:0 error:&error]; if (regex != nil) { NSTextCheckingResult *firstMatch=[regex firstMatchInString:ipAddress options:0 range:NSMakeRange(0, [ipAddress length])]; return firstMatch; } return NO; } - (NSDictionary *)getIPAddr { NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8]; // retrieve the current interfaces - returns 0 on success struct ifaddrs *interfaces; if(!getifaddrs(&interfaces)) { // Loop through linked list of interfaces struct ifaddrs *interface; for(interface=interfaces; interface; interface=interface->ifa_next) { if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) { continue; // deeply nested code harder to read } const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr; char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ]; if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) { NSString *name = [NSString stringWithUTF8String:interface->ifa_name]; NSString *type; if(addr->sin_family == AF_INET) { if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) { type = IP_ADDR_IPv4; } } else { const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr; if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) { type = IP_ADDR_IPv6; } } if(type) { NSString *key = [NSString stringWithFormat:@"%@/%@", name, type]; addresses[key] = [NSString stringWithUTF8String:addrBuf]; } } } // Free memory freeifaddrs(interfaces); } return [addresses count] ? addresses : nil; }
/* * 获取设备物理地址 */ - (nullable NSString *)getMacAddress { res_9_init(); int len; //get currnet ip address NSString *ip = [self currentIPAddressOf:IOS_WIFI]; if(ip == nil) { fprintf(stderr, "could not get current IP address of en0\n"); return DUMMY_MAC_ADDR; }//end if //set port and destination _res.nsaddr_list[0].sin_family = AF_INET; _res.nsaddr_list[0].sin_port = htons(MDNS_PORT); _res.nsaddr_list[0].sin_addr.s_addr = [self IPv4Pton:ip]; _res.nscount = 1; unsigned char response[NS_PACKETSZ]; //send mdns query if((len = res_9_query(QUERY_NAME, ns_c_in, ns_t_ptr, response, sizeof(response))) < 0) { fprintf(stderr, "res_search(): %s\n", hstrerror(h_errno)); return DUMMY_MAC_ADDR; }//end if //parse mdns message ns_msg handle; if(ns_initparse(response, len, &handle) < 0) { fprintf(stderr, "ns_initparse(): %s\n", hstrerror(h_errno)); return DUMMY_MAC_ADDR; }//end if //get answer length len = ns_msg_count(handle, ns_s_an); if(len < 0) { fprintf(stderr, "ns_msg_count return zero\n"); return DUMMY_MAC_ADDR; }//end if //try to get mac address from data NSString *macAddress = nil; for(int i = 0 ; i < len ; i++) { ns_rr rr; ns_parserr(&handle, ns_s_an, 0, &rr); if(ns_rr_class(rr) == ns_c_in && ns_rr_type(rr) == ns_t_ptr && !strcmp(ns_rr_name(rr), QUERY_NAME)) { char *ptr = (char *)(ns_rr_rdata(rr) + 1); int l = (int)strcspn(ptr, "@"); char *tmp = calloc(l + 1, sizeof(char)); if(!tmp) { perror("calloc()"); continue; }//end if memcpy(tmp, ptr, l); macAddress = [NSString stringWithUTF8String:tmp]; free(tmp); }//end if }//end for each macAddress = macAddress ? macAddress : DUMMY_MAC_ADDR; return macAddress; }//end getMacAddressFromMDNS - (nonnull NSString *)currentIPAddressOf: (nonnull NSString *)device { struct ifaddrs *addrs; NSString *ipAddress = nil; if(getifaddrs(&addrs) != 0) { return nil; }//end if //get ipv4 address for(struct ifaddrs *addr = addrs ; addr ; addr = addr->ifa_next) { if(!strcmp(addr->ifa_name, [device UTF8String])) { if(addr->ifa_addr) { struct sockaddr_in *in_addr = (struct sockaddr_in *)addr->ifa_addr; if(in_addr->sin_family == AF_INET) { ipAddress = [self IPv4Ntop:in_addr->sin_addr.s_addr]; break; }//end if }//end if }//end if }//end for freeifaddrs(addrs); return ipAddress; }//end currentIPAddressOf: - (nullable NSString *)IPv4Ntop: (in_addr_t)addr { char buffer[INET_ADDRSTRLEN] = {0}; return inet_ntop(AF_INET, &addr, buffer, sizeof(buffer)) ? [NSString stringWithUTF8String:buffer] : nil; }//end IPv4Ntop: - (in_addr_t)IPv4Pton: (nonnull NSString *)IPAddr { in_addr_t network = INADDR_NONE; return inet_pton(AF_INET, [IPAddr UTF8String], &network) == 1 ? network : INADDR_NONE; }//end IPv4Pton:
如果出现 “_res_9_ninit", referenced from:”这种报错,是因为没有添加步骤1的几个库
3.UUID+自己存储
3.1 获取UUID的两个方法
/** 卸载应用重新安装后会不一致*/ + (NSString *)getUUID{ CFUUIDRef uuid = CFUUIDCreate(NULL); NSString *UUID = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, uuid); CFRelease(uuid); return UUID; } /** 卸载应用重新安装后会不一致*/ + (NSString *)getUUID{ return [UIDevice currentDevice].identifierForVendor.UUIDString;; }
很明显UUID已经不足以支持设备的唯一性了,目前很多App都有新用户的优惠,但是又要保证每台设备绑定一个账户,如果单纯使用UUID的话已经满足不了这个需求,所以,这里需要用keychain保存,这样即使卸载app在安装,获取到的UUID也是唯一性的。
3.2 首先在项目中添加 KeyChain Sharing
3.3 导入第三方库 Security.framework
3.4 核心代码(代码有点多)
在github搜索SSKeychain可以找到,只要 SSKeychain.h 和 SSKeychain.m 文件即可
#import <Foundation/Foundation.h> #import <Security/Security.h> /** Error codes that can be returned in NSError objects. */ typedef enum { /** No error. */ SSKeychainErrorNone = noErr, /** Some of the arguments were invalid. */ SSKeychainErrorBadArguments = -1001, /** There was no password. */ SSKeychainErrorNoPassword = -1002, /** One or more parameters passed internally were not valid. */ SSKeychainErrorInvalidParameter = errSecParam, /** Failed to allocate memory. */ SSKeychainErrorFailedToAllocated = errSecAllocate, /** No trust results are available. */ SSKeychainErrorNotAvailable = errSecNotAvailable, /** Authorization/Authentication failed. */ SSKeychainErrorAuthorizationFailed = errSecAuthFailed, /** The item already exists. */ SSKeychainErrorDuplicatedItem = errSecDuplicateItem, /** The item cannot be found.*/ SSKeychainErrorNotFound = errSecItemNotFound, /** Interaction with the Security Server is not allowed. */ SSKeychainErrorInteractionNotAllowed = errSecInteractionNotAllowed, /** Unable to decode the provided data. */ SSKeychainErrorFailedToDecode = errSecDecode } SSKeychainErrorCode; extern NSString *const kSSKeychainErrorDomain; /** Account name. */ extern NSString *const kSSKeychainAccountKey; /** Time the item was created. The value will be a string. */ extern NSString *const kSSKeychainCreatedAtKey; /** Item class. */ extern NSString *const kSSKeychainClassKey; /** Item description. */ extern NSString *const kSSKeychainDescriptionKey; /** Item label. */ extern NSString *const kSSKeychainLabelKey; /** Time the item was last modified. The value will be a string. */ extern NSString *const kSSKeychainLastModifiedKey; /** Where the item was created. */ extern NSString *const kSSKeychainWhereKey; /** Simple wrapper for accessing accounts, getting passwords, setting passwords, and deleting passwords using the system Keychain on Mac OS X and iOS. This was originally inspired by EMKeychain and SDKeychain (both of which are now gone). Thanks to the authors. SSKeychain has since switched to a simpler implementation that was abstracted from [SSToolkit](http://sstoolk.it). */ @interface SSKeychain : NSObject ///----------------------- /// @name Getting Accounts ///----------------------- /** Returns an array containing the Keychain's accounts, or `nil` if the Keychain has no accounts. See the `NSString` constants declared in SSKeychain.h for a list of keys that can be used when accessing the dictionaries returned by this method. @return An array of dictionaries containing the Keychain's accounts, or `nil` if the Keychain doesn't have any accounts. The order of the objects in the array isn't defined. @see allAccounts: */ + (NSArray *)allAccounts; /** Returns an array containing the Keychain's accounts, or `nil` if the Keychain doesn't have any accounts. See the `NSString` constants declared in SSKeychain.h for a list of keys that can be used when accessing the dictionaries returned by this method. @param error If accessing the accounts fails, upon return contains an error that describes the problem. @return An array of dictionaries containing the Keychain's accounts, or `nil` if the Keychain doesn't have any accounts. The order of the objects in the array isn't defined. @see allAccounts */ + (NSArray *)allAccounts:(NSError **)error; /** Returns an array containing the Keychain's accounts for a given service, or `nil` if the Keychain doesn't have any accounts for the given service. See the `NSString` constants declared in SSKeychain.h for a list of keys that can be used when accessing the dictionaries returned by this method. @param serviceName The service for which to return the corresponding accounts. @return An array of dictionaries containing the Keychain's accountsfor a given `serviceName`, or `nil` if the Keychain doesn't have any accounts for the given `serviceName`. The order of the objects in the array isn't defined. @see accountsForService:error: */ + (NSArray *)accountsForService:(NSString *)serviceName; /** Returns an array containing the Keychain's accounts for a given service, or `nil` if the Keychain doesn't have any accounts for the given service. @param serviceName The service for which to return the corresponding accounts. @param error If accessing the accounts fails, upon return contains an error that describes the problem. @return An array of dictionaries containing the Keychain's accountsfor a given `serviceName`, or `nil` if the Keychain doesn't have any accounts for the given `serviceName`. The order of the objects in the array isn't defined. @see accountsForService: */ + (NSArray *)accountsForService:(NSString *)serviceName error:(NSError **)error; ///------------------------ /// @name Getting Passwords ///------------------------ /** Returns a string containing the password for a given account and service, or `nil` if the Keychain doesn't have a password for the given parameters. @param serviceName The service for which to return the corresponding password. @param account The account for which to return the corresponding password. @return Returns a string containing the password for a given account and service, or `nil` if the Keychain doesn't have a password for the given parameters. @see passwordForService:account:error: */ + (NSString *)passwordForService:(NSString *)serviceName account:(NSString *)account; /** Returns a string containing the password for a given account and service, or `nil` if the Keychain doesn't have a password for the given parameters. @param serviceName The service for which to return the corresponding password. @param account The account for which to return the corresponding password. @param error If accessing the password fails, upon return contains an error that describes the problem. @return Returns a string containing the password for a given account and service, or `nil` if the Keychain doesn't have a password for the given parameters. @see passwordForService:account: */ + (NSString *)passwordForService:(NSString *)serviceName account:(NSString *)account error:(NSError **)error; /** Returns the password data for a given account and service, or `nil` if the Keychain doesn't have data for the given parameters. @param serviceName The service for which to return the corresponding password. @param account The account for which to return the corresponding password. @param error If accessing the password fails, upon return contains an error that describes the problem. @return Returns a the password data for the given account and service, or `nil` if the Keychain doesn't have data for the given parameters. @see passwordDataForService:account:error: */ + (NSData *)passwordDataForService:(NSString *)serviceName account:(NSString *)account; /** Returns the password data for a given account and service, or `nil` if the Keychain doesn't have data for the given parameters. @param serviceName The service for which to return the corresponding password. @param account The account for which to return the corresponding password. @param error If accessing the password fails, upon return contains an error that describes the problem. @return Returns a the password data for the given account and service, or `nil` if the Keychain doesn't have a password for the given parameters. @see passwordDataForService:account: */ + (NSData *)passwordDataForService:(NSString *)serviceName account:(NSString *)account error:(NSError **)error; ///------------------------- /// @name Deleting Passwords ///------------------------- /** Deletes a password from the Keychain. @param serviceName The service for which to delete the corresponding password. @param account The account for which to delete the corresponding password. @return Returns `YES` on success, or `NO` on failure. @see deletePasswordForService:account:error: */ + (BOOL)deletePasswordForService:(NSString *)serviceName account:(NSString *)account; /** Deletes a password from the Keychain. @param serviceName The service for which to delete the corresponding password. @param account The account for which to delete the corresponding password. @param error If deleting the password fails, upon return contains an error that describes the problem. @return Returns `YES` on success, or `NO` on failure. @see deletePasswordForService:account: */ + (BOOL)deletePasswordForService:(NSString *)serviceName account:(NSString *)account error:(NSError **)error; ///------------------------ /// @name Setting Passwords ///------------------------ /** Sets a password in the Keychain. @param password The password to store in the Keychain. @param serviceName The service for which to set the corresponding password. @param account The account for which to set the corresponding password. @return Returns `YES` on success, or `NO` on failure. @see setPassword:forService:account:error: */ + (BOOL)setPassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account; /** Sets a password in the Keychain. @param password The password to store in the Keychain. @param serviceName The service for which to set the corresponding password. @param account The account for which to set the corresponding password. @param error If setting the password fails, upon return contains an error that describes the problem. @return Returns `YES` on success, or `NO` on failure. @see setPassword:forService:account: */ + (BOOL)setPassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account error:(NSError **)error; /** Sets arbirary data in the Keychain. @param password The data to store in the Keychain. @param serviceName The service for which to set the corresponding password. @param account The account for which to set the corresponding password. @param error If setting the password fails, upon return contains an error that describes the problem. @return Returns `YES` on success, or `NO` on failure. @see setPasswordData:forService:account:error: */ + (BOOL)setPasswordData:(NSData *)password forService:(NSString *)serviceName account:(NSString *)account; /** Sets arbirary data in the Keychain. @param password The data to store in the Keychain. @param serviceName The service for which to set the corresponding password. @param account The account for which to set the corresponding password. @param error If setting the password fails, upon return contains an error that describes the problem. @return Returns `YES` on success, or `NO` on failure. @see setPasswordData:forService:account: */ + (BOOL)setPasswordData:(NSData *)password forService:(NSString *)serviceName account:(NSString *)account error:(NSError **)error; ///-------------------- /// @name Configuration ///-------------------- #if __IPHONE_4_0 && TARGET_OS_IPHONE /** Returns the accessibility type for all future passwords saved to the Keychain. @return Returns the accessibility type. The return value will be `NULL` or one of the "Keychain Item Accessibility Constants" used for determining when a keychain item should be readable. @see accessibilityType */ + (CFTypeRef)accessibilityType; /** Sets the accessibility type for all future passwords saved to the Keychain. @param accessibilityType One of the "Keychain Item Accessibility Constants" used for determining when a keychain item should be readable. If the value is `NULL` (the default), the Keychain default will be used. @see accessibilityType */ + (void)setAccessibilityType:(CFTypeRef)accessibilityType; #endif @end
3.4 创建新类,引用 SSKeychain 封装
#import "GetKeychain.h" #import "SSKeychain.h" @implementation GetKeychain + (NSString *)getDeviceUUID { NSString *currentDeviceUUIDStr = [SSKeychain passwordForService:@"项目boudle id" account:@"uuid"]; if (currentDeviceUUIDStr == nil || [currentDeviceUUIDStr isEqualToString:@""]) { NSUUID *currentDeviceUUID = [UIDevice currentDevice].identifierForVendor; currentDeviceUUIDStr = currentDeviceUUID.UUIDString; currentDeviceUUIDStr = [currentDeviceUUIDStr stringByReplacingOccurrencesOfString:@"-" withString:@""]; currentDeviceUUIDStr = [currentDeviceUUIDStr lowercaseString]; [SSKeychain setPassword: currentDeviceUUIDStr forService:@"项目boudle id" account:@"uuid"]; } return currentDeviceUUIDStr; } @end
以上就是iOS获取设备唯一标识的实现步骤的详细内容,更多关于iOS获取设备唯一标识的资料请关注脚本之家其它相关文章!