消息认证码(MAC): 可以检测到负载是否被修改并验证其真实性。实现方式是对进来的请求数据(或是预先设定好的请求数据的子集)生成哈希值,然后将哈希值与随负载一同发送的预先计算好的 MAC 进行对比。 MAC 类似于之前介绍的哈希函数, 但却更加安全, 这是因为它们总是与一个密钥配对。应用会计算随请求发送的 MAC 值。 接下来, 将进来的 MAC 与服务层使用相同密钥和数据集计算出的 MAC 进行比较。 如果两个 MAC 不同, 我们就认为消息被修改了。 另一种方式是生成密文的 MAC。 虽然结果都是一样的, 不过这样可以在执行代价比较高昂的解密处理前判断消息是否已经被修改过
虽然还有其他的 MAC 算法, 不过本节介绍的示例专注于基于哈希的消息认证码(HMAC), 这是因为 HMAC 是 iOS 和大多数服务层平台原生支持的。 HMAC 也经常被称作密钥消息认证码, 它是由 RFC 2104(http://tools.ietf.org/html/rfc2104) 定义的。 HMAC 可以使用任何哈希函数, 通常会使用 MD5 或 SHA-1, 不过其功能主要依赖于底层哈希函数的功能与密钥。 虽然 MD5 哈希算法存在一些弱点, SHA-1 的加密性更强一些, 不过这并不会对它们在 HMAC 中的使用造成影响
iOS HMAC 实现支持 MD5、SHA-1、SHA-224、SHA-256、SHA-384 及 SHA-512 摘要算法。 HMAC 的输出长度总是与使用的哈希算法的摘要长度一样。 与之前介绍的其他哈希函数一样, HMAC 既可以手工生成, 也可以使用便捷方法生成, 代码清单 6-7 演示了如何使用便捷方法生成 HMAC
HMAC 示例基于之前创建的 NSString+Hashing 类别构建。 首先, 该类别还需要方法定义并导入两个库, 如代码清单 6-7 所示
导入 CommonCryptor 似乎没有必要, 不过需要在 hmacWithKey: 的实现中访问密钥长度常量 kCCKeySizeAES256, 如代码清单 6-8 所示。
代码清单 6-8 包含 hmacWithKey: 的实现, 它类似于之前介绍过的 hashWithType: 方法。 不过有两个差别, 分别是增加了密钥以及只能使用 SHA-256 摘要算法的限制。 密钥与输入数据的创建采用 UTF8 编码, 然后传给 CCHmac() 函数来创建 HMAC。 该函数会使用 HMAC 值来装配缓存, 然后 hmacWithKey: 以字符串的形式将其返回
你可能想知道密钥的值来自哪里, 是如何选择的。 密钥对于密码安全来说是必不可少的, 密钥长度的重要性怎么强调也不过分! 如果缺乏安全且随机的密钥, 应用就会暴露给各种攻击! 根据需要保护的数据的不同, 你可能还会受到可能的诉讼问题, 比如数据外泄或是违反美国健康保险流通与责任法案(HIPAA)等。 保护好密钥是确保传输数据安全的唯一保障措施
由于密钥是安全模型的关键所在, 因此必须好好选择。 密钥的最终长度应该等于加密算法或 HMAC 的密钥长度, 短于算法密钥长度的都需要补 NULL, 直到等于算法密钥的长度为止, 这会削弱缺少的那些字符的随机性。 如果必须将用户的输入数据作为密钥的基础, 那么可以在输入前追加随机或伪随机的值, 这叫做加盐。 如果需要再生成这个密钥, 那么确保将用于生成最终密钥值的盐也存储起来。 最后, 使用之前介绍过的算法(如 MD5 或 SHA-1 等)运行几千次哈希计算来得到加盐后的值, 然后去除一定的字节数作为最终使用的密钥
如果应用要求以用户输入作为密钥的基础, 那么可以考虑使用 CommonCrypto/CommonKeyDerivation 库的 CCKeyDerivationPBKDF() 函数。 CCKeyDerivationPBKDF() 会返回盐的密钥值、推导算法、推导次数以及用户指定的输出密钥长度。 还可以通过 SecRandomCopyBytes() 函数生成随机的字节数组
虽然对于开发者来说, 将各种处理细节输出到日志中是种很常见的做法, 不过绝不应该将生成的密钥打印到控制台。 日志文件可以非常容易地从设备上获取, 如果被攻击者发现, 那就是非常严重的安全问题
共享密钥的缺点在于应用需要规划好密钥版本。 不可避免地, 你会遇到必须修改共享密钥的情况。 这就要求部署应用的新版本, 然后更新服务层。 然而, iOS 用户并不总是会安装应用更新, 也没有任何机制可以强制用户这么做。 对于使用旧版本应用的用户来说, 该怎么处理呢? 需要确保来自旧版本的用户交易也能被正确地解密、 验证并被服务处理。 密钥版本化可以在不发布应用更新的情况下解决这个问题: 不过, 要从一开始就将其放到应用的开发当中。
该例将资金转移负载的子集作为 MAC; 不过, 也可以使用整个负载, MAC 的拼接顺序与属性(即想要哈希的值) 必须与服务层共享, 这样才能确保解密的正确性, 如果安谧端或服务层在不同的假设下动作, 那么消息完整性检查就会失败, 什么都不会得到处理
既然已经传输了 MAC, 并且服务层也对负载进行了解密, 那么服务必须生成与之相伴的 MAC。 如下代码片段展示了如何使用与客户端相同的拼接输入字符串在 PHP 中生成 HMAC(PHP 函数 hash_hmac() 与 iOS 中的 HMac() 函数接收类似的输入, 并且可以指定所用的算法、哈希的内容以及要使用的密钥):
注意之前两个示例的输出值是一样的。 由于这两个值相同, 因此服务层可以相信接收到的是未被修改的请求, 并且可以安全地进行资金转移