密码学(三)

目录

    • Hash
    • HMAC
    • 用户登录流程安全性分析
    • Objective-C 中 NSString 的 Hash 分类

Hash

  • Hash 简介

    Hash(哈希 / 散列)算法:把任意长度的输入通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是说,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出。所以,不可能从散列值来唯一确定输入值。简单地说,哈希函数就是一种将任意长度的消息压缩到某一固定长度的 消息摘要 的函数。计算哈希值的常用方法有(关键字 K = 输入,散列地址 = 哈希地址 = 散列值 = 哈希值):

    1. 直接寻址法:取关键字或关键字的某个线性函数的值作为哈希地址
    2. 数字分析法:提取关键字中取值比较均匀的数字作为作为哈希地址
    3. 平方取中法:取关键字平方后的中间几位作为哈希地址
    4. 折叠法:将关键字分割成位数相同的几部分(最后一部分位数可以不同),然后取这几部分的叠加和(去除进位)作为哈希地址
    5. 除留余数法:用关键字除以某个不大于哈希表长度的数,将所得余数作为哈希地址
    6. 随机数法:选择一随机函数,随机选取关键字的值作为哈希地址,通常用于关键字长度不同的场合

    哈希碰撞:两个不同的输入,根据同一哈希函数计算出相同的哈希值的现象。衡量一个哈希函数的好坏的重要指标就是发生碰撞的概率以及发生碰撞的解决方案。任何哈希函数基本都无法彻底避免碰撞,常见的解决碰撞的方法有以下几种:

    1. 开放定址法:一旦发生了冲突,就去寻找下一个空的哈希地址,只要哈希表足够大,空的哈希地址总能找到,并将记录存入
    2. 链地址法:将哈希表的每个单元作为链表的头结点,所有哈希地址相同的元素构成一个同义词链表。即发生冲突时就把该关键字链在以该单元为头结点的链表的尾部
    3. 再哈希法:当哈希地址发生冲突时,用其他的哈希函数计算出另一个哈希地址,直到冲突不再产生为止
    4. 建立公共溢出区:将哈希表分为基本表和溢出表两部分,发生冲突的元素都放入溢出表中
  • 常见的 Hash 函数

    MD5(Message-Digest Algorithm 5):信息摘要算法,用于生成信息摘要,确保信息传输完整一致。曾广泛运用于信息安全领域,但是随着近年来密码学领域实质性的研究进展,使本算法不再适合当前的安全环境。目前,MD5 广泛应用于错误检查。例如,在一些 BitTorrent 下载中,软件通过计算 MD5 检验下载到的碎片的完整性。MD5 的输出结果长度为 128 bit(32 Hex,16 Byte)

    SHA(Secure Hash Algorithm):安全散列算法,用于生成信息摘要,确保信息传输完整一致。SHA 是一个算法系列的统称,包括以下几个大版本:

    • SHA - 1:输出结果长度为 160 bit(40 Hex,20 Byte),在 2005 年的时候,出现了 SHA - 1 的破解方法,因此 SHA - 1 也在逐渐被淘汰

    • SHA - 2 包括以下几个子算法:

      1. SHA - 256,输出结果长度为 256 bit(64 Hex,32 Byte)
      2. SHA - 224,SHA - 256 的阉割版,输出结果长度为 224 bit(56 Hex,28 Byte)
      3. SHA - 512,输出结果长度为 512 bit(128 Hex,64 Byte)
      4. SHA - 384,SHA - 512 的阉割版,输出结果长度为 384 bit(96 Hex,48 Byte)
    • SHA - 3:又叫 Keccak 算法,可以生成任意长度的哈希值,但为了配合 SHA - 2 的哈希值长度,SHA - 3 标准中规定了 SHA3 - 224、SHA3 - 256、SHA3 - 384、SHA3 - 512这 4 种版本。
      SHA - 3 并没有取代 SHA - 2 的趋势,因为就目前来说,SHA - 2 并没有被发现明显的缺点。SHA - 3 更多的是做为 SHA - 2 的补充。

      Question:为什么 SHA - 2 要有这么多的版本呢?只使用最长的一种不行吗?
      Answer:信息摘要越长,发生碰撞的几率就越低,破解的难度就越大。但同时,耗费的性能和占用的空间也就越高。 SHA - 2 有不同输出长度的子算法是为了适应不同的应用场景,从而对安全、性能、空间等因素做出权衡。比如说过我的需求仅仅是验证数据完整性,使用 SHA - 512 显然是浪费的。

  • Hash 的特点

    所有哈希函数都有以下基本特性:

    1. 根据同一哈希函数计算出的哈希值如果不同,那么输入值肯定不同
    2. 根据同一哈希函数计算出的哈希值如果相同,那么输入值不一定相同
    3. 根据同一哈希函数计算相同的输入值,所得的哈希值一定相同
    4. 无法通过哈希值反算出输入值(哈希函数是一个单向函数,不可逆)
    5. 对输入值非常敏感,即使输入值只修改了一个比特,最后得到的哈希值也大不相同
    6. 哈希碰撞的概率很小,对于不同的输入值,哈希值相同的概率非常小
    7. 哈希算法的执行效率一般较高,针对较长的输入,也能快速地计算出哈希值
    8. 常见的哈希函数的算法是公开的

    因此,Hash 算法可以看出是一种单向的加密算法。

  • Hash 的应用

    无论是 MD5 还是 SHA 系列算法,本质上都是:把任意长度的输入通过散列算法变换成固定长度的输出,即对信息进行摘要(信息摘要 有时也叫 信息指纹)。Hash 的应用,其实就是对 Hash 函数的特点进行扬长避短:

    1. 安全加密:日常用户密码加密通常使用的都是 MD5、SHA 等哈希函数,因为不可逆,而且微小的区别加密之后的结果差距很大,所以安全性更好。
    2. 唯一标识:比如 URL 字段或者图片字段要求不能重复,这个时候就可以通过对相应字段值做 MD5 处理,将字段的数据统一为 32 位长度,从数据库索引构建和检索查询角度看,经 MD5 处理过的字段,效果更好。此外,还可以对文件之类的二进制数据做 MD5 处理,作为唯一标识,这样判定重复文件的时候更快捷。
    3. 数据校验:比如,从网上下载的很多文件(尤其是 P2P 站点资源),都会包含一个 MD5 值,用于校验下载数据的完整性,避免数据在中途被劫持篡改。
    4. 散列函数:高级语言 中的 MD5、SHA1、Hash 等函数都是基于散列算法计算哈希值
    5. 负载均衡:对于同一个客户端上的请求,尤其是已登录用户的请求,需要将其会话请求都路由到同一台机器,以保证数据的一致性,这可以借助哈希算法来实现,通过用户 ID 尾号对总机器数取模(取多少位可以根据机器数定),将结果值作为机器编号
    6. 分布式缓存:分布式缓存和其他机器或数据库的分布式不一样,因为每台机器存放的缓存数据不一致,每当缓存机器扩容时,需要对缓存存放机器进行重新索引(或者部分重新索引),这里应用到的也是哈希算法的思想
  • Hash 与 搜索引擎分词搜索

    Question:用户使用搜索引擎进行搜索时,不同用户拥有不同的搜索习惯。比如,当需要搜索2020年英雄联盟世界赛的信息时,以下分词的组合都是有可能的:2020 LOL 世界赛,LOL 世界赛 2020,LOL 2020 世界赛,… 。那么,搜索引擎是如何发现不同用户的同一搜索需求,进而提供相同的搜索结果的呢?

    Answer:对所有分词取 Hash 值,再将得到的所有 Hash 值相加,最终得到一个特征值。因为每个分词的 Hash 值是固定的,所以不论分词的组合如何变化,最终所有分词的 Hash 值相加的结果也是固定的。以对分词取 MD5 举例:
    MD5(2020) = 7B7A53E239400A13BD6BE6C91C4F6C4E
    MD5(LOL) = AEE4BD941F8B4D9E39210C06C44FCB71
    MD5(世界赛) = D594C223B8D03B1B85B62514286F2E06
    MD5(2020) + MD5(LOL) + MD5(世界赛) = 固定的特征结果

    注意:所有类型的 Hash 值,本质上都是一个固定长度的数值。如,MD5 是一个 128 bit 长的数值,SHA1 是一个 160 bit 长的数值 …

