背景

在不需要用户登录或者没有用户概念的移动互联网产品中(如百度,今日头条,浏览器等),设计一种稳定的能够唯一标识客户设备的id(CUID),相当于移动互联网的用户cookieid(匿名用户id),使得客户端自身以及和服务端交互时所产生的每一条行为日志的都能够找到对应到相应的设备标识,方便追踪和分析用户。

理论上每台终端机器都有一个唯一的设备号,如Android的IMEI,IOS的IDFA/IDFV。但是实际使用中会遇到各种各样的问题:

Android平台: 因为各种“中国特色”场景,并引发各种终端属性不靠谱的问题

  • 山寨机: 一批或多批出货的手机终端属性可能都相同,IMEI重复无法区分; 山寨机比例5%+;
  • 双卡机: 获取到的终端属性混乱,APP获取卡槽优先级可能不同,有的获取到空卡槽位SIM信息,有的可能第一次取到卡槽1信息,第二次取到卡槽2信息;
  • 渠道刷机: 会修改同一终端的各种属性来达到虚假刷量目的,甚至使用终端模拟器生成假的IMEI,MAC等信息;
  • 刷ROM: 更新ROM后终端属性可能发生改变,如: IMEI,机型都被替换成ROM中的信息,MAC被重新激活篡改;
  • 人为修改: 存在各种可能性的修改,如: 为了归避部分产品对同一个终端上重复注册的校验而修改IMEI, 无法WIFI上网而修改MAC等, 换卡改变SIM卡信息等;
  • 终端属性获取异常: 如卡槽无SIM卡, 根据业务数据分析结果IMSI能成功上报比例约80%; WIFI未启用时无法获取MAC,成功获取上报的比例小于70%; IMEI也有约2%~5%的比例上报不了。

抽象归纳Android终端身份标识的两类问题: 理论上使用IMEI可区分终端用户,但实际存在“不同终端IMEI重复”(典型场景: 山寨机) 和“同一终端IMEI被修改”(典型场景:双卡机/刷机/刷ROM) 两类情况。抽像归纳为”山寨机“和”刷机“两类问题。

iOS平台: 终端用户唯一标识更因苹果公司的策略限制变得不可靠, 业界开源解决方案在用户标识的持久性上也不够给力

  • Udid: iOS 6已被禁用;
  • MAC: iOS 7中封杀MAC地址,之前的方法获取到的MAC地址全部都变成了02:00:00:00:00:00;
  • IDFA-identifierForIdentifier: 仅在 >= iOS6 的版本中有效;且用户可随时关闭;
  • IDFV-identifierForVendor: 仅在 >= iOS6 的版本中有效;在同一个开发者账户下才具唯一性,重装或升级系统后都会重新产生;
  • 产品自己的GUID: 除了私有性外, 重点会因重装程序、重装系统、升级系统而丢失,服务器会重新生成导致用户虚高;
  • 开源OpenUdid: 在重装、升级系统后会下发新的ID,导致用户虚高。如升级 iOS7 的用户将获取到一个新的OpenUdid,这部分升级用户就会以新增用户计入,导致数据虚高。

问题抽象:苹果设备ID禁用风险。

解决方案

这个问题的难点其实不在于唯一性,而在于稳定性。随机构建一个唯一的设备/用户id,其实是一个非常简单的事情,只要把随机因子(如机器ip,线程id,时间戳)进行一个简单的运算(如md5),就可以得到一个唯一的id了。但是关键是要像身份证号码一样,要保证这个设备/用户一直都是使用这个id,在客户端版本的迁移过程中,必须保留不变。否则当用户进行版本升级后将会出现数据丢失现象,更好的情况就算是用户卸载安装了不同的版本,后台也能把这个用户的客户端的信息关联到找这个用户关联的id。

那么怎么解决这个问题呢?

有两种思路。

一种思路是每次都生成一样的id。只要输入是稳定的,算法也是固定的,那么生成的id就肯定是固定的。比如 md5(imei) 就是一种做法。

这个方法本来应该是完美的解决方案,但是正如前面分析的,存在”山寨机“和”刷机“以及获取不到设备id的问题。所以是一个可以解决大部分的简单快速的解决方案。

第二种思路是随机生成唯一id,但是尽量保证同一个设备只会生成一次,这样就可以保证稳定性了。唯一的id前面说过其实并不难,最简单的使用UUID就可以了。那么如何保证只生成一次呢?做法就是把第一次生成得到的cuid保存到一个有权限读写但是又不容易被清除(比如卸载应用就会自动清理)的地方。比如IOS的话,一般会保存在keychain中。安卓则一般存储在SD卡中。SD卡中以隐藏文件backups/.SystemConfig/.cuid保存,使用AES-128-CBC加密。

业界方案

腾讯无线运营部有个「灯塔移动终端统一身份体系」项目是为解决移动终端设备唯一标识的一套完整技术方案,支持Android、iOS等主流平台,并为复杂移动终端环境下追踪非登录类APP用户提供了一种较业界更准确、稳定的终端用户身份ID:QIMEI。

腾讯手机QQ浏览器则是采用服务端随机分配GUID的方式。

百度内部则统一使用CUID来唯一标识移动终端用户。其中CUID的生成策略依赖于一些设备信息。

andriod

  • ‘deviceid imei’逆序
  • deviceid = md5(imei + androidID + java.util.UUID)

IOS

md5(IDFV) + keychain

