IOS 逆向开发(二)密码学 HASH

2019-12-13  本文已影响0人  孔雨露

1. HASH算法简介

1.1 HASH是什么?

Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数

先举个例子。我们每个活在世上的人,为了能够参与各种社会活动,都需要一个用于识别自己的标志。也许你觉得名字或是身份证就足以代表你这个人,但是这种代表性非常脆弱,因为重名的人很多,身份证也可以伪造。最可靠的办法是把一个人的所有基因序列记录下来用来代表这个人,但显然,这样做并不实际。而指纹看上去是一种不错的选择,虽然一些专业组织仍然可以模拟某个人的指纹,但这种代价实在太高了。
而对于在互联网世界里传送的文件来说,如何标志一个文件的身份同样重要。比如说我们下载一个文件,文件的下载过程中会经过很多网络服务器、路由器的中转,如何保证这个文件就是我们所需要的呢?我们不可能去一一检测这个文件的每个字节,也不能简单地利用文件名、文件大小这些极容易伪装的信息,这时候,我们就需要一种指纹一样的标志来检查文件的可靠性,这种指纹就是我们现在所用的Hash算法(也叫散列算法)。

  1. 这种标志有何意义呢?
    之前文件下载过程就是一个很好的例子,事实上,现在大部分的网络部署和版本控制工具都在使用散列算法来保证文件可靠性。而另一方面,我们在进行文件系统同步、备份等工具时,使用散列算法来标志文件唯一性能帮助我们减少系统开销,这一点在很多云存储服务器中都有应用。
  2. 当然,作为一种指纹,散列算法最重要的用途在于给证书、文档、密码等高安全系数的内容添加加密保护。这一方面的用途主要是得益于散列算法的不可逆性,这种不可逆性体现在,你不仅不可能根据一段通过散列算法得到的指纹来获得原有的文件,也不可能简单地创造一个文件并让它的指纹与一段目标指纹相一致。散列算法的这种不可逆性维持着很多安全框架的运营,

1.2 Hash的特点

  1. 算法是公开的

  2. 对相同数据运算,得到的结果是一样的

  3. 对不同数据运算,如MD5得到的结果默认是128位,32个字符(16进制标识)。

  4. 没法逆运算

  5. 一个优秀的 hash 算法,将能实现:

  1. 正向快速:给定明文和 hash 算法,在有限时间和有限资源内能计算出 hash 值。
  2. 逆向困难:给定(若干) hash 值,在有限时间内很难(基本不可能)逆推出明文。
  3. 输入敏感:原始输入信息修改一点信息,产生的 hash 值看起来应该都有很大不同。
  4. 冲突避免:很难找到两段内容不同的明文,使得它们的 hash 值一致(发生冲突)。即对于任意两个不同的数据块,其hash值相同的可能性极小;对于一个给定的数据块,找到和它hash值相同的数据块极为困难。

1.3 Hash的作用

  1. 用户密码的加密
  2. 搜索引擎
  3. 版权
  4. 数字签名

在密码学中,hash算法的作用主要是用于消息摘要和签名,换句话说,它主要用于对整个消息的完整性进行校验。举个例子,我们登陆知乎的时候都需要输入密码,那么知乎如果明文保存这个密码,那么黑客就很容易窃取大家的密码来登陆,特别不安全。那么知乎就想到了一个方法,使用hash算法生成一个密码的签名,知乎后台只保存这个签名值。由于hash算法是不可逆的,那么黑客即便得到这个签名,也丝毫没有用处;而如果你在网站登陆界面上输入你的密码,那么知乎后台就会重新计算一下这个hash值,与网站中储存的原hash值进行比对,如果相同,证明你拥有这个账户的密码,那么就会允许你登陆。银行也是如此,银行是万万不敢保存用户密码的原文的,只会保存密码的hash值而而已。在这些应用场景里,对于抗碰撞和抗篡改能力要求极高,对速度的要求在其次。一个设计良好的hash算法,其抗碰撞能力是很高的。以MD5为例,其输出长度为128位,设计预期碰撞概率为2^128,这是一个极小极小的数字——而即便是在MD5被王小云教授破解之后,其碰撞概率上限也高达,也就是说,至少需要找次才能有1/2的概率来找到一个与目标文件相同的hash值。

1.4 Hash有哪些流行的算法

1.5 Hash算法的碰撞