HMAC

  • HMAC 算法简介

    HMAC(Hash-based Message Authentication Code):哈希消息认证码,一种使用单向散列函数来构造消息认证码的算法。HMAC 以一个密钥和一个消息作为输入,生成一个消息摘要作为输出。HMAC 算法用数学公式表示如下:

    H  ⁣ M  ⁣ A  ⁣ C ( K , M ) = H (   ( K ′ ⊕ o p a d )   ∣   H (   ( K ′ ⊕ i p a d )   ∣   M )   ) H\!M\!A\!C(K,M) = H(~(K'⊕opad)~∣~H(~(K'⊕ipad)~∣~M)~) HMAC(K,M)=H( (Kopad)  H( (Kipad)  M) )
    其中:
    K K K 表示秘钥(Key)
    M M M 表示消息(Message)
    K ′ K' K 表示被处理成 哈希函数分组长度 的秘钥
    i p a d ipad ipad 表示固定常数,值为 0x5C(1 Byte,二进制为:01011100)循环 哈希函数分组长度 所形成的比特序列,i 是 inner 的意思
    o p a d opad opad 表示固定常数,值为 0x36(1 Byte,二进制为:00110110)循环 哈希函数分组长度 所形成的比特序列,o 是 outer 的意思

  • HMAC 算法流程

    HMAC 中所使用的单向散列函数并不局限于一种,任何高强度的单向散列函数都可以被用于 HMAC,如果将来设计出的新的单向散列函数,也同样可以使用。如:
    HMAC + SHA1 = HMAC - SHA1
    HMAC + SHA224 = HMAC - SHA224
    HMAC + SHA256 = HMAC - SHA256
    HMAC + SHA384 = HMAC - SHA384
    HMAC + SHA512 = HMAC - SHA512

    这里以 HMAC - SHA1 举例说明 HMAC 的算法流程,其中 SHA1 的分组长度为 512 bit(64 Byte),输出结果为 160 bit(20 Byte):

    1. 密钥填充,将 K K K 转换为 K ′ K' K
      如果密钥 K K K 比单向散列函数分组长度要短,就需要在末尾填充0,直到其长度达到单向散列函数的分组长度为止
      如果密钥 K K K 比单向散列函数分组长度要长,则需要用单向散列函数求出密钥的散列值,然后将这个散列值用作为 K ′ K' K

    2. 将填充后的密钥 K ′ K' K i p a d ipad ipad 进行异或运算,得到比特序列 i p a d K  ⁣ e y ipadK\!ey ipadKey
      将填充后的密钥 K ′ K' K 与被称为 i p a d ipad ipad 的比特序列进行 XOR 运算
      i p a d ipad ipad 是将 00110110 这一比特序列不断循环反复直到达到散列函数分组长度所形成的比特序列( i p a d ipad ipad i i i 是 inner 的意思)
      XOR 运算所得到的值,就是一个和单向散列函数的分组长度相同,且和密钥相关的比特序列
      这里将这个比特序列称为 i p a d K  ⁣ e y ipadK\!ey ipadKey

    3. 组合 i p a d K  ⁣ e y ipadK\!ey ipadKey 与消息 M M M,也就是将 i p a d K  ⁣ e y ipadK\!ey ipadKey 附加在消息 M M M 的开头

    4. 将步骤 3 的结果输入单向散列函数,并计算出散列值

    5. 将填充后的密钥 K ′ K' K o p a d opad opad 进行异或运算,得到比特序列 o p a d K  ⁣ e y opadK\!ey opadKey
      将填充后的密钥 K ′ K' K 与被称为 o p a d opad opad 的比特序列进行 XOR 运算
      o p a d opad opad 是将 01011100 这一比特序列不断循环反复直到达到散列函数分组长度所形成的比特序列( o p a d opad opad o o o 是 outer 的意思)
      XOR 运算所得到的值,也是一个和单向散列函数的分组长度相同,且和密钥相关的比特序列
      这里将这个比特序列称为 o p a d K  ⁣ e y opadK\!ey opadKey

    6. 组合 o p a d K  ⁣ e y opadK\!ey opadKey 与步骤 4 得到的散列值,也就是将 o p a d K  ⁣ e y opadK\!ey opadKey 附加在步骤 4 得到的散列值的开头

    7. 将步骤 6 的结果输入单向散列函数,并计算出散列值,这个散列值就是最终的 HMAC 值

    HMAC - SHA1 的流程如下图所示:
    密码学(三)_第1张图片
    密码学(三)_第2张图片

  • Objective - C 中 HMAC 的使用

    #import 
    #import 
    
    -(void)HMAC_Demo {
        // 用户密码
        NSString* userPwd = @"123456";
        // HMAC 运算使用的 Key,一般从服务器获取,一个用户对应一个不同的 Key
        NSString* hmacKey = @"RandomString";
        // 转成 C 字符串
        const char* userPwdData = userPwd.UTF8String;
        const char* hmackeyData = hmacKey.UTF8String;
        // 开辟用于接收 HMAC 运算结果的缓冲区
        uint8_t buffer[CC_MD5_DIGEST_LENGTH];
        // 进行 HMAC 运算
        CCHmac(kCCHmacAlgSHA1, hmackeyData, strlen(hmackeyData), userPwdData, strlen(userPwdData), buffer);
        // 将 HMAC 运算结果转成 16 进制字符串
        NSMutableString* hmacStr = [NSMutableString string];
        for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
            [hmacStr appendFormat:@"%02x", buffer[i]];
        }
        // 打印结果
        NSLog(@"hmacStr = %@", hmacStr);
        /** 输出
         hmacStr = 318df6b14983a3f8dd29f7191c1e1b9b
         */
    }
    

