阶乘后的零(JS实现

阶乘后的零(JS实现)

1 题目
给定一个整数 n,返回 n! 结果尾数中零的数量。
示例 1:
输入: 3
输出: 0
解释: 3! = 6, 尾数中没有零。
示例 2:
输入: 5
输出: 1
解释: 5! = 120, 尾数中有 1 个零.
说明: 你算法的时间复杂度应为 O(log n) 。

链接:https://leetcode-cn.com/problems/factorial-trailing-zeroes

2 思路
这道题要知道n!有多少个0,可以转换为 1…n这一系列数里面,有多少能被5整除,例如1…11中,有两个数5和10能被整除,因此就找到规律了

3代码
/**
* @param {number} n
* @return {number}
*/
var trailingZeroes = function(n) {
let num=0;
while (n>=5) {
n = Math.floor(n / 5);
num += n;
}

return num;
};

二叉搜索树迭代器(JS实现)

二叉搜索树迭代器(JS实现)

1 题目
实现一个二叉搜索树迭代器。你将使用二叉搜索树的根节点初始化迭代器。
调用 next() 将返回二叉搜索树中的下一个*小的数。
示例:
BSTIterator iterator = new BSTIterator(root);
iterator.next(); // 返回 3
iterator.next(); // 返回 7
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 9
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 15
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 20
iterator.hasNext(); // 返回 false
提示:
next() 和 hasNext() 操作的时间复杂度是 O(1),并使用 O(h) 内存,其中 h 是树的高度。
你可以假设 next() 调用总是有效的,也就是说,当调用 next() 时,BST 中至少存在一个下一个*小的数。

链接:https://leetcode-cn.com/problems/binary-search-tree-iterator

2 思路
这道题思路就是一个中序遍历,用一个栈来保存部分遍历的结果,先遍历左分支,将结果压入栈中,再转到右分支遍历

3代码
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
*/
var BSTIterator = function(root) {
this.root = root;
this.stack = [];
while(root) {
this.stack.push(root);
root = root.left;
}
};

/**
* @return the next smallest number
* @return {number}
*/
BSTIterator.prototype.next = function() {
let p = this.stack.pop();
let q = p.right;
while(q) {
this.stack.push(q);
q = q.left;
}
return p.val;
};

/**
* @return whether we have a next smallest number
* @return {boolean}
*/
BSTIterator.prototype.hasNext = function() {
return this.stack.length > 0;
};

/**
* Your BSTIterator object will be instantiated and called as such:
* var obj = new BSTIterator(root)
* var param_1 = obj.next()
* var param_2 = obj.hasNext()
*/

*大数(JS实现)

*大数(JS实现)

1 题目
给定一组非负整数,重新排列它们的顺序使之组成一个*大的整数。
示例 1:
输入: [10,2]
输出: 210
示例 2:
输入: [3,30,34,5,9]
输出: 9534330
说明: 输出结果可能非常大,所以你需要返回一个字符串而不是整数。

链接:https://leetcode-cn.com/problems/largest-number

2 思路
这道题思路就是如何将数组里面的数字进行排序,使得组合的数字*大,比较方法简单来说,两个数字a和b,比较ab和ba的大小,例如30和34,就是比较3034和3430,明显3430要大一些,因此应该34在30前面

3代码
/**
* @param {number[]} nums
* @return {string}
*/
var largestNumber = function(nums) {
let strs = nums.map(item => ” + item);

function compare(a, b) {
let len = a.length + b.length
for (let i=0; i<len; i++) {
let num1 = i >= a.length ? parseInt(b[i-a.length]) : parseInt(a[i]);
let num2 = i >= b.length ? parseInt(a[i-b.length]) : parseInt(b[i]);

if (num1 > num2) {
return -1;
} else if (num1 < num2) {
return 1;
}
}

return 0;
}

let ans = strs.sort(compare);

while(ans[0] === ‘0’ && ans.length > 1) ans.shift();

return ans.join(”);
};

重复的DNA序列(JS实现)

重复的DNA序列(JS实现)

1 题目
所有 DNA 都由一系列缩写为 A,C,G 和 T 的核苷酸组成,例如:“ACGAATTCCG”。在研究 DNA 时,识别 DNA 中的重复序列有时会对研究非常有帮助。
编写一个函数来查找目标子串,目标子串的长度为 10,且在 DNA 字符串 s 中出现次数超过一次。
示例:
输入:s = “AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT”
输出:[“AAAAACCCCC”, “CCCCCAAAAA”]

链接:https://leetcode-cn.com/problems/repeated-dna-sequences

2 思路
这道题思路很简单,就是利用哈希表来记录遇到子串的次数,但每次计算hash值时,可以使用Rabin-Karp 旋转哈希算法根据上个子串的hash在O(1)的时间内计算下一个子串的hash

3代码
/**
* @param {string} s
* @return {string[]}
*/
var findRepeatedDnaSequences = function(s) {
let result = [];
if (s.length <= 10) return result;

let map = {‘A’: 0, ‘C’: 1, ‘G’: 2, ‘T’: 3};
let map1 = {};
let tempNum = Math.pow(4, 9);

let txtHash = computeHash(s, 0, 10); //计算首次hash值

for (let i=0; i<=s.length-10; i++) {
if (i !==0 ) txtHash = (txtHash – map[s[i-1]] * tempNum) * 4 + map[s[i+9]]; //根据上次子串hash推导当前子串的hash
if (!map1[txtHash]) {
map1[txtHash] = 1;
} else if (map1[txtHash] === 1) {
result.push(s.substr(i, 10));
map1[txtHash]++;
}
}

function computeHash(s, start, len) {
let hash = 0;
for (let i=0; i<len; i++) {
hash += map[s[i+start]] * Math.pow(4, len – i – 1);
}
return hash;
}

return result;
};

iOS app如何才能安全登录验证

使用场景:安全级别比较高的项目,在http请求过程中,即使嗅探器捕获到网络请求的链接也无法去请求到数据。经过一番思考,觉得支付宝的签名完全符合这样子的需求。但是之前自己对签名、RSA等知识也是知道的很少。

一.登录登录机制

登录机制大概可以分为一下三个阶段:

  1. 1. 登录验证:是指客户端提供用户名和密码,向服务器提出登录请求,服务器判断客户端是否可以登录并向客户端确认。
  2. 2. 登录保持:是指客户端登录后, 服务器能够分辨出已登录的客户端,并为其持续提供登录权限的服务器。
  3. 3. 登出:是指客户端主动退出登录状态。

1.1 登录验证

*种网络请求情况(安全级别:II)

一般的情况是这个样子的:一但用户登陆成功(单方面MD5加密:服务器加密则客户端不加密,客户端加密则明文传输),服务器为客户端分配sessionID(也可以称为userID),当然有些服务器不但为客户端分配了userID还有可能会为用户提供token了(这个下面会做解释),然后每次网络请求都将sessionID当做参数传递给服务器。

优点

能够保持用户登录状态、区分用户,相对于不返回任何信息的登录要安全了一些。

缺点

如果通过网络嗅探器(例如:青花瓷)可以获取到http链接,这样子服务器返回的sessionID便会被获取到,这样子依然会造成信息泄露,并且还能被伪造请求(浏览器请求)。

1.1.1 密码的传输

第二种网络请求情况 (安全级别:III)

*种存在明显的安全隐患,但是目前市面上的好多app依然采用*种方法去实现登录、网络请求,但是对于安全级别较高的app,已经不再适用了。所以在此基础上进行优化—-采用非对称加密(公钥、私钥)。

登录模型

客户端*次发出登录请求时, 用户密码以明文的方式传输, 一旦被截获, 后果严重。因此密码需要加密,例如可采用RSA非对称加密。具体流程如下:

  1. 客户端向服务器*次发起登录请求(不传输用户名和密码)。
  2. 服务器利用RSA算法产生一对公钥和私钥。并保留私钥, 将公钥发送给客户端。
  3. 客户端收到公钥后, 加密用户密码, 向服务器发起第二次登录请求(传输用户名和加密后的密码)。
  4. 服务器利用保留的私钥对密文进行解密,得到真正的密码。

1.1.2 登录状态token

第三种网络请求情况(安全级别:IIII)

再仔细核对上述登录流程, 我们发现服务器判断用户是否登录, 完全依赖于sessionId, 一旦其被截获, 黑客就能够模拟出用户的请求。于是我们需要引入token的概念: 用户登录成功后, 服务器不但为其分配了sessionId, 还分配了token, token是维持登录状态的关键秘密数据。在服务器向客户端发送的token数据,也需要加密。于是一次登录的细节再次扩展。

客户端向服务器*次发起登录请求(不传输用户名和密码)。

服务器利用RSA算法产生一对公钥和私钥。并保留私钥, 将公钥发送给客户端。

客户端收到公钥后, 加密用户密码,向服务器发送用户名和加密后的用户密码; 同时另外产生一对公钥和私钥,自己保留私钥, 向服务器发送公钥; 于是第二次登录请求传输了用户名和加密后的密码以及客户端生成的公钥。

服务器利用保留的私钥对密文进行解密,得到真正的密码。 经过判断, 确定用户可以登录后,生成sessionId和token, 同时利用客户端发送的公钥,对token进行加密。*后将sessionId和加密后的token返还给客户端。

客户端利用自己生成的私钥对token密文解密, 得到真正的token。
图示如下:

%title插图%numlogin-300×181.png

1.2 登录保持

在*原始的方案中, 登录保持仅仅靠服务器生成的sessionId: 客户端的请求中带上sessionId, 如果服务器的Redis中存在这个id,就认为请求来自相应的登录客户端。 但是只要sessionId被截获, 请求就可以为伪造, 存在安全隐患。

引入token后,上述问题便可得到解决。 客户端将token和其它的一些变量, 利用散列加密算法得到签名后,连同sessionId一并发送给服务器; 服务器取出保存于服务器端的token,利用相同的法则生成校验签名, 如果客户端签名与服务器的校验签名一致, 就认为请求来自登录的客户端。

 

1.3 TOKEN失效
用户登录出系统

失效原理:
在服务器端的redis中删除相应key为session的键值对。

 

二.散列算法

散列是信息的提炼,通常其长度要比信息小得多,且为一个固定长度。加密性强的散列一定是不可逆的,这就意味着通过散列结果,无法推出任何部分的原始信息。任何输入信息的变化,哪怕仅一位,都将导致散列结果的明显变化,这称之为雪崩效应。散列还应该是防冲突的,即找不出具有相同散列结果的两条信息。具有这些特性的散列结果就可以用于验证信息是否被修改。

散列算法可以用来加密token生成签名, 以便token信息不暴露在网络同时还能验证登录的有效性。

2.1 md5

全写: Message Digest Algorithm MD5(中文名为消息摘要算法第五版)
输出: 128bit

MD5算法具有以下特点:

1、压缩性:任意长度的数据,算出的MD5值长度都是固定的。
2、容易计算:从原数据计算出MD5值很容易。
3、抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
4、弱抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
5、强抗碰撞:想找到两个不同的数据,使它们具有相同的MD5值,是非常困难的。
缺陷:Md5一度被认为十分靠谱。2004年8月17日的美国加州圣巴巴拉的国际密码学会议(Crypto’2004)上,来自中国山东大学的王小云教授做了破译MD5、HAVAL-128、 MD4和RIPEMD算法的报告,公布了MD系列算法的破解结果。2009年,冯登国、谢涛二人利用差分攻击,将MD5的碰撞算法复杂度从王小云的2^42进一步降低到2^21,*端情况下甚至可以降低至2^10。仅仅2^21的复杂度意味着即便是在2008年的计算机上,也只要几秒便可以找到一对碰撞。Md5已老, 在安全性要求较高的场合,不建议使用。

2.2 sha1

全名: 安全哈希算法(Secure Hash Algorithm)输出: 160bit
与Md5比较
相同点:因为二者均由MD4导出,SHA-1和MD5彼此很相似。相应的,他们的强度和其他特性也是相似。不同点:1. 对强行攻击的安全性:*显著和*重要的区别是SHA-1摘要比MD5摘要长32 位。使用强行技术,产生任何一个报文使其摘要等于给定报摘要的难度对MD5是2^128数量级的操作,而对SHA-1则是2^160数量级的操作。这样,SHA-1对强行攻击有更大的强度。2. 对密码分析的安全性:由于MD5的设计,易受密码分析的攻击,SHA-1显得不易受这样的攻击。3. 速度:在相同的硬件上,SHA-1的运行速度比MD5慢。

2.3 加盐

所谓加盐, 就是在原本需要加密的信息基础上,糅入其它内容salt。签名的生成就是一次加盐。

三、对称加密

本系统使用对称加密对用户密码进行加密以及生成token字符串。

3.1 AuthCode加密

AuthCode是康盛科技发明的加密方式, 开源产品Discuz的密码是用这个算法进行加密。但是有点遗憾,这个函数所有权属于康盛创想,并不能自由使用的。不知使用是否有风险??

3.2 AES加密

高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。

四、非对称加密

RSA是目前*有影响力的公钥加密算法,它能够抵抗到目前为止已知的*大多数密码攻击,已被ISO推荐为公钥数据加密标准。RSA的安全基于大数分解的难度。其公钥和私钥是一对大素数(100到200位十进制数或更大)的函数。从一个公钥和密文恢复出明文的难度,等价于分解两个大素数之积(这是公认的数学难题)。
算法描述:
(1)选择一对不同的、足够大的素数p,q。
(2)计算n=pq。
(3)计算f(n)=(p-1)(q-1),同时对p, q严加保密,不让任何人知道。
(4)找一个与f(n)互质的数e(公钥指数),且1<e<f(n)。
(5)计算d(私钥指数),使得de≡1 mod f(n)。这个公式也可以表达为d ≡e-1 mod f(n)注,≡是数论中表示同余的符号。
(6)公钥KU=(e,n),私钥KR=(d,n)。

iOS-数据结构

一.     在iOS开发中常用的结构体

1.    NSRange-一个范围结构体,location是位置,length是长度;{4,5},NSMakeRange(4,5);NSStringFromRange可以把它当NSString*返回

2.    NSSize-由一个CGSize被typedef过来,CGFloat是double类型typedef过来的,也由两个值组成:with宽度,height高度;{21,19};也可以NSMakeSize(21,19);NSStringFromSize可以由它返回一个NSString*返回

3.    NSPoint-有一个CGPoint被typedef过来的,由两个值组成:一个x值,一个y值;{0,0}或者NSMakePoint(0,0);NSStringFromPoint可以把它返回一个NSString*

4.    NSRect-是CGRect被typedef过来,由一个CGPoint和一个CGSize组成;{0,0,100,200}或者NSMakeRect(0,0,100,200);NSStringFromRect返回一个描述它的String*

二.     基本数据类型的包装类

1.    什么是基本数据类型的包装类:NSNumber它是一个类,继承于NSValue,可以用numberWithxxx把xxx基本类型入参返回一个NSSNumber类型的指针,可以用%@打印NSNumber;也可以用xxxValue方法把当前NSNumber返回一个xxx类型的基本数据

2.    好处:可以直接转字符串不用记住那么多打印的%格式;

3.    数组,集合中不允许存储基本数据类型,如果想让数组,集合中存储基本数据类型,就必须要把基本数据类型包装成NSNumber类

三.     集合类

1.    NSArray,NSDictionary,NSSet都是用来打包数据的,统称为集合类

2.    他们会有增删改查的基本功能,也都是类对象

3.    NSArray不可变,NSMutableArray可变;不可以在NSArray中存储nil,因为nil代表结束;

4.    NSArray实际上存储的是地址,也可以继续存储NSArray*,也就变成类一个二维表,每次打印NSArray时都是调用内部对象的description方法

5.    如果要在数组中存储基本数据类型一定要包装为NSNumber,否则会存储进乱码

6.    2013年新加入了@[]方式初始化一个NSArray,也可以快速获取NSArray中的对象array[index];

7.    #pragma mark xxx就可以在.m文件定义一个标记,可以快速定位到标记的地方

8.    初始化一般用initWithObjects:,而获取一般用objectAtIndex:方法

9.    count方法可以获得数组中元素的个数

10. 判断NSArray中是否存在某个对象:containsObject:返回BOOL

11. 增强for循环:for(NSObject*obj in array){}

12. 在实际的开发当中,单个数组一般都是只用来存放一种对象,因为遍历数组时如果调用某个对象特有的方法时会引起异常

13. 枚举器:objectEnumerator方法返回一个NSEnumerator类型的对象,也就是我们的枚举器对象。while(value = [enumerator nextObject]){}当nextObject在NSArray的*后一个对象为nil时就不再执行了

14. NSArray的排序:1使用sortedArrayUsingSelector,[arraysortedArrayUsingSelector:@selector(compare:)];     2使用block排序,[arraysortedArrayUsingComparator:^NSComparisonResult: (id obj1, obj2){return[obj1compare: obj2]}];obj1,obj2反过来就是倒叙;

15. 自定义类数组的排序:如果你想对自己定义的对象所存的数组进行排序,要新建一个NSSortDescriptor对象,NSSortDescriptor *sd = [NSSortDescriptor sortDescriptorWithKey: @”age”ascending:YES];其中@”age”就是你想要排序的主键,在把NSSortDescriptor对象放入一个NSArray中,之后调用[array sortedArrayUsingDescriptors:数组]为什么是数组呢?因为它支持多字段排序,前面的键优先级高。

16. 其实用block排序同样可以实现对自定义类数组的排序,比较方法要自己在block中写

17. NSMutableArray,同样是继承自NSArray;同样有增删改查addObject:obj,insertObject:obj atIndex:index,removebject:obj会删除数组中所有同地址对象。还有removeObjectAtIndex:index方法,注意index如果超出范围就会导致异常

18. 数组中可以存储同一个地址多次

19. removeAll是清空整个数组

20. 在遍历可变数组时,千万不要更改数组的count,否则可能引起程序错误或者引发异常

21. 如果非要删除数组中的对象,那么可以先声明一个NSMutableArray,在遍历NSArray同时删除NSMutableArray中的对象就可以了;

22. NSDictionary,字典类,不可变;

23. 初始化字典:[dictionaryWithObjectsWithKeys:@”value”,@”key”…,nil],也可以@{@”key1”:@”value1”,@”key2”:@”value2”,…};

24. 字典中可以存放任意的数据类型,基础数据类型同样要求封装为NSNumber之后存储

25. 字典中的顺序不和数组顺序一样时自然顺序

26. 字典内同样可以存放字典类型,类似NSArray,其实字典中存放的也是对象的指针存放的地址

27. 字典一样有count,一样是字典中字典项的个数

28. 取值,[dictobjectForKey:@”key”],键对应的值是什么类型就用什么类型的指针去接收即可

29. 遍历方式,首先要取出所有key,然后遍历key组成的NSArray,在循环体之中用[objectForKey:@”key”]单独获取当前遍历到的value即可实现

30. 如果你的字典中存储类多种不同类型,那么注意点和NSArray几乎一样

31. NSMutableDictionary,继承自NSDictionary,可以变换的字典;

32. 有增删改查,[setObject:objforKey:key]为key设置obj的值,[removeObjectForKey:key]方法用来删除给定key键的obj对象值,[removeAllObjects]清空字典;

33. 遍历,普通for,增强for,枚举器while遍历同上,都是类似的。

34. NSSet*大的功能就是它不可以存储相同的对象多次,同样不可变

35. NSMutableSet,可变集合,继承自NSSet。addObject:添加元素,removeObject:删除元素removeAllObject清空集合

36. 遍历NSMutableSet,NSEnumerator* en = [muset objectEnumerator];然后用nextObject实现遍历

37. 集合类之间的相互转换:NSXxx转换NSMutableXxx,xxxWithXxx:方法就可以实现

38. NSDictionary à NSArray:allKeys,allValues分别生成俩数组

39. 字符串转换成NSArray:componentsSeparatedByString:@”.”方法

40. 集合的手动内存管理:当你把对象存入到数组中的时候,数组会对这个对象进行一次retain操作,在数组removeObject:的时候会对这个对象进行一次release操作,使得retainCount正常。同样的,清空数组会对数组内的每个元素发送release消息。调用数组的release方法也是会对每个元素发送release消息

41. ARC机制下的集合内存管理:当你把对象存入到数组中时,它对对象进行一次strong指针保存,当数组removeObject或者removeAllObjects时会释放这个strong指针。同样的道理,数组调用= nil的时候,也同样会释放所有的strong指针。这样当元素 = nil的时候系统就自动回收元素了;

42. 所以ARC机制之中还是要担心内存溢出了

四.     文件管理器

1.    文件操作:NSFileManager,文件管理器,通过创建使用这个对象的方法来进行文件操作。

2.    NSFileManager * file =[NSFileManager defaultManager];获得文件管理器对象,且它是单实例模式。

3.    如果要模仿这种单实例的话,要在类方法中实现一个静态变量static NSObj* instance = nil;这个静态变量会在main函数结束才销毁,defaultObj方法只需要判断如果这个静态变量为nil就新建一个,否则直接返回这个静态变量即可;

4.    用%p可以打印对象的指针地址,同样NSFileManager也可以alloc,init来创建一个新的NSFileManager;

5.    获取文件属性:获取NSFileManager对象,&error入参来获取错误信息,然后通过attributesOfItemXxx:方法返回一个NSDictionary对象,里面是返回的文件信息;

6.    获取文件中的子集:contentsOfDirectoryAtPath:方法获取路径下的含有的子文件,只能够获得目标目录下一级的目录!如果想要打出给定路径下包含的所有子文件以及子文件内的文件,要用subpathsOfDirectoryAtPath:方法实现。

7.    创建一个文件夹:调用NSFileManager对象的createDictionaryAtPath:方法就是可以实现创建,第二个参数会逐级的创建文件夹,如果为No的话没有上级目录则会报错

8.    如何移动目录:调用NSFileManager对象的moveItemAtPath: toPath:方法实现

9.    删除目录:调用NSFileManager对象的removeItemAtPath:方法实现

10. 复制文件:调用NSFileManeger对象的copyItemAtPath:toPath:方法实现

11. 只要是文件,不论格式基本都可以使用NSData对象接收

 

五.     数据NSData

1.    NSData是你要加载不同格式文件时要使用的数据类型,这是专门用来处理文件数据的数据类型

2.    NSMutableData,可变数据类,继承自NSData类,用法类似于NSData

六.     日期操作

1.    NSDate,日期数据类型,初始化直接date方法就可以

2.    日期的比较:首先NSTimeInterval其实是一个double类型,可以在当前日期调用addTimeInterval:方法加上这个数据类型获得一个相差这么长时间段的NSDate对象,对了,NSTimeInterval是以秒计数;如果要减去就加上一个负的NSTimeInterval

3.    isEqualToDate:是否相同;earlierDate:比较早的时间;laterDate:比较晚的时间;

4.    格式化日期:NSDateFormatter就是一个日期格式化对象数据类型,调用NSDateFormatter的setDateFormat:方法。例如:@”yyyy/MM/dd hh:mm:ss”让后用NSString的stringFromDate方法即可

5.    这个格式化的hh代表12时制,HH代码24时制

6.    处理时区问题:调用NSDateFormatter的setTimeZone:方法计算时区导致的时差

iOS 证书详解

引言

关于开发证书配置(Certificates & Identifiers &

Provisioning

Profiles),相信做iOS开发的同学没少被折腾。对于一个iOS开发小白、半吊子(比如像我自己)抑或老兵,或多或少会有或曾有过以下不详、疑问、疑惑甚至困惑:

什么是App ID?Explicit/Wildcard App ID有何区别?什么是App Group ID?

什么是证书(Certificate)?如何申请?有啥用?

什么是Key Pair(公钥/私钥)?有啥用?与证书有何关联?

什么是签名(Signature)?如何签名(CodeSign)?怎样校验(Verify)?

什么是(Team)Provisioning Profiles?有啥用?

Xcode如何配置才能使用iOS真机进行开发调试?

多台机器如何共享开发者账号或证书?

遇到证书配置问题怎么办?

本文将围绕相关概念做个系统的梳理串烧。

写在前面

1.假设你使用过Apple设备(iMac/iPad/iPhone)且注册过Apple ID(Apple Account)。

2.假设你或你所在的开发组已加入苹果开发者计划(Enroll in iOS Developer Program to become amember),即已注册开发者账号(Apple Developer Account)。

只有拥有开发者账号,才可以申请开发/发布证书及相关配置授权文件,进而在iOS真机上开发调试Apps或发布到App Store。

开发者账号分为Individual和Company/Organization两种类型。如无特别交代,下文基于$99/Year的普通个人开发者(Individual)账号展开。

3.若要真机调试实践,你必须至少拥有一台装有Mac OS X/Xcode的Mac开发机(iMac or MacBook),其上自带原生的Keychain Access。

一.App ID(bundle identifier)

App ID即Product ID,用于标识一个或者一组App。

App ID应该和Xcode中的Bundle Identifier是一致(Explicit)的或匹配(Wildcard)的。

App ID字符串通常以反域名(reverse-domain-name)格式的Company Identifier(Company ID)作为前缀(Prefix/Seed),一般不超过255个ASCII字符。

App ID全名会被追加Application Identifier Prefix(一般为TeamID.),分为两类:

Explicit App ID:唯一的App ID,用于唯一标识一个应用程序。例如“com.apple.garageband”这个App ID,用于标识Bundle Identifier为“com.apple.garageband”的App。

Wildcard App ID:含有通配符的App ID,用于标识一组应用程序。例如“*”(实际上是Application

Identifier Prefix)表示所有应用程序;而“com.apple.*”可以表示Bundle

Identifier以“com.apple.”开头(苹果公司)的所有应用程序。

用户可在Developer MemberCenter网站上注册(Register)或删除(Delete)已注册的App IDs。

App ID被配置到【XcodeTarget|Info|Bundle Identifier】下;对于Wildcard App ID,只要bundle identifier包含其作为Prefix/Seed即可。

.设备(Device)

Device就是运行iOS系统用于开发调试App的设备。每台Apple设备使用UDID来唯一标识。

iOS设备连接Mac后,可通过iTunes->Summary或者Xcode->Window->Devices获取iPhone的UDID(identifier)。

Apple Member Center网站个人账号下的Devices中包含了注册过的所有可用于开发和测试的设备,普通个人开发账号每年累计*多只能注册100个设备。

Apps signed by you or your team run only on designated development devices.

Apps run only on the test devices you specify.

用户可在网站上注册或启用/禁用(Enable/Disable)已注册的Device。

本文的Devices是指连接到Xcode被授权用于开发测试的iOS设备(iPhone/iPad)。

.开发证书(Certificates)

1.证书的概念

证书是由公证处或认证机关开具的证明资格或权力的证件,它是表明(或帮助断定)事理的一个凭证。证件或凭证的尾部通常会烙印公章

每个中国人一生可能需要70多个证件,含15种身份证明。证件中“必需的”有30到40个。将这些证件按时间顺序铺开,那就是一个天朝子民的一生——持准生证许可落地,以户籍证明入籍,以身份证认证身份,持结婚证以合法同居,*终以死亡证明注销。

2.数字证书的概念

数字证书就是互联网通讯中标志通讯各方身份信息的一串数字,提供了一种在Internet上验证通信实体身份的方式,其作用类似于司机的驾驶执照或日常生活中的身份证。它是由一个由权威机构——CA机构,又称为证书授权中心(Certificate Authority)发行的,人们可以在网上用它来识别对方的身份。

数字证书是一个经证书授权中心数字签名的包含公开密钥拥有者信息以及公开密钥的文件。*简单的证书包含一个公开密钥、名称以及证书授权中心的数字签名。

数字证书还有一个重要的特征就是时效性:只在特定的时间段内有效。

数字证书中的公开密钥(公钥)相当于公章。

某一认证领域内的根证书是CA认证中心给自己颁发的证书,是信任链的起始点。安装根证书意味着对这个CA认证中心的信任。

为了防止GFW进行中间人攻击(MitM),例如篡改github证书,导致无法访问github网站等问题,可选择不信任CNNIC:

在[钥匙串-系统]中双击CNNIC ROOT,在【信任】|【使用此证书时】下拉选择【永不信任】。

%title插图%num

在天朝子民的一生中,户籍证明可理解为等效的根证书:有了户籍证明,才能办理身份证;有了上流的身份证,才能办理下游居住证、结婚证、计划生育证、驾驶执照等认证。

3.iOS(开发)证书

iOS证书是用来证明iOS App内容(executable code)的合法性和完整性的数字证书。对于想安装到真机或发布到AppStore的应用程序(App),只有经过签名验证(Signature Validated)才能确保来源可信,并且保证App内容是完整、未经篡改的。

iOS证书分为两类:Development和Production(Distribution)。

Development证书用来开发和调试应用程序:Adevelopment certificateidentifies you, as a team member, in a development provisioning profile that allows apps signed by you tolaunchon devices.

Production主要用来分发应用程序(根据证书种类有不同作用):Adistribution certificateidentifies your team or organization in a distribution provisioning profile and allows you tosubmityour app to the store. Only a team agent or an admin can create a distribution certificate.

普通个人开发账号*多可注册iOS Development/Distribution证书各2个,用户可在网站上删除(Revoke)已注册的Certificate。

下文主要针对iOS App开发调试过程中的开发证书(Certificate for Development)。

4.iOS(开发)证书的根证书

那么,iOS开发证书是谁颁发的呢?或者说我们是从哪个CA申请到用于Xcode开发调试App的证书呢?

iOS以及Mac OS X系统(在安装Xcode时)将自动安装AppleWWDRCA.cer这个中间证书(Intermediate Certificates),它实际上就是iOS(开发)证书的证书,即根证书(Apple Root Certificate)。

AppleWWDRCA(Apple Root CA)类似注册管理户籍的公安机关户政管理机构,AppleWWDRCA.cer之于iOS(开发)证书则好比户籍证之于身份证。

如果Mac Keychain Access证书助理在申请证书时尚未安装过该证书,请先下载安装(Signing requires that

you have both the signing identity and the intermediate certificate

installed in your keychain)。

%title插图%num

5.申请证书(CSR:Certificate Signing Request)

可以在缺少证书时通过Xcode Fix Issue自动请求证书,这里通过Keychain证书助理从证书颁发机构请求证书:填写开发账号邮件和常用名称,勾选【存储到磁盘】。

%title插图%num

keychain将生成一个包含开发者身份信息的CSR(Certificate Signing Request)文件;同时,Keychain Access|Keys中将新增一对Public/PrivateKey Pair(Thissigning identity consists of a public-private key pair that Apple issues)。

%title插图%num

private key始终保存在Mac OS的Keychain Access中,用于签名(CodeSign)对外发布的App;public key一般随证书(随Provisioning Profile,随App)散布出去,对App签名进行校验认证。用户必须保护好本地Keychain中的private key,以防伪冒。

Keep a secure backup of your public-private key pair. If the private key is lost, you’ll have to create anentirely newidentity to sign code.

Worse, if someone else has your private key, that person may be able toimpersonateyou.

在Apple开发网站上传该CSR文件来添加证书(Upload CSR file to generate your certificate):

%title插图%num

Apple证书颁发机构WWDRCA(Apple Worldwide Developer Relations Certification Authority)将使用private key对CSR中的public key和一些身份信息进行加密签名生成数字证书(ios_development.cer)并记录在案(Apple Member Center)。

%title插图%num

从Apple Member

Center网站下载证书到Mac上双击即可安装(当然也可在Xcode中添加开发账号自动同步证书和[生成]配置文件)。证书安装成功后,在KeychainAccess|Keys中展开创建CSR时生成的Key

Pair中的私钥前面的箭头,可以查看到包含其对应公钥的证书(Your requested certificate will be the

public half of the key pair.);在Keychain

Access|Certificates中展开安装的证书(ios_development.cer)前面的箭头,可以看到其对应的私钥。

%title插图%num
%title插图%num

Certificate被配置到【Xcode Target|Build Settings|Code Signing|Code Signing Identity】下,下拉选择Identities from Profile “…”(一般先配置Provisioning Profile)。以下是Xcode配置示例:

%title插图%num

.供应配置文件(Provisioning Profiles

1.Provisioning Profile的概念

Provisioning Profile文件包含了上述的所有内容:证书、App ID和设备

%title插图%num

一个Provisioning Profile对应一个Explicit App ID或Wildcard App

ID(一组相同Prefix/Seed的App IDs)。在网站上手动创建一个Provisioning Profile时,需要依次指定App

ID(单选)、证书(Certificates,可多选)和设备(Devices,可多选)。用户可在网站上删除(Delete)已注册的Provisioning

Profiles。

Provisioning Profile决定Xcode用哪个证书(公钥)/私钥组合(Key

Pair/Signing Identity)来签署应用程序(Signing

Product),将在应用程序打包时嵌入到.ipa包里。安装应用程序时,Provisioning

Profile文件被拷贝到iOS设备中,运行该iOS App的设备也通过它来认证安装的程序。

如果要打包或者在真机上运行一个APP,一般要经历以下三步:

首先,需要指明它的App ID,并且验证Bundle ID是否与其一致;

其次,需要证书对应的私钥来进行签名,用于标识这个APP是合法、安全、完整的;

然后,如果是真机调试,需要确认这台设备是否授权运行该APP。

Provisioning Profile把这些信息全部打包在一起,方便我们在调试和发布程序打包时使用。这样,只要在不同的情况下选择不同的Provisioning Profile文件就可以了。

Provisioning

Profile也分为Development和Distribution两类,有效期同Certificate一样。Distribution版本的ProvisioningProfile主要用于提交App

Store审核,其中不指定开发测试的Devices(0,unlimited)。App ID为Wildcard App ID(*)。App

Store审核通过上架后,允许所有iOS设备(Deployment Target)上安装运行该App。

Xcode将全部供应配置文件(包括用户手动下载安装的和Xcode自动创建的Team Provisioning Profile)放在目录~/Library/MobileDevice/Provisioning Profiles下。

2.Provisioning Profile的构成

以下为典型供应配置文件*.mobileprovision的构成简析

(1)Name:该mobileprovision的文件名。

(2)UUID:该mobileprovision文件的真实文件名。

(3)TeamName:Apple ID账号名。

(4)TeamIdentifier:Team Identity。

(5)AppIDName:explicit/wildcard App ID name(ApplicationIdentifierPrefix)。

(6)ApplicationIdentifierPrefix:完整App ID的前缀(TeamIdentifier.*)。

(7)DeveloperCertificates:包含了可以为使用该配置文件应用签名的所有证书。

证书是基于Base64编码,符合PEM(PrivacyEnhanced Mail, RFC 1848)格式的,可使用OpenSSL来处理(opensslx509 -text -in file.pem)。

从DeveloperCertificates提取之间的内容到文件cert.cer(cert.perm):

—–BEGIN CERTIFICATE—–

将之间的内容拷贝至此

—–END CERTIFICATE—–`

Mac下右键QuickLook查看cert.cer(cert.perm),在Keychain Access中右键Get

Info查看对应证书ios_development.cer,正常情况(公私钥KeyPair配对)应吻合;Windows下没有足够信息(WWDRCA.cer),无法验证该证书。

如果你用了一个不在这个列表中的证书进行签名,无论这个证书是否有效,这个应用都将CodeSign Fail。

(8)Entitlements键对应的:

keychain-access-groups:$(AppIdentifierPrefix),参见Code Signing Entitlements(*.entitlements)。

每个应用程序都有一个可以用于安全保存一些如密码、认证等信息的keychain,一般而言自己的程序只能访问自己的keychain。通过对应用签名时的一些设置,还可以利用keychain的方式实现同一开发者签证(就是相同bundle seed)下的不同应用之间共享信息的操作。比如你有一个开发者帐户,并开发了两个不同的应用A和B,然后通过对A和B的keychain access group这个东西指定共用的访问分组,就可以实现共享此keychain中的内容。

application-identifier:带前缀的全名,例如$(AppIdentifierPrefix)com.apple.garageband。

com.apple.security.application-groups:App Group ID(group. com.apple),参见Code Signing Entitlements(*.entitlements)。

com.apple.developer.team-identifier:同Team Identifier。

(9)ProvisionedDevices:该mobileprovision授权的开发设备的UDID 。

Provisioning Profile被配置到【XcodeTarget|Build Settings|Code Signing|Provisioning Profile】下,然后在Code Signing Identity下拉可选择Identities from Profile “…”(即Provisioning Profile中包含的Certificates)。

.开发组供应配置文件(Team Provisioning Profiles

1.Team Provisioning Profile的概念

每个Apple开发者账号都对应一个唯一的Team ID,Xcode3.2.3预发布版本中加入了Team Provisioning Profile这项新功能。

在Xcode中添加Apple Developer Account时,它将与Apple Member Center后台勾兑自动生成iOS Team Provisioning Profile(Managed by Xcode)。

%title插图%num

Team Provisioning Profile包含一个为Xcode iOS Wildcard App ID(*)生成的iOS Team

Provisioning Profile:*(匹配所有应用程序),账户里所有的Development

Certificates和Devices都可以使用它在这个team注册的所有设备上调试所有的应用程序(不管bundle

identifier是什么)。同时,它还会为开发者自己创建的Wildcard/Explicit App IDs创建对应的iOS Team

Provisioning Profile。

%title插图%num

2.Team Provisioning Profile生成/更新时机

Add an Apple ID account to Xcode

Fix issue “No Provisioning Profiles with a valid signing identity” in Xcode

Assign Your App to a Team in Xcode project settings of General|Identity

Register new device on the apple development website or Xcode detected new device connected

利用Xcode生成和管理的iOS Team Provisioning Profile来进行开发非常方便,可以不需要上网站手动生成下载Provisioning Profile。

Team Provisioning Profile同Provisioning Profile,只不过是由Xcode自动生成的,也被配置到【XcodeTarget|Build Settings|Code Signing|Provisioning Profile】下。

.App Group (ID)

1.App Group的概念

WWDC14除了发布了OS X v10.10和switf外,iOS 8.0也开始变得更加开放了。说到开放,当然要数应用扩展(App Extension)了。顾名思义,应用扩展允许开发者扩展应用的自定义功能和内容,能够让用户在使用其他应用程序时使用该项功能,从而实现各个应用程序间的功能和资源共享。可以将扩展理解为一个轻量级(nimble and lightweight)的分身。

扩展和其Containing App各自拥有自己的沙盒,虽然扩展以插件形式内嵌在Containing

App中,但是它们是独立的二进制包,不可以互访彼此的沙盒。为了实现Containing App与扩展的数据共享,苹果在iOS

8中引入了一个新的概念——App Group,它主要用于同一Group下的APP实现数据共享,具体来说是通过以App Group

ID标识的共享资源区——App Group Container。

App Group ID同App ID一样,一般不超过255个ASCII字符。用户可在网站上编辑Explicit App IDs的App Group Assignment;可以删除(Delete)已注册的AppGroup (ID)。

2.App Group的配置

Containing App与Extension的Explicit App ID必须Assign到同一App Group下才能实现数据共享,并且Containing App与Extension的App ID命名必须符合规范:

置于同一App Group下的App IDs必须是唯一的(Explicit,not Wildcard)

Extension App ID以Containing App ID为Prefix/Seed

假如Garageband这个App ID为“com.apple.garageband”,则支持从语音备忘录导入到Garageband应用的插件的App ID可能形如“com.apple.garageband.extImportRecording”。

App(ex)

App Group IDProvisioning Profile

Code Signing Identity

(Certificate Key Pair)App ID

(bundle identifier)Devices

(test)

GarageBand

置于同一分组:

group.com.apple(1)共用同一证书:ios_development.cer

(2)共用证书Key Pair中的Private Key进行CodeSigncom.apple.garageband

授权开发测试设备的UDIDs

GarageBand扩展插件

com.apple.garageband.extImportRecording

关于Provisioning Profile,可以使用自己手动生成的,也可以使用Xcode自动生成的Team Provisioning Profile。

App Group会被配置到【Xcode Target|Build Settings|Code Signing|Code Signing Entitlements】文件(*.entitlements)的键com.apple.security.application-groups下,不影响Provisioning Profile生成流程。

.证书与签名(Certificate& Signature)

1.Code Signing Identity

%title插图%num
%title插图%num

Xcode中配置的Code Signing

Identity(entitlements、certificate)必须与Provisioning

Profile匹配,并且配置的Certificate必须在本机Keychain Access中存在对应Public/Private Key

Pair,否则编译会报错。

Xcode所在的Mac设备(系统)使用CA证书(WWDRCA.cer)来判断Code Signing Identity中Certificate的合法性:

若用WWDRCA公钥能成功解密出证书并得到公钥(Public Key)和内容摘要(Signature),证明此证书确乃AppleWWDRCA发布,即证书来源可信;

再对证书本身使用哈希算法计算摘要,若与上一步得到的摘要一致,则证明此证书未被篡改过,即证书完整。

2.Code Signing

每个证书(其实是公钥)对应Key Pair中的私钥会被用来对内容(executable code,resources such as images and nib files aren’t signed)进行数字签名(CodeSign)——使用哈希算法生成内容摘要(digest)。

Xcode使用指定证书配套的私钥进行签名时需要授权,选择【始终允许】后,以后使用该私钥进行签名便不会再弹出授权确认窗口。

%title插图%num

3.Verify Code Signature with Certificate

上面已经提到,公钥被包含在数字证书里,数字证书又被包含在描述文件(Provisioning File)中,描述文件在应用被安装的时候会被拷贝到iOS设备中。

*步,App在Mac/iOS真机上启动时,需要对配置的bundle ID、entitlements和certificate与Provisioning Profile进行匹配校验:

%title插图%num

第二步,iOS/Mac真机上的ios_development.cer被AppleWWDRCA.cer中的 public key解密校验合法后,获取每个开发证书中可信任的公钥对App的可靠性和完整性进行校验。

iOS/Mac设备(系统)使用App Provisioning Profile(Code Signing Identity)中的开发证书来判断App的合法性:

若用证书公钥能成功解密出App(executable code)的内容摘要(Signature),证明此App确乃认证开发者发布,即来源可信;

再对App(executable code)本身使用哈希算法计算摘要,若与上一步得到的摘要一致,则证明此App(executable code)未被篡改过,即内容完整。

小结:

基于Provisioning Profile校验了CodeSign的一致性;

基于Certificate校验App的可靠性和完整性;

启动时,真机的device ID(UUID)必须在Provisioning Profile的ProvisionedDevices授权之列。

.在多台机器上共享开发账户/证书

1.Xcode导出开发者账号(*.developerprofile)或PKCS12文件(*.p12)

进入Xcode Preferences|Accounts:

选中Apple IDs列表中对应Account的的Email,点击+-之后的☸|Export Accounts,可导出包含account/code signing identity/provisioning profiles信息的*.developerprofile(Exporting a Developer Profile)文件供其他机器上的Xcode开发使用(Import该Account)。

选中右下列表中某行Account Name条目|ViewDetails,可以查看Signing Identities和Provisioning Profiles。

选中欲导出的Signing Identity条目,点击栏底+之后的☸|Export,必须输入密码,并需授权export key “privateKey” from keychain,将导出Certificates.p12。

点击左下角的刷新按钮可从Member Center同步该账号下所有的Provisioning Profile到本地。

选中右击列表中某个Provisioning Profile可以【Show in Finder】到[~/Library/MobileDevice/Provisioning\ Profiles]目录,其中Provisioning Profile的真实名称为$(UUID).mobileprovision,名如”2488109f-ff65-442e-9774-fd50bd6bc827.mobileprovision”,其中Name中为Xcode中看到的描述性别名。

2.Keychain Access导出PKCS12文件(*.p12)

在Keychain Access|Certificates中选中欲导出的certificate或其下private key,右键Export或者通过菜单File|Export Items导出Certificates.p12——PKCS12 file holds theprivate keyandcertificate

其他Mac机器上双击Certificates.p12(如有密码需输入密码)即可安装该共享证书。有了共享证书之后,在开发者网站上将欲调试的iOS设备注册到该开发者账号名下,并下载对应证书授权了iOS调试设备的Provisioning

Profile文件,方可在iOS真机设备上开发调试。

九.证书配置常见错误

1.no such provisioning profile was found

Xcode Target|Genera|Identity Team下提示”Your build settings specify a

provisioning profile with the UUID “xxx”,howerver, no such provisioning

profile was found.”

Xcode Target|BuildSettings|Code Signing|当前配置的指定UDID的provisioning

profile在本地不存在,此时需要更改Provisioning Profile。必要时手动去网站下载或重新生成Provisioning

Profile或直接在Xcode中Fix issue予以解决(可能自动生成iOS Team ProvisioningProfile)!

2.No identities from profile

Build Settings|CodeSigning的Provisioning Profile中选择了本地安装的provisioning profile之后,Code Signing Identity中下拉提示No identities from profile “…”or No identities from keychain.

Xcode配置指定UDID的provisioning profile中的DeveloperCertificates在本地KeyChain中不存在(No identities are available)或不一致(KeyPair中的Private Key丢失),此时需去网站检查ProvisioningProfile中的App ID-Certificate-Device配置是否正确。如果是别人提供的共享账号(*.developerprofile)或共享证书(*.p12),请确保导出了对应Key Pair中的Private Key。必要时也直接在Xcode中Fix issue予以解决(可能自动生成iOS Team ProvisioningProfile)。

3.Code Signing Entitlements file do not match profile

“Invalid application-identifier Entitlement” or “Code Signing Entitlements file do not match those specified in your provisioning profile.(0xE8008016).”

(1)检查对应版本(Debug)指定的*.entitlements文件中的“Keychain Access Groups”键值是否与ProvisioningProfile中的Entitlements项相吻合(后者一般为前者的Prefix/Seed)。

(2)也可以将Build Settings|Code Signing的Provisioning Profile中对应版本(Debug)的Entitlements置空。

4.Xcode配置反应有时候不那么及时,可刷新、重置相关配置项开关(若有)或重启Xcode试试。

iOS 证书 密钥及信任服务

iOS 证书、密钥及信任服务

——翻译自Apple Reference《Certificate,Key,andTrust Services Programming Guide》

 

本章描述并演示了如何使用证书、密钥和信任服务去导入一个indentity,评估证书是否可信,判断证书失效的原因,以及失效证书的恢复。

本章按如下顺序分别演示了:

导入一个identity.

从导入的数据中获得证书.

获得用于证书评估的策略.

校验证书,根据指定策略评估证书是否可信.

测试证书中的可恢复错误.

判断证书是否过期.

改变评估条件,忽略过期证书.

重新评估证书.

“第2章,Certificate,Key,and TrustServices Concepts”,介绍了证书,密钥和信任服务的概念和术语。关于证书,密钥和信任服务的细节内容,请参考Certificate,Key,andTrust Services Reference.

从一个.p12文件中提取、评估Identity
如果你需要在iOS设备上使用加密过的identity(一个密钥及其关联的证书)进行客户端认证,例如——你可以把PKCS#12数据以受密码保护的文件的方式安全地传输到这个设备上。本节显示如何从PKCS#12数据中提取identity和trustobjects(可信任对象),并评估其可信度。

列表 2-1 显示了用SecPKCS12Import函数从.p12文件中提取identity和可信任对象,以及评估其可信度。

列表 2-2 显示如何从identity中获取证书并显示证书信息。每个列表后都对代码进行了解释。

在编译这段代码时,请确认在Xcode工程中加入了Security.framework。

列表  2-1  从PKCS#12数据中提取identity和trust对象

#import <UIKit/UIKit.h>

#import <Security/Security.h>

#import<CoreFoundation/CoreFoundation.h>

 

NSString *thePath = [[NSBundle mainBundle]

pathForResource:@”MyIdentity” ofType:@”p12″];

NSData *PKCS12Data = [[NSData alloc]initWithContentsOfFile:thePath];

CFDataRef inPKCS12Data =(CFDataRef)PKCS12Data;            // 1

 

OSStatus status = noErr;

SecIdentityRef myIdentity;

SecTrustRef myTrust;

status = extractIdentityAndTrust(

inPKCS12Data,

&myIdentity,

&myTrust);                // 2

if status != 0 …  //Do some error checking here

 

SecTrustResultType trustResult;

 

if(status == noErr) {                                     // 3

status =SecTrustEvaluate(myTrust, &trustResult);

}

 

 

…                                                            // 4

if (trustResult ==kSecTrustResultRecoverableTrustFailure) {

…;

}

 

OSStatus extractIdentityAndTrust(CFDataRefinPKCS12Data,        // 5

SecIdentityRef *outIdentity,

SecTrustRef *outTrust)

{

OSStatus securityError = errSecSuccess;

 

 

CFStringRef password =CFSTR(“Password”);

const void *keys[] =   { kSecImportExportPassphrase };

const void *values[] = { password };

CFDictionaryRef optionsDictionary =CFDictionaryCreate(

NULL,keys,

values, 1,

NULL, NULL);  // 6

 

 

CFArrayRef items = CFArrayCreate(NULL, 0, 0,NULL);

securityError = SecPKCS12Import(inPKCS12Data,

optionsDictionary,

&items);                   // 7

 

 

//

if (securityError == 0) {                                  // 8

CFDictionaryRefmyIdentityAndTrust = CFArrayGetValueAtIndex (items, 0);

const void *tempIdentity= NULL;

tempIdentity =CFDictionaryGetValue (myIdentityAndTrust,

kSecImportItemIdentity);

*outIdentity =(SecIdentityRef)tempIdentity;

const void *tempTrust =NULL;

tempTrust =CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemTrust);

*outTrust =(SecTrustRef)tempTrust;

}

 

if (optionsDictionary)

CFRelease(optionsDictionary);                           //9

[PKCS12Data release];

在这段代码中:

检索PKCS#12文件并获得数据。本例中,该文件位于applicationbundle中。你也可以通过网络方式把文件传输给你的应用程序。

调用该函数获得identity和trust对象(见步骤5)。

评估证书。这里的信任对象(trust object),包括信任策略和其他用于判断证书是否可信的信息,都已经含在了PKCS数据中。要单独评估一个证书是否可信,见列表2-6。

处理信任结果。如果信任结果是kSecTrustResultInvalid,kSecTrustResultDeny,kSecTrustResultFatalTrustFailure,你无法进行处理。如果评估结果是kSecTrustResultRecoverableTrustFailure,你可以从信任失败中恢复。参见“从信任失败中恢复”。

第2步中调用的函数的具体实现。

构造包含了密码的dictionary,用于传递给SecPKCS12Import函数。注意这里使用的是corefoundation中的CFDictionaryRef,与NSDictionary完全等价。列表 2-9 则是一个使用NSDictionary的例子。

从PKCS#12数据中提取证书、密钥和trust并将其放到数组中。

从数组中取出第1个dictionary,并从dictionary中取出identity和trust。SecPKCS12Import函数将PKCS数据中的每一个条目返回为一个dictionary。本例中,identity被提取到数组的第1个元素。

释放dictionary和PKCS12Data,他们不再被使用。

以下列表显示如何从identity中获取证书以及显示证书信息。编译本段代码前请确保在工程中导入了Security.framework。

列表  2-2  显示证书信息

// 从identity获取证书.

SecCertificateRef myReturnedCertificate = NULL;

status = SecIdentityCopyCertificate(myReturnedIdentity,

&myReturnedCertificate); // 1

 

CFStringRef certSummary = SecCertificateCopySubjectSummary

(myReturnedCertificate);  //2

 

NSString* summaryString = [[NSString alloc]

initWithString:(NSString*)certSummary];  // 3

 

//Display the string

[summaryString release];                                  // 4

在这段代码中:

从identity中提取证书。

从证书中获取summary摘要信息。

string 转换为NSString。

释放NSString。

 

获取和使用持久化的钥匙串
当你在钥匙串中添加或查找一个条目时,你需要有一个持久化的引用。因为持久化引用能保证在程序从启动到能写入磁盘这段时间内,始终可用。当需要反复在钥匙串中查找条目时,使用持久化引用更加容易。以下代码演示如何获取一个identity的持久化引用。

列表  2-3  获取identity的持久化引用

CFDataRef persistentRefForIdentity(SecIdentityRefidentity)

{

OSStatus status;

 

CFTypeRef identity_handle = NULL;

const void *keys[] =   { kSecReturnPersistentRef, kSecValueRef };

const void *values[] = { kCFBooleanTrue,          identity };

CFDictionaryRef dict = CFDictionaryCreate(NULL,keys, values,

2, NULL, NULL);

status = SecItemAdd(dict, &persistent_ref);

 

if (dict)

CFRelease(dict);

 

return (CFDataRef)persistent_ref;

}

下面演示使用持久化引用从钥匙串中检索identity对象。

列表 2-4  用持久化引用获取identity对象

SecIdentityRefidentityForPersistentRef(CFDataRef persistent_ref)

{

CFTypeRef   identity_ref     = NULL;

const void *keys[] =   { kSecReturnRef, kSecValuePersistentRef };

const void *values[] = { kCFBooleanTrue,persistent_ref };

CFDictionaryRef dict = CFDictionaryCreate(NULL,keys, values,

2, NULL, NULL);

SecItemCopyMatching(dict, &identity_ref);

 

if (dict)

CFRelease(dict);

 

return (SecIdentityRef)identity_ref;

}

 

从钥匙串中查找证书
以下代码演示如何使用证书名(在钥匙串中用证书名标识证书)查找证书。要用持久化引用在钥匙串中找到一个条目,参考列表 2-4。要用一个id字串查找一个条目,参考“数据加密和解密”。

列表 2-5  在钥匙串中查找证书

CFTypeRef   certificateRef     = NULL;                     // 1

const char *certLabelString = “RomeoMontegue”;

CFStringRef certLabel =CFStringCreateWithCString(

NULL, certLabelString,

kCFStringEncodingUTF8);         // 2

 

const void *keys[] =   { kSecClass, kSecAttrLabel, kSecReturnRef };

const void *values[] = { kSecClassCertificate,certLabel, kCFBooleanTrue };

CFDictionaryRef dict = CFDictionaryCreate(NULL,keys,

values, 3,

NULL, NULL);       // 3

status = SecItemCopyMatching(dict,&certificateRef);        // 4

 

if (dict)

CFRelease(dict);

 

在这段代码中:

定义变量,存储证书对象。

定义字符串,存储证书名。

定义 dictionary,存储证书查找条件。键-值序列中的键 kSecReturnRef表明,函数调用结束时应返回一个钥匙串条目的引用(当查找有结果时)。

 

在钥匙串中查找证书。

 

获取策略对象并评估可信度
评估证书可信度之前,必需获取到一个证书对象的引用。你可以从一个identity中提取一个证书对象(列表2-2),也可以从DER证书数据中创建证书对象(使用SecCertificateCreateWithData函数,见列表 2-6),或者从钥匙串中查找证书(列表2-5)。

评估信任度的标准由信任策略(trust policy)指定。列表3-2 显示如何获得用于评估的策略对象。在iOS中有两种策略可用:Basic X509和SSL(参考AppleX509TP 信任策略)。可以用SecPolicyCreateBasicX509或者SecPolicyCreateSSL函数获取策略对象。

下列代码显示了获取策略对象并用于评估证书是否可信。

列表 2-6  获取策略对象用于评估

NSString *thePath = [[NSBundle mainBundle]

pathForResource:@”Romeo Montegue” ofType:@”cer”];

NSData *certData = [[NSData alloc]

initWithContentsOfFile:thePath];

CFDataRef myCertData = (CFDataRef)certData;                // 1

 

SecCertificateRef myCert;

myCert = SecCertificateCreateWithData(NULL,myCertData);    // 2

 

SecPolicyRef myPolicy = SecPolicyCreateBasicX509();        // 3

 

SecCertificateRef certArray[1] = { myCert };

CFArrayRef myCerts = CFArrayCreate(

NULL, (void *)certArray,

1, NULL);

SecTrustRef myTrust;

OSStatus status =SecTrustCreateWithCertificates(

myCerts,

myPolicy,

&myTrust);  // 4

 

SecTrustResultType trustResult;

if (status == noErr) {

status =SecTrustEvaluate(myTrust, &trustResult);       // 5

}

…                                                            // 6

if (trustResult ==kSecTrustResultRecoverableTrustFailure) {

…;

}

if (myPolicy)

CFRelease(myPolicy);                                   // 7

在这段代码中:

查找证书文件并获取数据。本例中,该文件位于应用程序束。但你也可以从网络获取证书。如果证书存在于钥匙串中,参考“在钥匙串中查找证书”。

从证书数据中创建certificate引用。

创建用于评估证书的策略。

用证书和策略创建信任对象(trust)。如果存在中间证书或者锚证书,应把这些证书都包含在certificate数组中并传递给SecTrustCreateWithCertificates函数。这样会加快评估的速度。

评估一个信任对象。

处理信任结果(trust result)。如果信任结果是kSecTrustResultInvalid,kSecTrustResultDeny,kSecTrustResultFatalTrustFailure,你无法进行处理。如果信任结果是kSecTrustResultRecoverableTrustFailure,你可以恢复这个错误。参考“从信任失败中恢复”。

释放策略对象。

 

从信任失败中恢复
信任评估的结果有多个,这取决于:是否证书链中的所有证书都能找到并全都有效,以及用户对这些证书的信任设置是什么。信任结果怎么处理则由你的程序来决定。例如,如果信任结果是kSecTrustResultConfirm,你可以显示一个对话框,询问用户是否允许继续。

信任结果kSecTrustResultRecoverableTrustFailure的意思是:信任被否决,但可以通过改变设置获得不同结果。例如,如果证书签发过期,你可以改变评估日期以判断是否证书是有效的同时文档是已签名的。列表3-4 演示如何改变评估日期。注意 CFDateCreate函数使用*对时间(从2001年1月1日以来的秒数)。你可以用CFGregorianDateGetAbsoluteTime函数把日历时间转换为*对时间。

列表 2-7  设置评估时间

SecTrustResultTypetrustResult;

status =SecTrustEvaluate(myTrust, &trustResult);       // 1

 

//Get time used to verify trust

CFAbsoluteTimetrustTime,currentTime,timeIncrement,newTime;

CFDateRef newDate;

if (trustResult ==kSecTrustResultRecoverableTrustFailure) {// 2

trustTime =SecTrustGetVerifyTime(myTrust);            // 3

timeIncrement = 31536000;                              // 4

currentTime =CFAbsoluteTimeGetCurrent();              // 5

newTime = currentTime -timeIncrement;                 // 6

if (trustTime -newTime){                              //7

newDate = CFDateCreate(NULL, newTime);             // 8

SecTrustSetVerifyDate(myTrust, newDate);           // 9

status = SecTrustEvaluate(myTrust, &trustResult);   // 10

}

}

if (trustResult != kSecTrustResultProceed){               // 11

}

在这段代码中:

评估证书可信度。参考“获取策略对象并评估可信度”。

检查信任评估结果是否是可恢复的失败(kSecTrustResultRecoverableTrustFailure)。

取得证书的评估时间(*对时间)。如果证书在评估时已经过期了,则被认为无效。

设置时间的递增量为1年(以秒计算)。

取得当前时间的*对时间。

设置新时间(第2次评估的时间)为当前时间减一年。

检查评估时间是否大于1年前(*近一次评估是否1年前进行的)。如果是,使用新时间(1年前的时间)进行评估,看证书是否在1年前就已经过期。

把新时间转换为CFDateRef。也可以用NSDate,二者是完全互通的,方法中的NSDate*参数,可以用CFDateRef进行传递;反之亦可。

设置信任评估时间为新时间(1年前)。

再次进行信任评估。如果证书是因为过期(到期时间在1年内)导致前次评估失败,那么这次评估应该成功。

再次检查评估结果。如果仍不成功,则需要做更进一步的操作,比如提示用户安装中间证书,或则友好地告知用户证书校验失败。

 

数据加密和解密
证书,密钥和信任API包含了生产不对称密钥对并用于数据加密/解密的函数集。例如,你可能想加密数据,这些数据的备份不能被访问。或者,你可能想在你的iOS应用和桌面应用间共享公钥/私钥对,以通过网络发送加密数据。列表2-8 显示如何产生可用于手机的公/私钥对。列表 2-9 显示如何用公钥加密数据,列表 2-10 显示如何用私钥解密数据。注意,这几个示例都使用了cocoa对象(如NSMutableDictionary),而本章其他示例使用了coreframework对象(如CFMutableDictionaryRef),二者是等价的。

 

列表 2-8  生成密钥对

static const UInt8 publicKeyIdentifier[] =”com.apple.sample.publickey/0″;

static const UInt8 privateKeyIdentifier[] =”com.apple.sample.privatekey/0″;

// 1

 

– (void)generateKeyPairPlease

{

OSStatus status = noErr;

NSMutableDictionary *privateKeyAttr =[[NSMutableDictionary alloc] init];

NSMutableDictionary *publicKeyAttr =[[NSMutableDictionary alloc] init];

NSMutableDictionary *keyPairAttr =[[NSMutableDictionary alloc] init];

//2

 

NSData * publicTag = [NSDatadataWithBytes:publicKeyIdentifier

length:strlen((const char *)publicKeyIdentifier)];

NSData * privateTag = [NSData dataWithBytes:privateKeyIdentifier

length:strlen((const char *)privateKeyIdentifier)];

// 3

 

SecKeyRef publicKey = NULL;

SecKeyRef privateKey = NULL;                               //4

 

[keyPairAttr setObject:(id)kSecAttrKeyTypeRSA

forKey:(id)kSecAttrKeyType]; // 5

[keyPairAttr setObject:[NSNumbernumberWithInt:1024]

forKey:(id)kSecAttrKeySizeInBits]; // 6

 

[privateKeyAttr setObject:[NSNumbernumberWithBool:YES]

forKey:(id)kSecAttrIsPermanent]; // 7

[privateKeyAttr setObject:privateTag

forKey:(id)kSecAttrApplicationTag]; // 8

 

[publicKeyAttr setObject:[NSNumbernumberWithBool:YES]

forKey:(id)kSecAttrIsPermanent]; // 9

[publicKeyAttr setObject:publicTag

forKey:(id)kSecAttrApplicationTag]; // 10

 

[keyPairAttr setObject:privateKeyAttr

forKey:(id)kSecPrivateKeyAttrs]; // 11

[keyPairAttr setObject:publicKeyAttr

forKey:(id)kSecPublicKeyAttrs]; // 12

 

status = SecKeyGeneratePair((CFDictionaryRef)keyPairAttr,

&publicKey, &privateKey); // 13

//    error handling…

 

 

if(privateKeyAttr) [privateKeyAttr release];

if(publicKeyAttr) [publicKeyAttr release];

if(keyPairAttr) [keyPairAttr release];

if(publicKey) CFRelease(publicKey);

if(privateKey) CFRelease(privateKey);                      // 14

}

在这段代码中:

定义公/私钥id的字符串变量,以便后面使用。

定义dictionary,用于传递SecKeyGeneratePair函数中的第1个参数。

把第1步中定义的字符串转换为NSData对象。

为公/私钥对准备SecKeyRef对象。

设置密钥对的密钥类型为RSA。

设置密钥对的密钥长度为1024。

设置私钥的持久化属性(即是否存入钥匙串)为YES。

把1-3步中的identifier放到私钥的dictionary中。

设置公钥的持久化属性(即是否存入钥匙串)为YES。

把1-3步中的identifier放到公钥的dictionary中。

把私钥的属性集(dictionary)加到密钥对的属性集(dictionary)中。

把公钥的属性集(dictionary)加到密钥对的属性集(dictionary)中。

产生密钥对。

释放无用对象。

你可以把公钥发送给任何人,他们可以用它来加密数据。假设你安全地保存了私钥,则只有你能解密这些数据。以下代码演示如何用公钥加密数据。可以用从设备上产生的公钥(见后面代码),或者从证书中提取的公钥(发送给你的证书或者已经在钥匙串中的证书)。用SecTrustCopyPublicKey函数可以从证书中提取公钥。下面假设这个密钥由设备产生并已放到钥匙串中。

列表 2-9  用公钥加密数据

– (void)encryptWithPublicKey

{

OSStatus status = noErr;

 

size_t cipherBufferSize;

uint8_t *cipherBuffer;                    // 1

 

// [cipherBufferSize]

const uint8_t nonce[] = “the quick brownfox jumps

over the lazy dog/0”; // 2

 

SecKeyRef publicKey = NULL;                                // 3

 

NSData * publicTag = [NSDatadataWithBytes:publicKeyIdentifier

length:strlen((const char *)publicKeyIdentifier)]; // 4

 

NSMutableDictionary *queryPublicKey =

[[NSMutableDictionary alloc] init]; // 5

 

[queryPublicKey setObject:(id)kSecClassKeyforKey:(id)kSecClass];

[queryPublicKey setObject:publicTagforKey:(id)kSecAttrApplicationTag];

[queryPublicKey setObject:(id)kSecAttrKeyTypeRSAforKey:(id)kSecAttrKeyType];

[queryPublicKey setObject:[NSNumbernumberWithBool:YES] forKey:(id)kSecReturnRef];

//6

 

status = SecItemCopyMatching

((CFDictionaryRef)queryPublicKey, (CFTypeRef*)&publicKey); // 7

 

// Allocate a buffer

 

cipherBufferSize = cipherBufferSize(publicKey);

cipherBuffer = malloc(cipherBufferSize);

 

// Error handling

 

if (cipherBufferSize < sizeof(nonce)) {

// Ordinarily, you wouldsplit the data up into blocks

// equal tocipherBufferSize, with the last block being

// shorter. Forsimplicity, this example assumes that

// the data is shortenough to fit.

printf(“Could notdecrypt.  Packet toolarge./n”);

return;

}

 

// Encrypt using the public.

status = SecKeyEncrypt(    publicKey,

kSecPaddingPKCS1,

nonce,

(size_t) sizeof(nonce)/sizeof(nonce[0]),

cipherBuffer,

&cipherBufferSize

);                             //8

 

// Error handling

// Store or transmit the encrypted text

 

if(publicKey) CFRelease(publicKey);

if(queryPublicKey) [queryPublicKeyrelease];               // 9

free(cipherBuffer);

}

在这段代码中:

定义缓存,用于放入加密文本。

指定要加密的文本。

定义SecKeyRef,用于公钥。

定义NSData对象,存储公钥的identifier(见列表 2-8的第1、3、8步),该id在钥匙串中唯一。

定义dictionary,用于从钥匙串中查找公钥。

设置dictionary的键-值属性。属性中指定,钥匙串条目类型为“密钥”,条目identifier为第4步中指定的字符串,密钥类型为RSA,函数调用结束返回查找到的条目引用。

调用SecItemCopyMatching函数进行查找。

加密数据, 返回结果用PKCS1格式对齐。

释放不用的变量。

The following code sample shows how todecrypt data. This sample uses the private key corresponding to the public keyused to encrypt the data, and assumes you already have the cipher text createdin the preceding example. It gets the private key from the keychain using thesame technique as used in the preceding example to get the public key.

下面代码演示如何解密。本例采用与加密数据的公钥对的私钥进行解密,并且密文为上面例子中的加密结果。从钥匙串中获取私钥,采用与上例相同的技术。

 

列表 2-10  用私钥解密

– (void)decryptWithPrivateKey

{

OSStatus status = noErr;

 

size_t plainBufferSize;;

uint8_t *plainBuffer;

 

SecKeyRef privateKey = NULL;

 

NSData * privateTag = [NSDatadataWithBytes:privateKeyIdentifier

length:strlen((const char *)privateKeyIdentifier)];

 

NSMutableDictionary *queryPrivateKey = [[NSMutableDictionaryalloc] init];

 

// Set the private key query dictionary.

[queryPrivateKey setObject:(id)kSecClassKeyforKey:(id)kSecClass];

[queryPrivateKey setObject:privateTagforKey:(id)kSecAttrApplicationTag];

[queryPrivateKeysetObject:(id)kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];

[queryPrivateKey setObject:[NSNumbernumberWithBool:YES] forKey:(id)kSecReturnRef];

// 1

 

status = SecItemCopyMatching

((CFDictionaryRef)queryPrivateKey, (CFTypeRef *)&privateKey); // 2

 

if (plainBufferSize < cipherBufferSize) {

// Ordinarily, you wouldsplit the data up into blocks

// equal toplainBufferSize, with the last block being

// shorter. Forsimplicity, this example assumes that

// the data is shortenough to fit.

printf(“Could notdecrypt.  Packet toolarge./n”);

return;

}

 

// Allocate the buffer

plainBufferSize =SecKeyGetBlockSize(privateKey);

plainBuffer = malloc(plainBufferSize)

 

// Error handling

 

status = SecKeyDecrypt(    privateKey,

kSecPaddingPKCS1,

cipherBuffer,

cipherBufferSize,

plainBuffer,

&plainBufferSize

);                             // 3

 

// Error handling

// Store or display the decrypted text

 

if(publicKey) CFRelease(publicKey);

if(privateKey) CFRelease(privateKey);

if(queryPublicKey) [queryPublicKey release];

if(queryPrivateKey) [queryPrivateKeyrelease];             // 4

}

在这段代码中:

准备dictionary,用于从钥匙串查找私钥。

在钥匙串中找到私钥。

解密数据。

释放无用的变量。

 

浏览器端的AJAX缓存机制

AJAX的缓存是由浏览器维持的,对于发向服务器的某个url,ajax仅在*次请求时与服务器交互信息,之后的请求中,ajax不再向服务器提交请求,而是直接从缓存中提取数据。
有些情况下,我们需要每一次都从服务器得到更新后数据。思路是让每次请求的url都不同,而又不影响正常应用:在url之后加入随机内容。
e.g.

1
url=url+ "&" +Math.random();

Key points:
1.每次请求的url都不一样(ajax的缓存便不起作用)
2.不影响正常应用(*基本的)

这里我们由两条结论:

1:Ajax的缓存和HTTP的缓存是一样的
现代浏览器的HTTP和缓存机制比Ajax的XMLHttpRequest对象要差很多,所以它不认识也不关心Ajax请求.它仅仅是遵循普通的HTTP缓存规则,通过服务器返回的响应头来进行缓存.
如果你已经对 HTTP缓存 有了解,那么你可以把HTTP缓存的知识用对Ajax缓存的理解上. 他们只有一点不同的,就是设置响应头的方式会和普通文件不一样.
下面这些响应头可以让你的Ajax可缓存:
Expires: 这一项应该被设置成未来的某个合适的时间点,时间点的设置取决于内容变动的频繁程度.举个栗子,如果请求的是个库存数量,那么Expires的值可以是10秒以后.如果请求的是一个相片,那么Expires的值就可以久一点,因为它不会经常变动.Expires头可以让浏览器在一段时间内重用本地缓存数据,从而避免任何不必要的与服务器数据交互.
Last-Modified: 设置这一项是一个很好的选择,通过它,浏览器在发送条件性GET请求的时候会使用请求头里的 If-Modified-Since 来检查本地缓存的内容.如果数据不需要更新,服务器会返回304响应状态.
Cache-Control: 在合适的情况下,这个值应该被设置为 Public ,这样所有的中间代理和缓存都可以被保存并且与其他用户共享内容.在火狐里,它还支持HTTPS请求的缓存
当然,如果你使用POST方式发送Ajax是不能缓存的,因为POST请求永远不会被缓存.如果你的Ajax请求会产生其他作用(比如银行账户之间的转账),请使用POST请求.
我们设置了一个demo(这个demo已经不能看了ヽ(≧□≦)ノ)来阐明这些头信息是如何工作的. 在HttpWatch里,你可以看到我们在响应头信息里设置了以上三个响应头

2016621174746971.png (565×227)

如果你规律的点击 ‘Ajax Update’ 按钮,时间的改变会趋向于每隔一分钟一次.因为Expires响应头被设置为未来的一分钟.在下面这张截图里你可以看到:重复的点击更新按钮时,Ajax请求会读取浏览器本地的缓存而不会产生网络活动(发送和传输栏的值都是0)

2016621174823835.png (592×155)

*后一次1:06.531时刻的点击发送的Ajax请求产生了网络数据传输,因为缓存的数据已经超过了一分钟. 服务器返回200响应状态表示获取到了一份新的数据.
猜测这个demo应该是一个按钮,每点击一次获取一次当前时间然后回现在页面上.

2:IE浏览器在Expires时间过期之前不会刷新通过Ajax获取的内容.
有些时候,Ajax在页面加载的时候就被用来填充页面的某些部分(比如一个价格列表).它并不是通过用户的某个事件(比如点击某个按钮)触发的,而是在页面加载的时候就通过javascript来发送的.就好像Ajax请求和那些嵌入资源(比如js和css)是一样的.
如果你开发这样的页面,在刷新它的时候,很可能想要更新嵌入的Ajax请求内容.对于嵌入资源(CSS文件,图片等),浏览器会通过用户刷新的方式是F5(刷新)还是Ctrl+F5(强制刷新)来自动发送下列不同类型的请求:
1.F5(刷新): 如果请求内容带有 Last-Modified 响应头,那么浏览器会发送条件性更新请求. 它使用 If-Modified-Since 请求头进行比较,这样服务器就可以返回304状态来避免传输不必要的数据.
2.Ctrl+F5(强制刷新): 告诉浏览器发送无条件更新请求,请求头的 Cache-Control 被设置为‘no-cache’.这告诉所有的中间代理和缓存:浏览器需要获取*新的版本,无论它是否已经被缓存.
Firefox把这个刷新的方式传播到了那些在页面加载的时候就发送的Ajax请求上,把这些Ajax请求当成嵌入资源来处理.下面是HttpWatch在火狐下的截图,显示了Ajax Caching demo(这个demo已经不能看了ヽ(≧□≦)ノ)刷新(F5)页面时Ajax请求的效果:

2016621175010316.png (595×282)

火狐确保Ajax发起的请求是条件性的.在这个例子里,如果缓存数据不到10秒,服务器返回304,超过10秒,服务器返回200,重新传送数据.
在ie里,加载页面时就发起的Ajax请求被看做是和页面其他部分刷新毫无关系的,也不会被用户的刷新方式所左右.如果缓存的ajax数据没有过期,就不会有GET请求发送到服务器.它会直接从缓存里读取数据,从HttpWatch里看就是(Cache)结果.下面这个图是在ie下缓存没有过期的情况下按F5刷新:

2016621175032362.png (600×147)

就算是通过 Ctrl+F5 强制刷新,通过Ajax获取的数据也是从缓存里读取:

2016621175400871.png (583×130)

这就意味着,任何通过Ajax得到的内容如果没有过期,在ie下都不会被更新 – 即使你使用Ctrl+F5强制刷新. 唯一能确保你获取*新数据的方法就是手动清楚缓存. 可以使用HttpWatch的工具栏:

2016621175426365.png (587×258)

注意,Cache结果和304结果是不同的.Cache其实是200(cache),304就是304.Cache其实没有向服务器发送请求,可以从chrome里看到,它的耗时是0,response也是空.而304不同,
304请求是浏览器发起了一个条件性的请求,这个请求携带了 If-Modified-Since 请求头,如果这个文件在浏览器发送的这个时间之后没有修改过,服务器端就回返回一个304状态,告诉浏览器使用它本地的缓存内容.它没有Cache快,因为请求还是发送到了服务器端,只不过服务器端没有发送数据.
可以看下taobao首页,里面既有200(cache)也有304.可以查看他们的区别.

总结:

我们都知道,ajax能提高页面载入的速度的主要原因是通过ajax减少了重复数据的载入,真正做到按需获取,既然如此,我们在写ajax程序的时候不妨送佛送到西,在客户端再做一次缓存,进一步提高数据载入速度。那就是在载入数据的同时将数据缓存在浏览器内存中,一旦数据被载入,只要页面未刷新,该数据就永远的缓存在内存中,当用户再次查看该数据时,则不需要从服务器上去获取数据,*大的降低了服务器的负载和提高了用户的体验。

常用的消息摘要算法

天偶然的学习了一下几种关于消息摘要算法的知识。个人觉得很好。应着老话“好记性不如烂笔头”,我就码了几行代码咯。


算法嘛,没什么好说的了。毕竟是设计者智慧与汗水的结晶,也是时代进步的推动力。我们能做的就是将这种算法运用得当,造福全人类,就行了!

//格外需要注意的是采用CC(Commons codec)方式生成消息摘要时,一定要记得导入相关的jar包哦!
  • 1
  • 1

MD方式


  1. package MD;
  2. /**
  3. * MD:MessageDigest—-消息摘要算法
  4. */
  5. import java.security.MessageDigest;
  6. import org.apache.commons.codec.binary.Hex;
  7. import org.apache.commons.codec.digest.DigestUtils;
  8. import org.apache.commons.codec.digest.Md5Crypt;
  9. import org.junit.Test;
  10. /**
  11. * 简单的使用MessageDigest实现消息摘要的小案例
  12. * 收获:
  13. * 使用摘要算法获得字节数组无法直接输出,需要转成相应的十六进制才能获得结果
  14. * @author Summer
  15. *
  16. */
  17. public class Demo {
  18. private static String targetString = “I am Summer!”;
  19. /**
  20. * 测试MD5算法加密效果,转成了十六进制
  21. * JDK实现
  22. * @throws Exception
  23. */
  24. @Test
  25. public void test1() throws Exception {
  26. MessageDigest md = MessageDigest.getInstance(“MD5”);
  27. byte [] bytes = md.digest(targetString.getBytes());
  28. String result = Hex.encodeHexString(bytes);
  29. System.out.println(“MD5加密后的串是:”+result);
  30. //5b704caf20c179cdf61d7121e59dcd76
  31. }
  32. /**
  33. * 测试MD2算法加密效果,转成了十六进制
  34. * JDK实现
  35. * @throws Exception
  36. */
  37. @Test
  38. public void test2() throws Exception {
  39. MessageDigest md = MessageDigest.getInstance(“MD2”);
  40. byte [] bytes = md.digest(targetString.getBytes());
  41. String result = Hex.encodeHexString(bytes);
  42. System.out.println(“MD5加密后的串是:”+result);
  43. //387992acc0a756abc7026d635383b0a7
  44. }
  45. /**
  46. * 测试MD2算法加密效果,不转成十六进制
  47. * JDK实现
  48. * @throws Exception
  49. */
  50. @Test
  51. public void test3() throws Exception {
  52. MessageDigest md = MessageDigest.getInstance(“MD2”);
  53. byte [] bytes = md.digest(targetString.getBytes());
  54. System.out.println(bytes);
  55. }
  56. /**
  57. * 测试MD5算法加密效果,不转成十六进制
  58. * JDK实现
  59. * @throws Exception
  60. */
  61. @Test
  62. public void test4() throws Exception {
  63. MessageDigest md = MessageDigest.getInstance(“MD5”);
  64. byte [] bytes = md.digest(targetString.getBytes());
  65. System.out.println(bytes.toString());
  66. }
  67. /**
  68. * 使用commons codec的方式对目标字符串进行加密MD2,并使用十六进制进行输出
  69. * @throws Exception
  70. */
  71. @Test
  72. public void test5() throws Exception {
  73. MessageDigest md = DigestUtils.getMd2Digest();
  74. byte[] bytes = md.digest(targetString.getBytes());
  75. String result = Hex.encodeHexString(bytes);
  76. System.out.println(“Commons codec 方式加密MD2:”+ result);
  77. //387992acc0a756abc7026d635383b0a7
  78. }
  79. /**
  80. * 使用commons codec的方式对目标字符串进行加密MD5,并使用十六进制进行输出
  81. * 对比方法发现,使用CC这个开源方式,可以大大的简化操作。但其底层仍是JDK实现的,cc知识做了一些简化,仅此而已
  82. * @throws Exception
  83. */
  84. @Test
  85. public void test6() throws Exception {
  86. MessageDigest md = DigestUtils.getMd5Digest();
  87. byte[] bytes = md.digest(targetString.getBytes());
  88. String result = Hex.encodeHexString(bytes);
  89. System.out.println(“Commons codec 方式加密MD5:”+ result);
  90. //5b704caf20c179cdf61d7121e59dcd76
  91. System.out.println(DigestUtils.md5Hex(targetString));
  92. //5b704caf20c179cdf61d7121e59dcd76
  93. }
  94. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108

SHA方式


  1. package SHA;
  2. /**
  3. * SHA: Security Hash Algorithm —–安全哈希算法
  4. */
  5. import java.security.MessageDigest;
  6. import org.apache.commons.codec.binary.Hex;
  7. import org.junit.Test;
  8. public class Demo {
  9. private static String targetString = “I am Summer!”;
  10. /**
  11. * 使用JDK方式以SHA1的方式实现消息摘要算法
  12. * @throws Exception
  13. */
  14. @Test
  15. public void testSHA1() throws Exception {
  16. /*方式一:
  17. * //算法名称对于SHA1方式: 可以是SHA也可以是SHA1
  18. MessageDigest md = MessageDigest.getInstance(“SHA”);
  19. byte[] bytes = md.digest(targetString.getBytes());
  20. String result = Hex.encodeHexString(bytes);
  21. System.out.println(“JDK 方式实现SHA1消息摘要的加密:\t”+ result);
  22. //a621ca634410c2521ef560736a6a9da048f42961
  23. */
  24. /*
  25. * 方式二:
  26. */
  27. MessageDigest md = MessageDigest.getInstance(“SHA”);
  28. md.update(targetString.getBytes());
  29. String result = Hex.encodeHexString(md.digest());
  30. System.out.println(result);
  31. }
  32. /**
  33. * 使用JDK方式以SHA-224的方式实现消息摘要算法
  34. * @throws Exception
  35. */
  36. @Test
  37. public void testSHA224() throws Exception {
  38. MessageDigest md = MessageDigest.getInstance(“SHA-224”);
  39. md.update(targetString.getBytes());
  40. String result = Hex.encodeHexString(md.digest());
  41. System.out.println(result);
  42. //6fb07533ef05a1f1b6a0fbd25f08ed3cbcf17807a507c0224756f06d
  43. }
  44. /**
  45. * 使用JDK方式以SHA-384的方式实现消息摘要算法
  46. * @throws Exception
  47. */
  48. @Test
  49. public void testSHA384() throws Exception {
  50. MessageDigest md = MessageDigest.getInstance(“SHA-384”);
  51. md.update(targetString.getBytes());
  52. String result = Hex.encodeHexString(md.digest());
  53. System.out.println(result);
  54. //c640d1c73a8e2078b290f284fce59da103ecfc1c3c9442cc90ebd5f08900d8e6f19551da7b2f213d96dd055bd2759698
  55. }
  56. /**
  57. * 使用JDK方式以SHA-256的方式实现消息摘要算法
  58. * @throws Exception
  59. */
  60. @Test
  61. public void testSHA256() throws Exception {
  62. MessageDigest md = MessageDigest.getInstance(“SHA-256”);
  63. md.update(targetString.getBytes());
  64. String result = Hex.encodeHexString(md.digest());
  65. System.out.println(result);
  66. //8305809696717b31c6765b7ba89cfd67c17ef62c79a700ae4e305fee5ebdf457
  67. }
  68. /**
  69. * 使用JDK方式以SHA-512的方式实现消息摘要算法
  70. * @throws Exception
  71. */
  72. @Test
  73. public void testSHA512() throws Exception {
  74. MessageDigest md = MessageDigest.getInstance(“SHA-512”);
  75. md.update(targetString.getBytes());
  76. String result = Hex.encodeHexString(md.digest());
  77. System.out.println(result);
  78. //520abdfdc5e0ac43c795bee9da1cac3fa2f55b8e89f1d33f9a4b83367f4b74cd5d42bfa2c6d4f68c362e64d44ea664244c03c7fd2b7bc167a489fe7129c91156
  79. }
  80. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107

MAC方式


  1. package MAC;
  2. import javax.crypto.KeyGenerator;
  3. import javax.crypto.Mac;
  4. import javax.crypto.SecretKey;
  5. import javax.crypto.spec.SecretKeySpec;
  6. import org.apache.commons.codec.binary.Hex;
  7. import org.junit.Test;
  8. /**
  9. * MAC: Message Authentication Code ——消息验证码
  10. */
  11. public class Demo {
  12. private static String targetString = “I am Summer!”;
  13. /**
  14. * 使用MAC算法以MD5方式加密
  15. * @throws Exception
  16. */
  17. @Test
  18. public void hmacMD5() throws Exception {
  19. //初始化KeyGeerator
  20. KeyGenerator keyGenerator = KeyGenerator.getInstance(“HmacMD5”);
  21. //产生密钥
  22. SecretKey secretKey = keyGenerator.generateKey();
  23. //获得密钥
  24. byte[] key = secretKey.getEncoded();
  25. key = Hex.decodeHex(new char[]{‘S’,‘u’,‘m’,‘m’,‘e’,‘r’,‘!’});
  26. //还原密钥
  27. SecretKey restoreSecretKey = new SecretKeySpec(key, “HmacMD5”);
  28. //实例化Mac
  29. Mac mac = Mac.getInstance(restoreSecretKey.getAlgorithm());
  30. //初始化Mac
  31. mac.init(restoreSecretKey);
  32. byte[] hmacMD5Bytes = mac.doFinal(targetString.getBytes());
  33. //转成十六进制并进行输出
  34. String result = Hex.encodeHexString(hmacMD5Bytes);
  35. System.out.println(result);
  36. //8371828a9e53f04977b4d2ceb73ff506
  37. }
  38. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

小总结


对于MAC方式,其原理其实和前两个差不多,只不过是加入了key加密的权限。详见代码哦!