这很容易理解,因为作为一种可用的散列算法,其位数一定是有限的,也就是说它能记录的文件是有限的——而文件数量是无限的,两个文件指纹发生碰撞的概率永远不会是零。

但这并不意味着散列算法就不能用了,因为凡事都要考虑代价,买光所有彩票去中一次头奖是毫无意义的。现代散列算法所存在的理由就是,它的不可逆性能在较大概率上得到实现,也就是说,发现碰撞的概率很小,这种碰撞能被利用的概率更小。
随意找到一组碰撞是有可能的,只要穷举就可以。散列算法得到的指纹位数是有限的,比如MD5算法指纹字长为128位,意味着只要我们穷举2^128次,就肯定能得到一组碰撞——当然,这个时间代价是难以想象的,而更重要的是,仅仅找到一组碰撞并没有什么实际意义。更有意义的是,如果我们已经有了一组指纹,能否找到一个原始文件,让它的散列计算结果等于这组指纹。如果这一点被实现,我们就可以很容易地篡改和伪造网络证书、密码等关键信息。
你也许已经听过MD5已经被破解的新闻——但事实上,即便是MD5这种已经过时的散列算法,也很难实现逆向运算。我们现在更多的还是依赖于海量字典来进行尝试,也就是通过已经知道的大量的文件——指纹对应关系,搜索某个指纹所对应的文件是否在数据库里存在。

1.6 MD5简介

  1. MD5的典型应用是对一段信息(Message)产生信息摘要(Message-Digest),以防止被篡改。比如,在UNIX下有很多软件在下载的时候都有一个文件名相同,文件扩展名为.md5的文件,在这个文件中通常只有一行文本,大致结构如:
    MD5 (tanajiya.tar.gz) = 0ca175b9c0f726a831d895e269332461
  2. 这就是tanajiya.tar.gz文件的数字签名。MD5将整个文件当作一个大文本信息,通过其不可逆的字符串变换算法,产生了这个唯一的MD5信息摘要。
  3. 大家都知道,地球上任何人都有自己独一无二的指纹,这常常成为公安机关鉴别罪犯身份最值得信赖的方法;与之类似,MD5就可以为任何文件(不管其大小、格式、数量)产生一个同样独一无二的“数字指纹”,如果任何人对文件名做了任何改动,其MD5值也就是对应的“数字指纹”都会发生变化。
  4. 我们常常在某些软件下载站点的某软件信息中看到其MD5值,它的作用就在于我们可以在下载该软件后,对下载回来的文件用专门的软件(如Windows MD5 Check等)做一次MD5校验,以确保我们获得的文件与该站点提供的文件为同一文件。利用MD5算法来进行文件校验的方案被大量应用到软件下载站、论坛数据库、系统文件安全等方面。
  5. MD5的典型应用是对一段Message(字节串)产生fingerprint(指纹),以防止被“篡改”。举个例子,你将一段话写在一个叫 readme.txt文件中,并对这个readme.txt产生一个MD5的值并记录在案,然后你可以传播这个文件给别人,别人如果修改了文件中的任何内容,你对这个文件重新计算MD5时就会发现(两个MD5值不相同)。如果再有一个第三方的认证机构,用MD5还可以防止文件作者的“抵赖”,这就是所谓的数字签名应用。
  6. MD5还广泛用于操作系统的登陆认证上,如Unix、各类BSD系统登录密码、数字签名等诸多方。如在UNIX系统中用户的密码是以MD5(或其它类似的算法)经Hash运算后存储在文件系统中。当用户登录的时候,系统把用户输入的密码进行MD5 Hash运算,然后再去和保存在文件系统中的MD5值进行比较,进而确定输入的密码是否正确。通过这样的步骤,系统在并不知道用户密码的明码的情况下就可以确定用户登录系统的合法性。这可以避免用户的密码被具有系统管理员权限的用户知道。MD5将任意长度的“字节串”映射为一个128bit的大整数,并且是通过该128bit反推原始字符串是困难的,换句话说就是,即使你看到源程序和算法描述,也无法将一个MD5的值变换回原始的字符串,从数学原理上说,是因为原始的字符串有无穷多个,这有点象不存在反函数的数学函数。所以,要遇到了md5密码的问题,比较好的办法是:你可以用这个系统中的md5()函数重新设一个密码,如admin,把生成的一串密码的Hash值覆盖原来的Hash值就行了。