用户登录流程安全性分析

  • 用户登录流程 && 注意点

    一般情况下,用户登录的业务流程为:

    1. 客户端 通过网络 发送账号密码给 服务器 进行验证
    2. 服务器 将验证结果 通过网络 返回给 客户端

    如下图所示:
    密码学(三)_第3张图片
    其中存在风险的节点有:

    1. 账号、密码在 Client 的输入与存储
    2. 账号、密码发送给 Server 时,需要在网络上传输;Server 的验证结果需要通过网络返回给 Client
    3. 账号、密码在 Server 上的存储

    关于 Client 端的风险:
    Client 直接暴露在外界,容易遭受逆向分析,需要做到,即使某一客户端被黑客攻陷,也只有被攻陷的客户端的安全受影响,没有被逆向攻陷的客户端,仍然是安全的。这是什么意思呢?比如,在做 Client 开发的时候,用户账号、密码的存储与传输,需要进行加密,但不能将加密的算法与加密的秘钥全部写死在客户端里面。即,虽然所有 Client 可以采用同一套加密算法与流程,但是每个 Client 加密算法的秘钥需要不同。这样子,即使黑客攻陷了某一个客户端,也只能获取到所有客户端的加密算法和被攻陷的客户端的秘钥,黑客不能获取到其他没有被攻陷的客户端的秘钥,因此其他没被攻陷的客户端相对来说是安全的。

    关于网络传输的风险:
    虽然涉及到用户隐私的数据请求,一般都使用 HTTPS 进行传输,但是不安全的网络环境下,HTTPS 中的内容,仍然可能被监听和拦截,从而进行重放攻击。因此,Client 和 Server 之间的 身份验证请求 和 身份验证结果,需要做到具有短暂的时效性。即黑客在拦截到 身份验证请求 或者 身份验证结果 的时候,如果不能在短时间内进行破解或者使用,身份验证请求 或者 身份验证结果 就会失效。

    关于 Server 端的风险:
    Server 保存着所有用户的账号、密码,需要做到即使 Server 的源码与数据库都遭到了泄漏,黑客也不能通过 Server 源码中的算法和数据库,反推出用户的明文密码。

    因此,我们在进行开发时,需要注意:

    1. 涉及到用户隐私的信息,尤其是像登录密码这样重要且敏感的信息,在 Client 或者 Server 上,都不能以任何形式明文保存。尤其是 Server ,保存了所有用户的账号、密码,更加需要注意。什么叫 任何形式的明文 呢?即,在加密算法,加密后的数据都暴露的情况下(甚至所有客户端和整个服务器都被黑客攻陷的情况下),黑客也不能通过加密算法和加密后的数据反推出密码明文。即要做到,由始至终,有且只有用户一个人知道密码的明文。
    2. Client 发送给 Server 的验证信息 和 Server 返回给 Client 的验证结果,具有短暂的时效。即使验证信息和验证结果在网络上被监听和拦截,黑客也不能在短暂的时间内,破解用户的账号密码或者拿到用户的登录权限。
  • 常见登录方法分析

    ① 直接使用 Hash 对登录密码进行不可逆加密:
    Client 直接将用户输入的密码进行 Hash 运算,将得到结果发送给 Server 进行验证。同时,Server 在用户注册的时候,数据库中保存的就是用户密码的 Hash 值,而不是密码本身。这样就算 Server 被攻克,用户的隐私信息也能起到一定的保护。
    这样做存在的隐患:

    1. 简单密码的 Hash 结果,可以通过一些 功能网站 反向查询得到 密码明文
    2. 无法防止重放攻击

    ② 为了防止对登录密码的 Hash 值进行反向查询,在方法 ① 的基础上 Client 可以对登录密码加盐后,再进行 Hash 运算。使用这种方式,对于反向查询来说就比较困难了,安全系数也相对较高,但是依然存在隐患:

    1. 这种方式将会加盐算法与盐的值写死在程序里面,今后要想替换是非常难的。如果盐被泄露了,那么整个项目将陷入被动
    2. 无法防止重放攻击

    ③ Client 使用 HMAC 计算登录密码的 Hash 值。用户的登录密码即为 HMAC 的 M M M,并且 Client 的 HMAC. K  ⁣ e y K\!ey Key 通过服务器获取,服务器上一个用户对应一个不同的 HMAC. K  ⁣ e y K\!ey Key 。这种加密方案,可以很好的保护用户的隐私信息。因为就算 Client 泄露了 K  ⁣ e y K\!ey Key 值,这个 K  ⁣ e y K\!ey Key 值也只是一个用户的,不会污染整个项目。再者,就算 Server 被黑客攻陷,Server 的加密算法与数据库全部遭到了泄露,因为每个用户使用的 HMAC. K  ⁣ e y K\!ey Key 都是不一样的,黑客要破解所有用户的账号、密码,成本会趋近于无穷大。其实,HMAC 算法当中的 K  ⁣ e y K\!ey Key 值,可以看成是一种特殊的盐值,此种做法可以很好地解决方法 ② 中,盐值和代码耦合度高的问题。但是这种做法,依然无法防止重放攻击。

    ④ 从 方法① 演变到 方法③ 的过程,虽然已经可以很好地保护用户的真实密码,但是 方法① ~ 方法③ 都有一个致命的缺陷:无论 Client 和 Server 如何复杂地对密码进行 Hash 运算,在用户不更改密码的情况下,任何时候,Client 发送给 Server 的数据请求中,用户密码都是一个不变的 Hash 值。此时,黑客只要拿到用户密码的 Hash 值,那么模拟用户登录,再简单不过了。

    重放攻击本质上针对的是没有时效性或者时效性很长的数据请求,要解决重放攻击,需要让数据请求变得具有较短的时效性,或者让数据请求只在当次有效

    为防止重放攻击,可以用拼接时间戳的方式对方法 ③ 的流程进行升级:

    关于注册:

    1. Client 进行用户注册请求时,Server 随机生成该用户对应的 HMAC.Key,Server 保存并返回 HMAC.Key 给 Client
    2. Client 接收到 Server 返回的 HMAC.Key 后,保存 HMAC.Key 并使用 HMAC.Key 对用户登录密码进行 HMAC 运算,Client 将 HMAC 的运算结果发送给 Server。
    3. Server 接收到 Client 发送的 HMAC 结果,进行保存,注册完成。
      密码学(三)_第4张图片

    关于登录:

    1. Client 获取本地的 HMAC.Key ,如果不存在 HMAC.Key ,则说明用户重装 Client 或者 在新设备上登录,启动获取 HMAC.Key 的流程,或者启动验证设备锁流程。如果存在 HMAC.Key ,则使用 HMAC.Key 对用户的登录密码进行 HMAC 运算。
    2. Client 对 HMAC 运算后的密码,拼接提前获取到的服务器时间戳(一般精确到分钟),再对拼接后的字符串进行哈希运算,Client 将哈希运算的结果发送给 Server 进行验证。
    3. Server 接收到 Client 发送过来的哈希运算结果 hash0,Server 取出存储在数据库中的 Client 密码的 HMAC 值,拼接当前分钟时间戳与上一分钟时间戳,得到字符串 str1、str2,Server 对字符串 str1、str2 做与 Client 相同的哈希运算,得到哈希运算的结果 hash1、hash2。
    4. Server 将 Client 发送过来的 hash0 与 Server 自己得到的 hash1、hash2 进行匹配,如果有一个匹配,则登录成功。
      密码学(三)_第5张图片

    注意点:

    1. 服务器验证用户登录权限的有效时间不一定是两分钟,可以是任意时间片。设服务器当前秒钟时间戳为:TimeStamp.Second,时间片的长度为:Scale,则当前服务器在指定时间片 Scale 下的时间戳 = TimeStamp.Second / Scale
    2. 在实际开发中,客户端往往不会只简单地往 HMAC(password, key) 后面拼接服务器时间戳,而是会有一套特殊的加盐流程。即特殊的构造盐的方式,本身也是一种加密手段

    ⑤ 其实,上面的方法 ④ 还是存在漏洞,即用户在注册时,Client 需要将 HMAC.Result 发送给 Server,如果黑客在用户注册时,监听到了 HMAC.Result,并且知道服务器拼接时间戳的规则,还是可以轻易拿到用户的登录权限。网络安全就是这么有趣,总是在道高一尺魔高一丈的对抗中,旧的漏洞不断被修复,新的漏洞被不断发现。我们在进行开发时,往往需要基于业务场景,在安全程度和开发效率上,做一个平衡。作为刚刚启程我们,需要保持严谨且谦卑的心态。

  • 一些细节

    大多数用户都有一个特点:不同平台,不同应用的账号、密码喜欢使用重复的。如果某款应用泄露了用户的手机号、账号、密码,那么很有可能,黑客会利用用户的手机号码加上密码,套出用户的支付信息,这种后果是非常严重的!

    在以前,很多的平台、应用,关于用户的密码,都会提供一个功能:找回密码。但是现在,找回密码 这个功能被越来越多的大型平台和应用所淡化,取而代之的是:重置密码。如果某个平台或者应用提供了找回密码的功能,那么就意味着用户的真实密码,会以某种形式存储在该平台或者用户的数据库里面,就现在的安全形式而言,这是一个非常危险的操作。

    在进行开发的时候,需要谨记两个原则:
    1.用户的隐私信息不允许在网络上明文传递
    2.用户的隐私信息不允许在本地(Client / Server)明文保存

    macOS、iOS 中,钥匙串采用的是 AES(高级密码标准) 加密。