如果keychain中已经记录了cuid,会一直读keychain记录的值作为cuid,否则将cuid记到keychain中。

因为IDFV在用户卸载了所有相关Vendor(Appid的域名,如:com.baidu.XXX)应用后会改变,所以用户在卸载了所有百度系应用后,每次执行一次生成函数CUID都有可能不同(因为在iOS10.3 beta 2版本,苹果改变了keychain的存储策略,当同一开发者账号下的所有APP被删除,keychain存储的数据也会被清除) 因此,为了保证唯一性。当用户第一次生成CUID后,程序会将CUID存储到系统的KeyChain中。KeyChain的存储与系统的开发者信息相关,一旦存储成功就不会再次执行生成函数,保证了CUID的唯一性。

Android也是类型的,都是通过应用在终端缓存CUID来避免二次运行CUID生成算法来保证ID的稳定性。

但是这种方式存在变更风险。例如IOS在以下情况下就可能会产生CUID变更:

  1. 系统大版本升级,且没有通过iTunes备份系统(最大可能性)
  2. 对于一个没有安装过正式版百度地图的手机,安装渠道版本会写入KeyChain失败。因此,在这种情况下,每一次安装渠道包cuid都会发生变化
  3. 用户的IDFV获取失败,且KeyChain写入失败,这种情况会用随机数生成CUID(可能性小,随机算出的CUID每次都会不一样)

TIPS 为什么苹果反对开发人员使用UDID?

许多开发者把UDID跟用户的真实姓名、密码、住址、其它数据关联起来; 网络窥探者会从多个应用收集这些数据,然后顺藤摸瓜得到这个人的许多隐私数据。但是哈佛数据实验室的导师在2000年证实仅仅用ZIP、生日、性别就可以辨别87%的美国人。2010年10月巴克内尔大学信息安全和网络副主任则表示大部分应用确实在频繁传输UDID和私人信息。“例如,Amazon以纯文本的用户真名登陆,跟UDID一起”Amazon.com和网络窃取者都很容易匹配一个用户的UDID和手机名称。”而这些数据可以被关联然后被卖给有意向的用户,比如告商、配偶、离婚律师、债务催收公司,或工业间谍。

为了避免集体诉讼,苹果最终决定在iOS 5 的时候,将这一惯例废除,开发者被引导生成一个唯一的标识符,只能检测应用程序,其他的信息不提供。现在应用试图获取UDID已被禁止。


一些术语说明

1、UDID

一个40字符的唯一字串,一个设备对应一个UDID,例如5f9d473fa45e5c68be836dc63fef6e8700000000)。IOS6之后禁止使用。

禁用之后苹果给出了替代方案(此方法最终生成一个由连字符组合在一起的32字符,包含连字符一起是36个字符,CFUUIDCreate本身生成的值是128-bit,其算法是以以太网的硬件地址和以1582.10.15.0:0:0算起的100纳秒的数量组合形成的)的唯一字串,形如:68753A44-4D6F-1226-9C60-0050E4C00067。

但是此方法每次调用都会生成一个新值,不对应一个设备。而且iOS 7之后MAC地址获取也被禁用,之前的方法获取到的MAC地址全部都变成了02:00:00:00:00:00。

2、OpenUDID

OpenUDID是由Yann Lechelle(Appsfire创始人之一)在11年8月28日建立的一个开源项目,旨在创建一个设备唯一标识(UDID)此标识不因app的删除而改变。

其实现原理是:利用苹果推荐的UUID生成方法生成一个UDID(格式类似于:a633884c144d873504cdf379f21afd71bfa2eb0a),生成之后分别存在app对应的存储空间(NSUserDefaults)和一个剪切板中(每个应用会创建自己的剪切板用于保存数据,经验证重启后剪切板数据不丢失),当OpenUDID每次生成时会先取这两个值(剪切板数据每个App共享,每个应用均会创建一个剪切板保存此UDID)从而达到每个App、App删除后重新安装生成的UDID均一样。

OpenUDID利用了一个非常巧妙的方法在不同程序间存储标示符:在粘贴板中用了一个特殊的名称来存储标示符。通过这种方法,别的程序(同样使用了OpenUDID)知道去什么地方获取已经生成的标示符(而不用再生成一个新的)。

风险:每台iOS设备的OpenUDID是通过第一个带有OpenUDID SDK包的App生成,如果你完全删除全部带有OpenUDID SDK包的App(比如恢复系统等),那么再次调用生成函数将会生成一个不一样的UDID,相当于新设备。目前所有使用OpenUDID的App在github上有介绍。

为了解决所有使用OpenUDID的App全删除会使得生成的UDID不一样的问题,可以考虑将生成的UDID保存到keychain中(keychain不会因为App的删除或系统升级而删除,可以在同一SEEDID下的所有App之间共享,但是在iOS的系统还原设置中抹掉所有内容和设置后会清空keychain内的内容),优先使用keychain中的UDID。

3、idfa(advertisingIdentifier)

优点:不限于同一开发者账号共享 缺点:用户关闭广告追踪就获取不到此ID;用户可以手动还原重置ID。(系统“设置》隐私》广告”里限制广告跟踪和还原标识符)

4、idfv(identifierForVendor)

优点:同一开发者账号的APP可以共享ID 缺点:同一开发者账号下的APP被删除再重新安装,ID会重新生成

推荐阅读

  1. ios获取手机唯一标识符(标识符的详细使用)