1.6.1 MD5 加密

1.6.2 MD5 破解

2. 对称加密, 非对称加密, HMAC

2.1 HMAC

  1. 直接使用MD5
  2. MD5加盐
  3. HMAC加密方案

1、客户端填写一个账号,发给服务端验证。服务端返回一个随机数给客户端。
2、此随机数就是HMAC的key。客户端的密码使用这个key加密,然后把这个加密之后的hash值发个客户端。客户端保存这个key。
3、服务端保存客服端发送过来的hash值。这个hash值只会传输一次。在注册或换手机登录的情况下传输。
4、以后的每次登录验证都是用这个hash值加上服务端时间戳(精确到分),然后再hash一次,得到新的hash发送给服务端。服务端用它保存的hash值也加上服务端时间,然后再hash一次,用得到的hash和客户端发送过来的hash比对。比对这一分钟和上一分钟,只要有一个比对成功,就算成功。
5、有一种情况,客户端换了手机且开启了设备验证,向服务端要key。服务器会先向授权设备发起是否授权,授权通过发送key,授权不通过不发送key。

2.2 对称加密

对称加密(也叫私钥加密)指加密和解密使用相同密钥的加密算法。有时又叫传统密码算法,就是加密密钥能够从解密密钥中推算出来,同时解密密钥也可以从加密密钥中推算出来。而在大多数的对称算法中,加密密钥和解密密钥是相同的,所以也称这种加密算法为秘密密钥算法或单密钥算法。它要求发送方和接收方在安全通信之前,商定一个密钥。对称算法的安全性依赖于密钥,泄漏密钥就意味着任何人都可以对他们发送或接收的消息解密,所以密钥的保密性对通信性至关重要。

对称加密算法的特点是算法公开、计算量小、加密速度快、加密效率高

  1. 要求提供一条安全的渠道使通讯双方在首次通讯时协商一个共同的密钥。直接的面对面协商可能是不现实而且难于实施的,所以双方可能需要借助于邮件和电话等其它相对不够安全的手段来进行协商;
  2. 密钥的数目难于管理。因为对于每一个合作者都需要使用不同的密钥,很难适应开放社会中大量的信息交流;
  3. 对称加密算法一般不能提供信息完整性的鉴别。它无法验证发送者和接受者的身份;
  4. 对称密钥的管理和分发工作是一件具有潜在危险的和烦琐的过程。对称加密是基于共同保守秘密来实现的,采用对称加密技术的贸易双方必须保证采用的是相同的密钥,保证彼此密钥的交换是安全可靠的,同时还要设定防止密钥泄密和更改密钥的程序。

2.2.1 常用的对称加密算法

