ios设备唯一标识获取策略
2014-08-12 17:16 PingKang 阅读(225) 评论(0) 编辑 收藏 举报-
英文原文:In iOS 7 and later, if you ask for the MAC address of an iOS device, the system returns the value 02:00:00:00:00:00. If you need to identify the device, use the identifierForVendor property of UIDevice instead. (Apps that need an identifier for their own advertising purposes should consider using the advertisingIdentifier property of ASIdentifierManager instead.)
翻译:从iOS7及更高版本往后,如果你向ios设备请求获取mac地址,系统将返回一个固定值“02:00:00:00:00:00”,如果你需要识别设备的 唯一性,请使用UIDevice的identifierForVendor属性。(因广告目的而需要识别设备的应用,请考虑使用 ASIdentifierManager的advertisingIdentifier属性作为替代)
这个MAC地址是指什么?有什么用?
MAC(Medium/Media Access Control)地址,用来表示互联网上每一个站点的标识符,采用十六进制数表示,共六个字节(48位)。其中,前三个字节是由IEEE的注册管理机构 RA负责给不同厂家分配的代码(高位24位),也称为“编制上唯一的标识符” (Organizationally Unique Identifier),后三个字节(低位24位)由各厂家自行指派给生产的适配器接口,称为扩展标识符(唯一性)。
MAC地址在网络上用来区分设备的唯一性,接入网络的设备都有一个MAC地址,他们肯定都是不同的,是唯一的。一部iPhone上可能有多个MAC地址,包括WIFI的、SIM的等,但是iTouch和iPad上就有一个WIFI的,因此只需获取WIFI的MAC地址就好了,也就是en0的地址。
形象的说,MAC地址就如同我们身份证上的身份证号码,具有全球唯一性。这样就可以非常好的标识设备唯一性,类似与苹果设备的UDID号,通常的用途有:1)用于一些统计与分析目的,利用用户的操作习惯和数据更好的规划产品;2)作为用户ID来唯一识别用户,可以用游客身份使用app又能在服务器端保存相应的信息,省去用户名、密码等注册过程。
那么,如何使用Mac地址生成设备的唯一标识呢?主要分三种:
1、直接使用“MAC Address”
2、使用“MD5(MAC Address)”
3、使用“MD5(Mac Address+bundle_id)”获得“机器+应用”的唯一标识(bundle_id 是应用的唯一标识)
iOS7之前,因为Mac地址是唯一的, 一般app开发者会采取第3种方式来识别安装对应app的设备。为什么会使用它?在iOS5之前,都是使用UDID的,后来被禁用。苹果推荐使用UUID 但是也有诸多问题,从而使用MAC地址。而MAC地址跟UDID一样,存在隐私问题,现在苹果新发布的iOS7上,如果请求Mac地址都会返回一个固定 值,那么Mac Address+bundle_id这个值大家的设备都变成一致的啦,跟UDID一样相当于被禁用。那么,要怎么标识设备唯一呢?
在iOS系统中,获取设备唯一标识的方法有很多:
一.UDID(Unique Device Identifier)
二.UUID(Universally Unique Identifier)
三.MAC Address
四.OPEN UDID
五.广告标示符(IDFA-identifierForIdentifier)
六.Vindor标示符 (IDFV-identifierForVendor)
七.推送token+bundle_idUDID的全称是Unique Device Identifier,它就是苹果IOS设备的唯一识别码,它由40个字符的字母和数字组成(越狱的设备通过某些工具可以改变设备的UDID)。移动网络可利用UDID来识别移动设备,但是,从IOS5.0(2011年8月份)开始,苹果宣布将不再支持用uniqueIdentifier方法获取设备的UDID,iOS5以下是可以用的。在2013年3月21日苹果已经通知开发者:从2013年5月1日起,访问UIDIDs的程序将不再被审核通过,替代的方案是开发者应该使用“在iOS 6中介绍的Vendor或Advertising标示符”。所以UDID是绝对不能用啦。
OPEN UDID,没有用到MAC地址,同时能保证同一台设备上的不同应用使用同一个OpenUDID,只要用户设备上有一个使用了OpenUDID的应用存在时,其他后续安装的应用如果获取OpenUDID,都将会获得第一个应用生成的那个。但是根据贡献者的代码和方法,和一些开发者的经验,如果把使用了OpenUDID方案的应用全部都删除,再重新获取OpenUDID,此时的OpenUDID就跟以前的不一样。可见,这种方法还是不保险。
广告标示符,是iOS 6中另外一个新的方法,提供了一个方法advertisingIdentifier,通过调用该方法会返回一个NSUUID实例,最后可以获得一个UUID,由系统存储着的。不过即使这是由系统存储的,但是有几种情况下,会重新生成广告标示符。如果用户完全重置系统((设置程序 -> 通用 -> 还原 -> 还原位置与隐私) ,这个广告标示符会重新生成。另外如果用户明确的还原广告(设置程序-> 通用 -> 关于本机 -> 广告 -> 还原广告标示符) ,那么广告标示符也会重新生成。关于广告标示符的还原,有一点需要注意:如果程序在后台运行,此时用户“还原广告标示符”,然后再回到程序中,此时获取广 告标示符并不会立即获得还原后的标示符。必须要终止程序,然后再重新启动程序,才能获得还原后的广告标示符。
Vindor标示符,也是在iOS 6中新增的,跟advertisingIdentifier一样,该方法返回的是一个 NSUUID对象,可以获得一个UUID。如果满足条件“相同的一个程序里面-相同的vindor-相同的设备”,那么获取到的这个属性值就不会变。如果是“相同的程序-相同的设备-不同的vindor,或者是相同的程序-不同的设备-无论是否相同的vindor”这样的情况,那么这个值是不会相同的。
推送token+bundle_id的方法:
1、应用中增加推送用来获取token
2、获取应用bundle_id
3、根据token+bundle_id进行散列运算
apple push token保证设备唯一,但必须有网络情况下才能工作,该方法不依赖于设备本身,但依赖于apple push,而苹果push有时候会抽风的。
UUID是Universally Unique Identifier的缩写,中文意思是通用唯一识别码。它是让分布式系统中的所有元素,都能有唯一的辨识资讯,而不需要透过中央控制端来做辨识资讯的指定。这样,每个人都可以建立不与其它人冲突的 UUID。在此情况下,就不需考虑数据库建立时的名称重复问题。苹果公司建议使用UUID为应用生成唯一标识字符串。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127iOS中获取UUID的代码如下:
-(NSString*)
uuid
{
CFUUIDRef puuid = CFUUIDCreate( nil );
CFStringRef uuidString = CFUUIDCreateString( nil, puuid );
NSString * result = (NSString *)CFStringCreateCopy( NULL, uuidString);
CFRelease(puuid);
CFRelease(uuidString);
return
[result autorelease];
}
iOS中获取网卡mac的代码如下:
#include <sys/socket.h> // Per msqr
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_dl.h>
#pragma mark MAC addy
// Return the local MAC addy
// Courtesy of FreeBSD hackers email list
// Accidentally munged during previous update. Fixed thanks to mlamb.
- (NSString *) macaddress
{
int
mib[6];
size_t
len;
char
*buf;
unsigned
char
*ptr;
struct
if_msghdr *ifm;
struct
sockaddr_dl *sdl;
mib[0] = CTL_NET;
mib[1] = AF_ROUTE;
mib[2] = 0;
mib[3] = AF_LINK;
mib[4] = NET_RT_IFLIST;
if
((mib[5] = if_nametoindex(
"en0"
)) == 0) {
printf
(
"Error: if_nametoindex error/n"
);
return
NULL;
}
if
(sysctl(mib, 6, NULL, &len, NULL, 0) < 0) {
printf
(
"Error: sysctl, take 1/n"
);
return
NULL;
}
if
((buf =
malloc
(len)) == NULL) {
printf
(
"Could not allocate memory. error!/n"
);
return
NULL;
}
if
(sysctl(mib, 6, buf, &len, NULL, 0) < 0) {
printf
(
"Error: sysctl, take 2"
);
return
NULL;
}
ifm = (
struct
if_msghdr *)buf;
sdl = (
struct
sockaddr_dl *)(ifm + 1);
ptr = (unsigned
char
*)LLADDR(sdl);
// NSString *outstring = [NSString stringWithFormat:@"%02x:%02x:%02x:%02x:%02x:%02x", *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5)];
NSString *outstring = [NSString stringWithFormat:@
"%02x%02x%02x%02x%02x%02x"
, *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5)];
free
(buf);
return
[outstring uppercaseString];
}
iOS中获取UUID的代码如下:
-(NSString*)
uuid
{
CFUUIDRef puuid = CFUUIDCreate( nil );
CFStringRef uuidString = CFUUIDCreateString( nil, puuid );
NSString * result = (NSString *)CFStringCreateCopy( NULL, uuidString);
CFRelease(puuid);
CFRelease(uuidString);
return
[result autorelease];
}
iOS中获取网卡mac的代码如下:
#include <sys/socket.h> // Per msqr
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_dl.h>
#pragma mark MAC addy
// Return the local MAC addy
// Courtesy of FreeBSD hackers email list
// Accidentally munged during previous update. Fixed thanks to mlamb.
- (NSString *) macaddress
{
int
mib[6];
size_t
len;
char
*buf;
unsigned
char
*ptr;
struct
if_msghdr *ifm;
struct
sockaddr_dl *sdl;
mib[0] = CTL_NET;
mib[1] = AF_ROUTE;
mib[2] = 0;
mib[3] = AF_LINK;
mib[4] = NET_RT_IFLIST;
if
((mib[5] = if_nametoindex(
"en0"
)) == 0) {
printf
(
"Error: if_nametoindex error/n"
);
return
NULL;
}
if
(sysctl(mib, 6, NULL, &len, NULL, 0) < 0) {
printf
(
"Error: sysctl, take 1/n"
);
return
NULL;
}
if
((buf =
malloc
(len)) == NULL) {
printf
(
"Could not allocate memory. error!/n"
);
return
NULL;
}
if
(sysctl(mib, 6, buf, &len, NULL, 0) < 0) {
printf
(
"Error: sysctl, take 2"
);
return
NULL;
}
ifm = (
struct
if_msghdr *)buf;
sdl = (
struct
sockaddr_dl *)(ifm + 1);
ptr = (unsigned
char
*)LLADDR(sdl);
// NSString *outstring = [NSString stringWithFormat:@"%02x:%02x:%02x:%02x:%02x:%02x", *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5)];
NSString *outstring = [NSString stringWithFormat:@
"%02x%02x%02x%02x%02x%02x"
, *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5)];
free
(buf);
return
[outstring uppercaseString];
}
开发者可以在应用第一次启动时调用一 次,然后将该串存储起来,以便以后替代UDID来使用。但是,如果用户删除该应用再次安装时,又会生成新的字符串,所以不能保证唯一识别该设备。这就需要各路高手想出各种解决方案。所以,之前很多应用就采用MAC Address。但是现在如果用户升级到iOS7(及其以后的苹果系统)后,他们机子的MAC Address就是一样的,没办法做区分,只能弃用此方法,重新使用UUID来标识。如果使用UUID,就要考虑应用被删除后再重新安装时的处理。
一个解决的办法是:UUID一般只生成一次,保存在iOS系统里面,如果应用删除了,重装应用之后它的UUID还是一样的,除非系统重置 。但是不能保证在以后的系统升级后还能用(如果系统保存了该信息就能用).
由于IOS系统存储的数据都是在sandBox里面,一旦删除App,sandBox也不复存在。好在有一个例外,那就是keychain(钥匙串)。
通常情况下,IOS系统用NSUserDefaults存储数据信息,但是对于一些私密信息,比如密码、证书等等,就需要使用更为安全的keychain了。
keychain里保存的信息不会因App被删除而丢失。所以,可以利用这个keychain这个特点来保存设备唯一标识。
那么,如何在应用里使用使用keyChain呢,我们需要导入Security.framework ,keychain的操作接口声明在头文件SecItem.h里。
直接使用SecItem.h里方法操作keychain,需要写的代码较为复杂,我们可以使用已经封装好了的工具类KeychainItemWrapper来对keychain进行操作。
KeychainItemWrapper是apple官方例子“GenericKeychain”里一个访问keychain常用操作的封装类,在官网上下载了GenericKeychain项目后,
只需要把“KeychainItemWrapper.h”和“KeychainItemWrapper.m”拷贝到我们项目,并导入Security.framework 。KeychainItemWrapper的用法:
/** 初始化一个保存用户帐号的KeychainItemWrapper */
KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"Account Number"
accessGroup:@"YOUR_APP_ID_HERE.com.yourcompany.AppIdentifier"];
//保存数据
[wrapper setObject:@"<帐号>" forKey:(id)kSecAttrAccount];
[wrapper setObject:@"<帐号密码>" forKey:(id)kSecValueData];
//从keychain里取出帐号密码
NSString *password = [wrapper objectForKey:(id)kSecValueData];
//清空设置
[wrapper resetKeychainItem];
其中方法“- (void)setObject:(id)inObject forKey:(id)key;”里参数“forKey”的值应该是Security.framework 里头文件“SecItem.h”里定义好的key,用其他字符串做key程序会出错!