如何使用KeyChain保存和获取UDID
本文是iOS7系列文章第一篇文章,主要介绍使用KeyChain保存和获取APP数据,解决iOS7上获取不变UDID的问题。并给出一个获取UDID的工具类,使用方便,只需要替换两个地方即可。
一、iOS不用版本获取UDID的方法比较
1)iOS 5.0
iOS 2.0版本以后UIDevice提供一个获取设备唯一标识符的方法uniqueIdentifier,通过该方法我们可以获取设备的序列号,这个也是目前为止唯一可以确认唯一的标示符。好景不长,因为该唯一标识符与手机一一对应,苹果觉得可能会泄露用户隐私,所以在 iOS 5.0之后该方法就被废弃掉了。
而且苹果做的更狠,今年5月份以后提交App Store的产品都不允许再用uniqueIdentifier接口,甚至有些朋友因为代码中有UDID还被打回来,看来这条路是被封死了。
2)iOS 6.0
iOS 6.0系统新增了两个用于替换uniqueIdentifier的接口,分别是:identifierForVendor,advertisingIdentifier。
identifierForVendor接口的官方文档介绍如下:
The value of this property is the same for apps that come from the same vendor running on the same device. A different value is returned for apps on the same device that come from different vendors, and for apps on different devices regardless of vendor. The value of this property may be nil if the app is running in the background, before the user has unlocked the device the first time after the device has been restarted. If the value is nil, wait and get the value again later. The value in this property remains the same while the app (or another app from the same vendor) is installed on the iOS device. The value changes when the user deletes all of that vendor’s apps from the device and subsequently reinstalls one or more of them. Therefore, if your app stores the value of this property anywhere, you should gracefully handle situations where the identifier changes.
大概意思就是“同一开发商的APP在指定机器上都会获得同一个ID。当我们删除了某一个设备上某个开发商的所有APP之后,下次获取将会获取到不同的ID。” 也就是说我们通过该接口不能获取用来唯一标识设备的ID,问题总是难不倒聪明的程序员,于是大家想到了使用WiFi的mac地址来取代已经废弃了的uniqueIdentifier方法。具体的方法晚上有很多,大家感兴趣的可以自己找找,这儿提供一个网址: http://stackoverflow.com/questions/677530/how-can-i-programmatically-get-the-mac-address-of-an-iphone
3)iOS 7.0
iOS 7中苹果再一次无情的封杀mac地址,使用之前的方法获取到的mac地址全部都变成了02:00:00:00:00:00。有问题总的解决啊,于是四处查资料,终于有了思路是否可以使用KeyChain来保存获取到的唯一标示符呢,这样以后即使APP删了再装回来,也可以从KeyChain中读取回来。有了方向以后就开始做,看关于KeyChain的官方文档,看官方使用KeyChain的Demo,大概花了一下午时间,问题终于解决了。
二、KeyChain介绍
我们搞iOS开发,一定都知道OS X里面的KeyChain(钥匙串),通常要乡镇及调试的话,都得安装证书之类的,这些证书就是保存在KeyChain中,还有我们平时浏览网页记录的账号密码也都是记录在KeyChain中。iOS中的KeyChain相比OS X比较简单,整个系统只有一个KeyChain,每个程序都可以往KeyChain中记录数据,而且只能读取到自己程序记录在KeyChain中的数据。iOS中Security.framework框架提供了四个主要的方法来操作KeyChain:
// 查询
OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result); // 添加
OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result); // 更新KeyChain中的Item
OSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate); // 删除KeyChain中的Item
OSStatus SecItemDelete(CFDictionaryRef query)
这四个方法参数比较复杂,一旦传错就会导致操作KeyChain失败,这块儿文档中介绍的比较详细,大家可以查查官方文档Keychain Services Reference。
前面提到了每个APP只允许访问自己在KeyChain中记录的数据,那么是不是就没有别的办法访问其他APP存在KeyChain的数据了?
苹果提供了一个方法允许同一个发商的多个APP访问各APP之间的途径,即在调SecItemAdd添加数据的时候指定AccessGroup,即访问组。一个APP可以属于同事属于多个分组,添加KeyChain数据访问组需要做一下两件事情:
a、在APP target的bulibSetting里面设置Code Signing Entitlements,指向包含AceessGroup的分组信息的plist文件。该文件必须和工程文件在同一个目录下,我在添加访问分组的时候就因为plist文件位置问题,操作KeyChain失败,查找这个问题还花了好久的时间。
b、在工程目录下新建一个KeychainAccessGroups.plist文件,该文件的结构中最顶层的节点必须是一个名为“keychain-access-groups”的Array,并且该Array中每一项都是一个描述分组的NSString。对于String的格式也有相应要求,格式为:"AppIdentifier.com.***",其中APPIdentifier就是你的开发者帐号对应的ID。
c、在代码中往KeyChain中Add数据的时候,设置kSecAttrAccessGroup,代码如下:
NSString *accessGroup = [NSString stringWithUTF8String:"APPIdentifier.com.cnblogs.smileEvday"];
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
}
这段代码是从官方的Demo中直接拷贝过来的,根据注释我们可以看到,模拟器是不支持AccessGroup的,所以才行了预编译宏来选择性添加。
注:appIdentifer就是开发者帐号的那一串标识,如下图所示:
打开xcode的Organizer,选择Device选项卡,连接设备就可以看到设备上安装的开发者账号描述文件列表,其中第五列最开始的10个字符即为App Identifier,这块儿前面写的不是很清楚,好多朋友加我qq问我,今天特地补上。
三、使用KeyChain保存和获取UDID
说了这么多终于进入正题了,如何在iOS 7上面获取到不变的UDID。我们将第二部分所讲的知识直接应用进来就可以了轻松达到我们要的效果了,下面我们先看看往如何将获取到的identifierForVendor添加到KeyChain中的代码。
+ (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 stringWithUTF8String: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;
}
上面代码中,首先构建一个要添加到KeyChain中数据的Dictionary,包含一些基本的KeyChain Item的数据类型,描述,访问分组以及最重要的数据等信息,最后通过调用SecItemAdd方法将我们需要保存的UUID保存到KeyChain中。
获取KeyChain中相应数据的代码如下:
+ (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 stringWithUTF8String: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:%ld", queryErr);
}
if (queryErr == errSecSuccess) {
NSLog(@"KeyChain Item: %@", udidValue); if (udidValue) {
udid = [NSString stringWithUTF8String:udidValue.bytes];
}
} [dictForQuery release];
return udid;
}
上面代码的流程也差不多一样,首先创建一个Dictionary,其中设置一下查找条件,然后通过SecItemCopyMatching方法获取到我们之前保存到KeyChain中的数据。
四、总结
本文介绍了使用KeyChain实现APP删除后依然可以获取到相同的UDID信息的解决方法。
你可能有疑问,如果系统升级以后,是否仍然可以获取到之前记录的UDID数据?
答案是肯定的,这一点我专门做了测试。就算我们程序删除掉,系统经过升级以后再安装回来,依旧可以获取到与之前一致的UDID。但是当我们把整个系统还原以后是否还能获取到之前记录的UDID,这一点我觉得应该不行,不过手机里面数据太多,没有测试,如果大家有兴趣可以测试一下,验证一下我的猜想。
完整代码地址: https://github.com/smileEvday/SvUDID
大家如果要在真机运行时,需要替换两个地方:
第一个地方是plist文件中的accessGroup中的APPIdentifier。
第二个地方是SvUDIDTools.m中的kKeyChainUDIDAccessGroup的APPIdentity为你所使用的profile的APPIdentifier。
文章和代码中如果有什么不对的地方,欢迎指正,在这儿先谢过了。
转载地址:
http://www.cnblogs.com/smileEvday/p/UDID.html
如何使用KeyChain保存和获取UDID的更多相关文章
- 【转】如何使用KeyChain保存和获取UDID
本文是iOS7系列文章第一篇文章,主要介绍使用KeyChain保存和获取APP数据,解决iOS7上获取不变UDID的问题.并给出一个获取UDID的工具类,使用方便,只需要替换两个地方即可. 一.iOS ...
- 如何使用KeyChain保存和获取UDID - Flex/AS Programmer
原文 http://www.cnblogs.com/yssgyw/p/3364370.html 本文是iOS7系列文章第一篇文章,主要介绍使用KeyChain保存和获取APP数据,解决iOS7上获取不 ...
- 使用keychain保存用户名和密码等敏感信息 KeychainItemWrapper和SFHFKeychainUtils
iOS的keychain服务提供了一种安全的保存私密信息(密码,序列号,证书等)的方式,每个ios程序都有一个独立的keychain存储.相对于 NSUserDefaults.文件保存等一般方式,ke ...
- iOS 使用Keychain 保存 用户名和密码到 本地
iOS 使用Keychain 保存 用户名和密码到 本地 之前曾把一些简单的数据保存在了plist,文件,及NsuserDefault里面, 但是如果要保存密码之类的,保存在本地就很不安全了: 但是利 ...
- 手把手教你用蒲公英获取udid
如果需要获取udid,但是拥有手机的测试用户身边没有mac电脑和xcode环境, 今天就分享一个快捷的在线获得udid的方法 利用蒲公英网站的获取udid功能 手机浏览器访问 http://www.p ...
- 苹果ios通过描述文件获取udid
苹果ios通过描述文件获取udid 需要准备的东西 1,安装描述文件只支持https的回调地址,所以需要申请https域名 2,描述文件签名,不安装也可,只要能接受红色的字 步骤: 1,准备xml文件 ...
- 第十八篇、keychain保存UUID(保持唯一性,应用卸载,下载安装也不变)和获取IP地址
.h #import <Foundation/Foundation.h> #import <Security/Security.h> /**使用**/ //-(void) se ...
- sessionStorage的保存和获取
保存一组数组,需要转换为字符串格式: var arr = [1,2,3]; var str = JSON.stringify(arr); window.sessionStorage.setItem(' ...
- Spring MVC 保存并获取属性参数
在开发控制器的时候,有时也需要保存对应的数据到这些对象中去,或者从中获取数据.而Spring MVC给予了支持,它的主要注解有3个:@RequestAttribute.@SessionAttribut ...
随机推荐
- bzoj1095
动态点分治 先建出点分树,每个点上维护两个堆,s1,s2,分别表示子树中到点分树中父亲的所有长度,每个儿子s1的最大值,那么对于每个点答案就是s2的最大+次大,再维护一个s3保存这个. 首先我们要搞一 ...
- 计时器 vb
十分钟 我们寒假就是这么长!! vb 执行cmd : shell "cmd /c DOS命令" vb 取系统日期: Print Date; " ...
- MSSQL 调用 .net 代码
http://www.cnblogs.com/laozhao8/p/3398681.html 在SQL Server中调用.NET程序集 需求是这样的,我在.net程序里操作数据时将一些字段数据加 ...
- Git(二)--常用命令
$ git init 从当前目录初始化 $ git clone git://xxxx 从远程仓库克隆 $ git status 确定哪些文件当前处于什么状态 $ git add 开始跟踪一个新文件 ...
- Flutter实战视频-移动电商-15.首页_商品推荐模块编写
15.首页_商品推荐模块编写 商品推荐,我们做成可以横向滚动的 分析: 上面是标题,下面是ListView,里面是一个Column, column分三层,第一是图片,第二是价格,第三是市场价格 小细节 ...
- XML DOM 笔记
一.概念理解: 关于Element跟Node的区别,cilldren跟childNodes的区别很多朋友弄不清楚,本文试图让大家明白这几个概念之间的区别. Node(节点)是DOM层次结构中的任何类型 ...
- 使用命令把类打成jar包
测试用类 public class Hello { public static void main(String[] args) { System.out.println("hello wo ...
- HDU - 1715 - 大菲波数 - JAVA
http://acm.hdu.edu.cn/showproblem.php?pid=1715 import java.io.*; import java.util.*; import java.mat ...
- luoguP4242树上的毒瘤
传送门 模板集合吧,除了码农,没啥难的... 和bzoj2243:[SDOI2011]染色十分相像,但是多了点集和查询的区别 然后点集显然可以看出是虚树问题,查询可以用点分治\(O(nlogn)\), ...
- C - Catch That Cow POJ - 3278
//标准bfs #include <iostream> #include <cstdio> #include <algorithm> #include <cm ...