对称加密算法的优点在于加解密的高速度和使用长密钥时的难破解性。假设两个用户需要使用对称加密方法加密然后交换数据,则用户最少需要2个密钥并交换使用,如果企业内用户有n个,则整个企业共需要n×(n-1) 个密钥,密钥的生成和分发将成为企业信息部门的恶梦。对称加密算法的安全性取决于加密密钥的保存情况,但要求企业中每一个持有密钥的人都保守秘密是不可能的,他们通常会有意无意的把密钥泄漏出去--如果一个用户使用的密钥被入侵者所获得,入侵者便可以读取该用户密钥加密的所有文档,如果整个企业共用一个加密密钥,那整个企业文档的保密性便无从谈起。

  1. DES对64位的明文分组M进行操作,M经过一个初始置换IP置换成m0,将m0明文分成左半部分和右半部分m0=(L0,R0),各32位长。然后进行16轮完全相同的运算,这些运算称为函数f,在运算过程中,数据与密匙结合。经过16轮运算之后,可以看到第16轮运算,将右侧第15轮运算的结果(R15)作为左侧运算的最终结果(L16),而右侧最后的结果(R16)为左侧第15轮运算结果(L15)和函数f运算结果的异或运算所得。此后,再将左、右部分合在一起经过一个逆置换,输出密文。
  2. 实际加密过程要分成两个同时进行的过程,即加密过程和密钥生成过程:


    加密过程
  3. 在16轮循环的每一轮中,密匙位移位,然后再从密匙的64位中选出48位。通过一个扩展置换将数据的右半部分扩展成48位,并通过一个异或操作替代成新的32位数据,在将其置换一次。这四步运算构成了图6-2中的函数f。然后,通过另一个异或运算,函数f的输出与左半部分结合,其结果成为新的右半部分,原来的右半部分成为新的左半部分。该操作重复16次。
  4. DES算法的解密过程和加密过程几乎完全相同,只是使用密钥的顺序相反。
  5. NIST(National Institute of Standards and Technology,美国国家标准技术研究院)在1999年发布了新的DES加密标准,3DES取代DES成为新的加密标准。3DES采用168位的密钥,三重加密,但速度较慢。之后,又出现了AES(Advanced Encryption Standard,先进加密标准)等高级对称机密算法。
  1. 由于DES算法安全性方面的原因,为了提高DES算法的抗攻击性,因此提出了Triple-DES算法。
  2. Triple-DES算法的基本原理是:用两个密钥对数据进行3次加密/解密运算。即首先使用第一个密钥对数据进行加密,然后用第二个密钥对其进行解密,最后用第一个密钥再加密。这两个密钥可以是同一个,也可以不同,它们也可以来源于一个128位密钥,只是在加密/解密时将其分割成两个64位的密钥,分别轮换使用这两个64位密钥去完成加密/解密运算。Triple-DES算法保留了DES算法运算速度快的特点,通过增加运算次数和密钥长度(两个64位密钥相当于128位密钥)来增加破解者的破解时间,但是从密码学本身来说,其安全强度并没有增加。
  1. 现在我们用到的RC系列算法包括RC2、RC4、RC5、RC6算法,其中RC4是序列密码算法,其他三种是分组密码算法。
  2. RC2算法: 该算法设计的目的是用来取代DES算法,它采用密钥长度可变的对明文采取64位分组的分组加密算法,属于Festel网络结构。
  3. RC4算法: 该算法是一个密钥长度可变的面向字节流的加密算法,以随机置换为基础。该算法执行速度快,每输出1字节的结果仅需要816字节的机器指令。RC4算法比较容易描述,它首先用82048位可变长度的密钥初始化一个256字节的状态矢量S。S的成员标记为S[0],S[1],…,S[255],整个置换过程都包含0~255的8比特数。对于加密和解密,设字节数据为K,由S中256个元素按一定方式选出一个元素生成,每生成一个K值,元素中的数据就要被重新置换一次。
  1. Rijndael是一个反复运算的加密算法,它允许可变动的数据区块及密钥的长度。数据区块与密钥长度的变动是各自独立的。
  2. 在Rijndael算法中定义了两个名词:
    State:在运算过程中所产生的中间值,是一个4×Nb的矩阵,Nb可由数据长度除以32位求得,也就是把数据分割成Nb个区块。
    Cipher Key:用来做加密运算的密钥,形式是一个4×Nk的矩阵,Nk可由金钥长度除以32位求得,也就是把密钥分割成Nk个32位的子密钥。
  3. 在Rijndael算法中,运算的回合数(Nr)是由Nb及Nk决定的

2.3 非对称加密

非对称加密:指加密和解密使用不同密钥的加密算法。非对称加密算法需要两个密钥:公钥(publickey)和私钥(privatekey)。
公钥与私钥是一对存在,如果用公钥对数据进行加密,只有用对应的私钥才能解密;如果用密钥对数据进行加密,那么只有用对应的公钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。