Objective-C 中 NSString 的 Hash 分类

  • NSString+Hash.h

    #import 
    
    @interface NSString (Hash)
    
    #pragma mark - 散列函数
    // 计算 MD5 散列结果
    // 终端测试命令:
    // echo -n "string" | openssl dgst -md5
    // 提示:随着 MD5 碰撞生成器的出现,MD5 算法不应被用于任何软件完整性检查或代码签名的用途。
    // @return 32 个十六进制字符的 MD5 散列字符串
    -(NSString *)md5String;
    
    // 计算 SHA1 散列结果
    // 终端测试命令:
    // echo -n "string" | openssl dgst -sha1
    // @return 40 个十六进制字符的 SHA1 散列字符串
    -(NSString *)sha1String;
    
    // 计算 SHA256 散列结果
    // 终端测试命令:
    // echo -n "string" | openssl dgst -sha256
    // @return 64个十六进制字符的 SHA256 散列字符串
    -(NSString *)sha256String;
    
    // 计算 SHA512 散列结果
    // 终端测试命令:
    // echo -n "string" | openssl dgst -sha512
    // @return 128 个十六进制字符的 SHA512 散列字符串
    -(NSString *)sha512String;
    
    #pragma mark - HMAC 散列函数
    // 计算 HMAC MD5 散列结果
    // 终端测试命令:
    // echo -n "string" | openssl dgst -md5 -hmac "key"
    // @return 32 个十六进制字符的 HMAC MD5 散列字符串
    -(NSString *)hmacMD5StringWithKey:(NSString *)key;
    
    // 计算 HMAC SHA1 散列结果
    // 终端测试命令:
    // echo -n "string" | openssl dgst -sha1 -hmac "key"
    // @return 40 个十六进制字符的 HMAC SHA1 散列字符串
    -(NSString *)hmacSHA1StringWithKey:(NSString *)key;
    
    // 计算 HMAC SHA256 散列结果
    // 终端测试命令:
    // echo -n "string" | openssl dgst -sha256 -hmac "key"
    // @return 64 个十六进制字符的 HMAC SHA256 散列字符串
    -(NSString *)hmacSHA256StringWithKey:(NSString *)key;
    
    // 计算 HMAC SHA512 散列结果
    // 终端测试命令:
    // echo -n "string" | openssl dgst -sha512 -hmac "key"
    // @return 128 个十六进制字符的 HMAC SHA512 散列字符串
    -(NSString *)hmacSHA512StringWithKey:(NSString *)key;
    
    #pragma mark - 文件散列函数
    // 计算文件的 MD5 散列结果
    // 终端测试命令:
    // md5 file.dat
    // @return 32 个十六进制字符的 MD5 散列字符串
    -(NSString *)fileMD5Hash;
    
    // 计算文件的 SHA1 散列结果
    // 终端测试命令:
    // openssl dgst -sha1 file.dat
    // @return 40 个十六进制字符的 SHA1 散列字符串
    -(NSString *)fileSHA1Hash;
    
    // 计算文件的 SHA256 散列结果
    // 终端测试命令:
    // openssl dgst -sha256 file.dat
    // @return 64 个十六进制字符的 SHA256 散列字符串
    -(NSString *)fileSHA256Hash;
    
    // 计算文件的 SHA512 散列结果
    // 终端测试命令:
    // openssl dgst -sha512 file.dat
    // @return 128 个十六进制字符的 SHA512 散列字符串
    -(NSString *)fileSHA512Hash;	    
    @end
    
  • NSString+Hash.m

    #import "NSString+Hash.h"
    #import 
    
    @implementation NSString (Hash)
        
    #pragma mark - 散列函数
    -(NSString *)md5String {
        const char *str = self.UTF8String;
        uint8_t buffer[CC_MD5_DIGEST_LENGTH];
        
        CC_MD5(str, (CC_LONG)strlen(str), buffer);
        
        return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
    }
        
    -(NSString *)sha1String {
        const char *str = self.UTF8String;
        uint8_t buffer[CC_SHA1_DIGEST_LENGTH];
        
        CC_SHA1(str, (CC_LONG)strlen(str), buffer);
        
        return [self stringFromBytes:buffer length:CC_SHA1_DIGEST_LENGTH];
    }
        
    -(NSString *)sha256String {
        const char *str = self.UTF8String;
        uint8_t buffer[CC_SHA256_DIGEST_LENGTH];
        
        CC_SHA256(str, (CC_LONG)strlen(str), buffer);
        
        return [self stringFromBytes:buffer length:CC_SHA256_DIGEST_LENGTH];
    }
        
    -(NSString *)sha512String {
        const char *str = self.UTF8String;
        uint8_t buffer[CC_SHA512_DIGEST_LENGTH];
        
        CC_SHA512(str, (CC_LONG)strlen(str), buffer);
        
        return [self stringFromBytes:buffer length:CC_SHA512_DIGEST_LENGTH];
    }
        
    #pragma mark - HMAC 散列函数
    -(NSString *)hmacMD5StringWithKey:(NSString *)key {
        const char *keyData = key.UTF8String;
        const char *strData = self.UTF8String;
        uint8_t buffer[CC_MD5_DIGEST_LENGTH];
        
        CCHmac(kCCHmacAlgMD5, keyData, strlen(keyData), strData, strlen(strData), buffer);
        
        return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
    }
        
    -(NSString *)hmacSHA1StringWithKey:(NSString *)key {
        const char *keyData = key.UTF8String;
        const char *strData = self.UTF8String;
        uint8_t buffer[CC_SHA1_DIGEST_LENGTH];
        
        CCHmac(kCCHmacAlgSHA1, keyData, strlen(keyData), strData, strlen(strData), buffer);
        
        return [self stringFromBytes:buffer length:CC_SHA1_DIGEST_LENGTH];
    }
        
    -(NSString *)hmacSHA256StringWithKey:(NSString *)key {
        const char *keyData = key.UTF8String;
        const char *strData = self.UTF8String;
        uint8_t buffer[CC_SHA256_DIGEST_LENGTH];
        
        CCHmac(kCCHmacAlgSHA256, keyData, strlen(keyData), strData, strlen(strData), buffer);
        
        return [self stringFromBytes:buffer length:CC_SHA256_DIGEST_LENGTH];
    }
        
    -(NSString *)hmacSHA512StringWithKey:(NSString *)key {
        const char *keyData = key.UTF8String;
        const char *strData = self.UTF8String;
        uint8_t buffer[CC_SHA512_DIGEST_LENGTH];
        
        CCHmac(kCCHmacAlgSHA512, keyData, strlen(keyData), strData, strlen(strData), buffer);
        
        return [self stringFromBytes:buffer length:CC_SHA512_DIGEST_LENGTH];
    }
        
    #pragma mark - 文件散列函数
        
    #define FileHashDefaultChunkSizeForReadingData 4096
        
    -(NSString *)fileMD5Hash {
        NSFileHandle *fp = [NSFileHandle fileHandleForReadingAtPath:self];
        if (fp == nil) {
            return nil;
        }
        
        CC_MD5_CTX hashCtx;
        CC_MD5_Init(&hashCtx);
        
        while (YES) {
            @autoreleasepool {
                NSData *data = [fp readDataOfLength:FileHashDefaultChunkSizeForReadingData];
                
                CC_MD5_Update(&hashCtx, data.bytes, (CC_LONG)data.length);
                
                if (data.length == 0) {
                    break;
                }
            }
        }
        [fp closeFile];
        
        uint8_t buffer[CC_MD5_DIGEST_LENGTH];
        CC_MD5_Final(buffer, &hashCtx);
        
        return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
    }
        
    -(NSString *)fileSHA1Hash {
        NSFileHandle *fp = [NSFileHandle fileHandleForReadingAtPath:self];
        if (fp == nil) {
            return nil;
        }
        
        CC_SHA1_CTX hashCtx;
        CC_SHA1_Init(&hashCtx);
        
        while (YES) {
            @autoreleasepool {
                NSData *data = [fp readDataOfLength:FileHashDefaultChunkSizeForReadingData];
                
                CC_SHA1_Update(&hashCtx, data.bytes, (CC_LONG)data.length);
                
                if (data.length == 0) {
                    break;
                }
            }
        }
        [fp closeFile];
        
        uint8_t buffer[CC_SHA1_DIGEST_LENGTH];
        CC_SHA1_Final(buffer, &hashCtx);
        
        return [self stringFromBytes:buffer length:CC_SHA1_DIGEST_LENGTH];
    }
        
    -(NSString *)fileSHA256Hash {
        NSFileHandle *fp = [NSFileHandle fileHandleForReadingAtPath:self];
        if (fp == nil) {
            return nil;
        }
        
        CC_SHA256_CTX hashCtx;
        CC_SHA256_Init(&hashCtx);
        
        while (YES) {
            @autoreleasepool {
                NSData *data = [fp readDataOfLength:FileHashDefaultChunkSizeForReadingData];
                
                CC_SHA256_Update(&hashCtx, data.bytes, (CC_LONG)data.length);
                
                if (data.length == 0) {
                    break;
                }
            }
        }
        [fp closeFile];
        
        uint8_t buffer[CC_SHA256_DIGEST_LENGTH];
        CC_SHA256_Final(buffer, &hashCtx);
        
        return [self stringFromBytes:buffer length:CC_SHA256_DIGEST_LENGTH];
    }
        
    -(NSString *)fileSHA512Hash {
        NSFileHandle *fp = [NSFileHandle fileHandleForReadingAtPath:self];
        if (fp == nil) {
            return nil;
        }
        
        CC_SHA512_CTX hashCtx;
        CC_SHA512_Init(&hashCtx);
        
        while (YES) {
            @autoreleasepool {
                NSData *data = [fp readDataOfLength:FileHashDefaultChunkSizeForReadingData];
                
                CC_SHA512_Update(&hashCtx, data.bytes, (CC_LONG)data.length);
                
                if (data.length == 0) {
                    break;
                }
            }
        }
        [fp closeFile];
        
        uint8_t buffer[CC_SHA512_DIGEST_LENGTH];
        CC_SHA512_Final(buffer, &hashCtx);
        
        return [self stringFromBytes:buffer length:CC_SHA512_DIGEST_LENGTH];
    }
        
    #pragma mark - 助手方法
    // 返回二进制 Bytes 流的字符串表示形式
    // @param bytes  二进制 Bytes 数组
    // @param length 数组长度
    // @return 字符串表示形式
    -(NSString *)stringFromBytes:(uint8_t *)bytes length:(int)length {
        NSMutableString *strM = [NSMutableString string];
        
        for (int i = 0; i < length; i++) {
            [strM appendFormat:@"%02x", bytes[i]];
        }
        
        return [strM copy];
    }
        
    @end
    

你可能感兴趣的:(iOS,安全攻防)