非对称密码体制的特点:算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快。对称密码体制中只有一种密钥,并且是非公开的,如果要解密就得让对方知道密钥。所以保证其安全性就是保证密钥的安全,而非对称密钥体制有两种密钥,其中一个是公开的,这样就可以不需要像对称密码那样传输对方的密钥了。这样安全性就大了很多。

  1. 非对称密码体制的特点:算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快。
  2. 对称密码体制中只有一种密钥,并且是非公开的,如果要解密就得让对方知道密钥,所以保证其安全性就是保证密钥的安全。而非对称密钥体制有两种密钥,其中一个是公开的,这样就可以不需要像对称密码那样传输对方的密钥了。这样安全性就大了很多。
  3. 假设两个用户要加密交换数据,双方交换公钥,使用时一方用对方的公钥加密,另一方即可用自己的私钥解密。
  4. 如果企业中有n个用户,企业需要生成n对密钥,并分发n个公钥。由于公钥是可以公开的,用户只要保管好自己的私钥即可,因此加密密钥的分发将变得 十分简单。同时,由于每个用户的私钥是唯一的,其他用户除了可以通过"信息发送者的公钥"来验证信息的来源是否真实,还可以确保发送者无法否认曾发送过该信息。非对称加密的缺点是加解密速度要远远慢于对称加密,在某些极端情况下,甚至能比对称加密慢上1000倍。
  5. 非对称的好处显而易见,非对称加密体系不要求通信双方事先传递密钥或有任何约定就能完成保密通信,并且密钥管理方便,可实现防止假冒和抵赖,因此,更适合网络通信中的保密通信要求。
  1. 对称加密加密与解密使用的是同样的密钥,所以速度快,但由于需要将密钥在网络传输,所以安全性不高。
  2. 非对称加密使用了一对密钥,公钥与私钥,所以安全性高,但加密与解密速度慢。
  3. 常用解决的办法是将对称加密的密钥使用非对称加密的公钥进行加密,然后发送出去,接收方使用私钥进行解密得到对称加密的密钥,然后双方可以使用对称加密来进行沟通。这就是在HTTPS中使用的方式。

2.3.1 非对称加密算法

2.3.2 完整的非对称加密过程

  1. 首先你要确认是否是支付宝的数字证书,如果确认为支付宝身份后,则对方真实可信。可以向对方传送信息;
  2. 你准备好要传送的数字信息(明文)计算要转的多少钱,对方支付宝账号等;
  3. 你 对数字信息进行哈希运算,得到一个信息摘要(客户端主要职责);
  4. 你用自己的私钥对信息摘要进行加密得到 你 的数字签名,并将其附在数字信息上;
  5. 你随机产生一个加密密钥,并用此密码对要发送的信息进行加密(密文);
  6. 你用 支付宝的公钥对刚才随机产生的加密密钥进行加密,将加密后的 DES 密钥连同密文一起传送给支付宝;
  7. 支付宝收到 你 传送来的密文和加密过的 DES 密钥,先用自己的私钥对加密的 DES 密钥进行解密,得到 你随机产生的加密密钥;
  8. 支付宝 然后用随机密钥对收到的密文进行解密,得到明文的数字信息,然后将随机密钥抛弃;
  9. 支付宝 用你 的公钥对 你的的数字签名进行解密,得到信息摘要;
  10. 支付宝用相同的哈希算法对收到的明文再进行一次哈希运算,得到一个新的信息摘要;
  11. 支付宝将收到的信息摘要和新产生的信息摘要进行比较,如果一致,说明收到的信息没有被修改过;
  12. 确定收到信息,然后进行向对方进行付款交易,一次非对称密过程结束。

2.3.3 对称加密的应用模式

最基本的加密模式,也就是通常理解的加密,相同的明文将永远加密成相同的密文,无初始向量,容易受到密码本重放攻击,一般情况下很少用。

明文被加密前要与前面的密文进行异或运算后再加密,因此只要选择不同的初始向量,相同的密文加密后会形成不同的密文,这是目前应用最广泛的模式。CBC加密后的密文是上下文相关的,但明文的错误不会传递到后续分组,但如果一个分组丢失,后面的分组将全部作废(同步错误)。

2.3.3.1 终端验证

注意:上述加密命令中616263是加密的key,-nosalt是表示不加盐。

3. 数字签名

  1. 数字签名:是将摘要信息用发送者的私钥加密,与原文一起传送给接收者。接收者只有用发送者的公钥才能解密被加密的摘要信息,然后用HASH函数对收到的原文产生一个摘要信息,与解密的摘要信息对比。
  2. 如果相同,则说明收到的信息是完整的,在传输过程中没有被修改;
    否则说明信息被修改过,因此数字签名能够验证信息的完整性。
    如果中途数据被纂改或者丢失。那么对方就可以根据数字签名来辨别是否是来自对方的第一手信息数据。
  3. 数字签名是个加密的过程,数字签名验证是个解密的过程
  4. 数字签名用来,保证信息传输的完整性、发送者的身份认证、防止交易中的抵赖发生。
  5. 非对称加密算法实现机密信息交换的基本过程是:甲方生成一对密钥并将其中的一把作为公用密钥向其它方公开;得到该公用密钥的乙方使用该密钥对机密信息进行加密后再发送给甲方;甲方再用自己保存的另一把专用密钥对加密后的信息进行解密。
  1. 数字证书:就是互联网通讯中标志通讯各方身份信息的一串数字,提供了一种在Internet上验证通信实体身份的方式,数字证书不是数字身份证,而是身份认证机构盖在数字身份证上的一个章或印(或者说加在数字身份证上的一个签名)。
  2. 它是由权威机构——CA机构,又称为证书授权(Certificate Authority)中心发行的,人们可以在网上用它来识别对方的身份。
  3. 数字证书绑定了公钥及其持有者的真实身份,它类似于现实生活中的居民身份证,所不同的是数字证书不再是纸质的证照,而是一段含有证书持有者身份信息并经过认证中心审核签发的电子数据,广泛用在电子商务和移动互联网中。

3.1 数字前面在HTTPS协议中的应用

  1. 浏览器向服务器发出请求,询问对方支持的对称加密算法和非对称加密算法;服务器回应自己支持的算法。
  2. 浏览器选择双方都支持的加密算法,并请求服务器出示自己的证书;服务器回应自己的证书。
  3. 浏览器随机产生一个用于本次会话的对称加密的钥匙,并使用服务器证书中附带的公钥对该钥匙进行加密后传递给服务器;服务器为本次会话保持该对称加密的钥匙。第三方不知道服务器的私钥,即使截获了数据也无法解密。非对称加密让任何浏览器都可以与服务器进行加密会话。
  4. 浏览器使用对称加密的钥匙对请求消息加密后传送给服务器,服务器使用该对称加密的钥匙进行解密;服务器使用对称加密的钥匙对响应消息加密后传送给浏览器,浏览器使用该对称加密的钥匙进行解密。第三方不知道对称加密的钥匙,即使截获了数据也无法解密。对称加密提高了加密速度 。

3.1.1 HTTPS中的SSL流程:

  1. 为了提高安全性,我们常用的做法是使用对称加密的手段加密数据。可是只使用对称加密的话,双方通信的开始总会以明文的方式传输密钥。那么从一开始这个密钥就泄露了,谈不上什么安全。所以 TLS/SSL 在握手的阶段,结合非对称加密的手段,保证只有通信双方才知道对称加密的密钥。大概的流程如下:


    HTTPS中的SSL流程

    CA证书验证过程:


    CA证书验证过程
    CA证书验证过程2
Client Hello

3.1.2 HTTPS中的双向验证

4. 对称加密终端演练

终端输入命令

 echo -n hello | openssl enc -des-ecb -K 616263 -nosalt | base64

加密后结果输出为:HQr0Oij2kbo=


DES(ECB)加密

终端输入命令

echo -n hello | openssl enc -des-cbc -iv 0102030405060708 -K 616263 -nosalt | base64

加密后结果:alvrvb3Gz88=


DES(CBC)加密

终端输入命令

  echo -n hello | openssl enc -aes-128-ecb -K 616263 -nosalt | base64

加密后结果:d1QG4T2tivoi0Kiu3NEmZQ==


AES(ECB)加密

终端输入命令

  echo -n hello | openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt | base64

加密后结果:u3W/N816uzFpcg6pZ+kbdg==


AES(CBC)加密

终端输入命令

  echo -n HQr0Oij2kbo= | base64 -D | openssl enc -des-ecb -K 616263 -nosalt -d

解密后结果:


DES(ECB)解密

终端输入命令

  echo -n alvrvb3Gz88= | base64 -D | openssl enc -des-cbc -iv 0102030405060708 -K 616263 -nosalt -d

加密后结果:


DES(CBC)解密

终端输入命令

  echo -n d1QG4T2tivoi0Kiu3NEmZQ== | base64 -D | openssl enc -aes-128-ecb -K 616263 -nosalt -d

加密后结果:


AES(ECB)解密

终端输入命令

 echo -n u3W/N816uzFpcg6pZ+kbdg== | base64 -D | openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt -d

加密后结果:

AES(CBC)解密

5. 对称加密实战

5.1 Hash加密代码

NSString+Hash.h文件

//
//  NSString+Hash.h
//  001-KEncryDemo
//
//  Created by 孔雨露 on 2019/12/14.
//  Copyright © 2019 Apple. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

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

NS_ASSUME_NONNULL_END

NSString+Hash.m文件

//
//  NSString+Hash.m
//  001-KEncryDemo
//
//  Created by 孔雨露 on 2019/12/14.
//  Copyright © 2019 Apple. All rights reserved.
//

#import "NSString+Hash.h"
#import <CommonCrypto/CommonCrypto.h>

@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

//
//  KEncryptionTool.h
//  001-KylAppEncrypt
//
//  Created by 孔雨露 on 2019/12/14.
//  Copyright © 2019 Apple. All rights reserved.
//
/**
*  终端测试指令
*
*  DES(ECB)加密
*  $ echo -n hello | openssl enc -des-ecb -K 616263 -nosalt | base64
*
* DES(CBC)加密
*  $ echo -n hello | openssl enc -des-cbc -iv 0102030405060708 -K 616263 -nosalt | base64
*
*  AES(ECB)加密
*  $ echo -n hello | openssl enc -aes-128-ecb -K 616263 -nosalt | base64
*
*  AES(CBC)加密
*  $ echo -n hello | openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt | base64
*
*  DES(ECB)解密
*  $ echo -n HQr0Oij2kbo= | base64 -D | openssl enc -des-ecb -K 616263 -nosalt -d
*
*  DES(CBC)解密
*  $ echo -n alvrvb3Gz88= | base64 -D | openssl enc -des-cbc -iv 0102030405060708 -K 616263 -nosalt -d
*
*  AES(ECB)解密
*  $ echo -n d1QG4T2tivoi0Kiu3NEmZQ== | base64 -D | openssl enc -aes-128-ecb -K 616263 -nosalt -d
*
*  AES(CBC)解密
*  $ echo -n u3W/N816uzFpcg6pZ+kbdg== | base64 -D | openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt -d
*
*  提示:
*      1> 加密过程是先加密,再base64编码
*      2> 解密过程是先base64解码,再解密
*/


#import <Foundation/Foundation.h>


NS_ASSUME_NONNULL_BEGIN

@interface KEncryptionTool : NSObject

    + (instancetype)shared;
    
    /**
     @constant   kCCAlgorithmAES     高级加密标准,128位(默认)
     @constant   kCCAlgorithmDES     数据加密标准
     */
    @property (nonatomic, assign) uint32_t algorithm;
    
    /**
     *  加密字符串并返回base64编码字符串
     *
     *  @param string    要加密的字符串
     *  @param keyString 加密密钥
     *  @param iv        初始化向量(8个字节)
     *
     *  @return 返回加密后的base64编码字符串
     */

- (NSString *)encryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv;
    
    /**
     *  解密字符串
     *
     *  @param string    加密并base64编码后的字符串
     *  @param keyString 解密密钥
     *  @param iv        初始化向量(8个字节)
     *
     *  @return 返回解密后的字符串
     */
- (NSString *)decryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv;
@end

NS_ASSUME_NONNULL_END

KEncryptionTool.m文件:

//
//  KEncryptionTool.m
//  001-KylAppEncrypt
//
//  Created by 孔雨露 on 2019/12/14.
//  Copyright © 2019 Apple. All rights reserved.
//



#import "KEncryptionTool.h"
#import <CommonCrypto/CommonCrypto.h>
@interface KEncryptionTool()
@property (nonatomic, assign) int keySize;
@property (nonatomic, assign) int blockSize;

@end


@implementation KEncryptionTool
+ (instancetype)shared {
    static KEncryptionTool *instance;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
        instance.algorithm = kCCAlgorithmAES;
    });
    
    return instance;
}
    
- (void)setAlgorithm:(uint32_t)algorithm {
    _algorithm = algorithm;
    switch (algorithm) {
        case kCCAlgorithmAES:
        self.keySize = kCCKeySizeAES128;
        self.blockSize = kCCBlockSizeAES128;
        break;
        case kCCAlgorithmDES:
        self.keySize = kCCKeySizeDES;
        self.blockSize = kCCBlockSizeDES;
        break;
        default:
        break;
    }
}
    
- (NSString *)encryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv {
    
    // 设置秘钥
    NSData *keyData = [keyString dataUsingEncoding:NSUTF8StringEncoding];
    uint8_t cKey[self.keySize];
    bzero(cKey, sizeof(cKey));
    [keyData getBytes:cKey length:self.keySize];
    
    // 设置iv
    uint8_t cIv[self.blockSize];
    bzero(cIv, self.blockSize);
    int option = 0;
    if (iv) {
        [iv getBytes:cIv length:self.blockSize];
        option = kCCOptionPKCS7Padding;
    } else {
        option = kCCOptionPKCS7Padding | kCCOptionECBMode;
    }
    
    // 设置输出缓冲区
    NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
    size_t bufferSize = [data length] + self.blockSize;
    void *buffer = malloc(bufferSize);
    
    // 开始加密
    size_t encryptedSize = 0;
    //加密解密都是它 -- CCCrypt
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                          self.algorithm,
                                          option,
                                          cKey,
                                          self.keySize,
                                          cIv,
                                          [data bytes],
                                          [data length],
                                          buffer,
                                          bufferSize,
                                          &encryptedSize);
    
    NSData *result = nil;
    if (cryptStatus == kCCSuccess) {
        result = [NSData dataWithBytesNoCopy:buffer length:encryptedSize];
    } else {
        free(buffer);
        NSLog(@"[错误] 加密失败|状态编码: %d", cryptStatus);
    }
    
    return [result base64EncodedStringWithOptions:0];
}
    
- (NSString *)decryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv {
    
    // 设置秘钥
    NSData *keyData = [keyString dataUsingEncoding:NSUTF8StringEncoding];
    uint8_t cKey[self.keySize];
    bzero(cKey, sizeof(cKey));
    [keyData getBytes:cKey length:self.keySize];
    
    // 设置iv
    uint8_t cIv[self.blockSize];
    bzero(cIv, self.blockSize);
    int option = 0;
    if (iv) {
        [iv getBytes:cIv length:self.blockSize];
        option = kCCOptionPKCS7Padding;//CBC 加密!
    } else {
        option = kCCOptionPKCS7Padding | kCCOptionECBMode;//ECB加密!
    }
    
    // 设置输出缓冲区
    NSData *data = [[NSData alloc] initWithBase64EncodedString:string options:0];
    size_t bufferSize = [data length] + self.blockSize;
    void *buffer = malloc(bufferSize);
    
    // 开始解密
    size_t decryptedSize = 0;
    /**CCCrypt 对称加密算法的核心函数(加密/解密)
     参数:
     1、kCCEncrypt 加密/kCCDecrypt 解密
     2、加密算法、默认的 AES/DES
     3、加密方式的选项
        kCCOptionPKCS7Padding | kCCOptionECBMode;//ECB加密!
        kCCOptionPKCS7Padding;//CBC 加密!
     4、加密密钥
     5、密钥长度
     6、iv 初始化向量,ECB 不需要指定
     7、加密的数据
     8、加密的数据长度
     9、缓冲区(地址),存放密文的
     10、缓冲区的大小
     11、加密结果大小
     */
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
                                          self.algorithm,
                                          option,
                                          cKey,
                                          self.keySize,
                                          cIv,
                                          [data bytes],
                                          [data length],
                                          buffer,
                                          bufferSize,
                                          &decryptedSize);
    
    NSData *result = nil;
    if (cryptStatus == kCCSuccess) {
        result = [NSData dataWithBytesNoCopy:buffer length:decryptedSize];
    } else {
        free(buffer);
        NSLog(@"[错误] 解密失败|状态编码: %d", cryptStatus);
    }
    
    return [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];
}
    
@end

- (void) testEncrpytion {
    /** AES - ECB */
    NSString * key = @"abc";
    NSString * encStr = [[KEncryptionTool shared] encryptString:@"hello" keyString:key iv:nil];
    
    NSLog(@"加密的结果是:%@",encStr);
    
    NSLog(@"解密的结果是:%@",[[KEncryptionTool shared] decryptString:encStr keyString:key iv:nil]);
    
    /** AES - CBC 加密 */
    uint8_t iv[8] = {1,2,3,4,5,6,7,8};
    NSData * ivData = [NSData dataWithBytes:iv length:sizeof(iv)];
    
    
    
    NSLog(@"CBC加密:%@",[[KEncryptionTool shared] encryptString:@"hello" keyString:@"abc" iv:ivData]);
    
    NSLog(@"解密:%@",[[KEncryptionTool shared] decryptString:@"u3W/N816uzFpcg6pZ+kbdg==" keyString:key iv:ivData]);
}

参考:https://www.jianshu.com/p/a3624d4e2248

上一篇下一篇

猜你喜欢

热点阅读