密码技术

密码技术的特性

●机密性(信息加密);
●完整性(防篡改,验证数据完整性);
●认证(防止伪装者,验证身份);
●不可否认性(放置抵赖)。

关于Base64

BASE64 只是一种编码方式,本质上其实和 ASCII 和 UTF-8 编码类似,主要用途就是将不可打印的二进制数据编码为可打印的字符串,它并不具备以上密码技术的四个特性中的任何一个。

对称加密

对消息加密和解密使用相同密钥的加密算法就叫对称加密,也称为私钥加密、密钥加密。常用的对称加密算法有:DES、3DES、AES、Blowfish、RC4、RC5、RC6 等,现在的标准是 AES,也是目前使用最广泛的对称加密算法。

AES 是一种分组密码,即将明文消息拆分为一定长度的N个分组,然后对每个分组进行加密。AES 的分组长度固定为 128 比特,而密钥可以是 128/192/256 比特。既然是固定长度的分组,那我们要加密任意长度的明文,就涉及到如何将多个分组进行迭代加密的问题,因此,就有了分组模式。常用的分组模式有:ECB、CBC、CFB、OFB、CTR 等。最常用的是 ECB 和 CBC 模式,因此,需要了解下这两种模式的用法和区别。

ECB 全称为 Electronic CodeBook,电子密码本模式,是最简单的一种模式,它直接将明文分割成多个分组并逐个加密,如下图:

这种模式的优点就是简单、快速,加密和解密都支持并行计算。而缺点也比较明显,因为每个明文分组都各自独立地进行加密和解密,如果明文中存在多个相同的明文分组,则这些分组最终会被转换为相同的密文分组。这样一来,只要观察一下密文,就可以知道明文中存在怎样的重复组合,并可以以此为线索来破译密码。另外,攻击者可以通过改变密文分组的顺序,或删除密文分组,或替换掉密文分组,就可以达到对明文操纵的目的,而无需破译密码。在实际应用中,很少需要进行并行计算的加解密场景,因此,一般情况下不会采用这种分组模式,而更推荐采用 CBC 模式。

CBC 全称为 Cipher Block Channing,密文分组链接模式,是将前一个密文分组与当前明文分组的内容混合起来进行加密的,如下图:

在 CBC 模式中,首先将明文分组与前一个密文分组进行 XOR (异或)运算,然后再进行加密。加密第一个明文分组时,由于不存在“前一个密文分组”,因此需要事先准备一个长度为一个分组的比特序列来代替“前一个密文分组”,这个比特序列称为初始化向量(initialization vector),通常缩写为 IV。CBC 模式避免了 ECB 模式的弱点,明文的重复排列不会反映在密文中。不过,相比 ECB 模式,CBC 模式多了一个初始化向量IV。

另外,当最后一个明文分组的内容小于分组长度时,需要用一些特定的数据进行填充,填充方式也有很多种,常用的有两种:PKCS#5 和 PKCS#7。需要注意的就是,不同编程语言使用的填充方式可能会不同。比如,Java 是使用 PKCS#5,而 iOS 的 Objective-C 和 Swift 则采用 PKCS#7。不过,对于 AES 来说,两种填充方式是一样的。

在实际应用中,我们一般都是在前端对密码或其他敏感数据进行加密,然后在后端进行解密。因为前后端涉及到不同语言的实现,为了保证前后端经过加解密后的结果一致,有几个参数是需要保持一致的:

●密钥:密钥都要使用同一个,这点基本没有疑问,但需要注意的就是,密钥长度需要统一为 128/192/256 比特,即 16/24/32 字节。
●分组模式:分组模式推荐统一为 CBC 模式,且要显式声明,因为不同语言的默认分组模式可能会不同。
●初始化向量:加密和解密时的初始化向量 IV 也是要一致的,同样也不要使用默认设置,而要显式定义。
●填充方式:Java 采用 PKCS5Padding,iOS 和 JavaScript 采用 PKCS7,对于 AES 来说,两者是一样的。

AES 算法本身操作的都是 byte 字节数组,因此,加密后一般会使用 BASE64 编码将 byte 数组转为字符串,而解密之前则先用 BASE64 解码将字符串转回 byte 数组。

使用对称加密最关键的就是要保证密钥的安全,一般不建议直接在网络上传输密钥,另外,在客户端也要做好密钥的安全存储。

非对称加密

非对称加密也称公钥加密,使用了一对密钥,用公钥进行加密,再用配对的私钥进行解密。公钥是公开的,而私钥是保密的。相比对称加密安全性提高了,但牺牲了性能,加解密的速度慢了几个数量级,消息越长,加密和解密的速度越慢。


使用最广泛的非对称加密算法就是 RSA,其原理是利用了大整数质因数分解(每个合数都可以写成几个质数相乘的形式,其中每个质数都是这个合数的因数,叫做这个合数的分解质因数。)问题的困难度,加密和解密其实就是非常简单的两条公式:
●加密:密文 = 明文^E mod N
●解密:明文 = 密文^D mod N

即是说,加密就是对明文的 E 次方后除以 N 求余数的过程,其中 E 和 N 的组合就是公钥,即公钥 = (E, N)。
而解密过程就是对密文进行 D 次方后除以 N 得到余数,即是明文,D 和 N 的组合就是私钥,即私钥 = (D, N)。

公钥和私钥共有的 N 称为 module,即模数,E 和 D 则分别是公钥指数和私钥指数。因为 RSA 是基于以上数学问题的,所以其明文、密钥和密文都是数字,我们平时看到的字符串其实都是二进制表示的数字经过 BASE64 编码的

密钥长度越长越安全,推荐使用 1024 比特或更大的值,这里说的 1024 密钥长度其实是指模数的长度。还有,不同于对称密码可以加密任意长度的明文,RSA 明文长度是不能超过密钥长度的。Java 默认的 RSA 加密实现明文长度最长为密钥长度减去 11 字节,假如密钥长度设为 1024 比特,即 128 字节,那明文长度则不能超过 128 - 11 = 117 字节,如果超过该长度则会抛异常。如果想要加密的明文比较长,那就生成更长的密钥,如 2048 比特,那明文可以长达 245 字节,足够了。太长的明文也不推荐使用 RSA 进行加密,性能太低了。

另外,为了提高安全性,RSA 加密时都会填充一些随机数。RSA 加密填充方式主要有三种:NoPadding、PKCS1Padding、OAEPPadding。其中,最常用的就是 PKCS1Padding,它会在明文前面填充 11 字节的随机数,因此,对同一明文每次加密产生的密文都会不一样。如果想让每次加密产生的密文都一样,那填充方式就采用 NoPadding,即不填充,但这样无疑减低了安全性,所以一般不建议采用 NoPadding。

实际应用中,我们不会直接对长消息进行非对称加密,而只会对一些安全性要求非常高的短消息进行加密,比如用户的密码、对称加密的密钥。SSL/TLS 的加密方案就是用对称加密对请求消息进行加密,用公钥加密对对称加密的密钥进行加密。

单向散列函数

对称加密和非对称加密主要是用来解决消息的机密性问题的,即可以防止消息被窃听导致秘密泄露,但却无法校验消息是否被篡改。要校验消息是否被篡改,就要对消息进行完整性校验,有多种校验方案,最简单高效的就是单向散列函数。

单向散列函数也称哈希函数、杂凑函数、消息摘要算法等,是能把任意长的输入消息串转变成固定长的输出串的一种函数,输出值称为“散列值”或“消息摘要”,也称为消息的“指纹”。使用单向散列函数,同一消息会生成同样的散列值,而只要改了消息,哪怕只改了 1 个字节,最终的散列值变化也很大,因此,很适合用这个散列值校验消息的完整性。

最常用的单向散列函数就是 MD5 和 SHA,SHA 其实包括了 SHA-1、SHA-224、SHA-256、SHA-384 和 SHA-512,后四种并称为 SHA-2。有时候,我们去下载一些软件的安装文件时,官方一般都会提供对应该文件的 MD5 和 SHA-1 的散列值,以便我们可以对下载后的文件自己生成散列值,再和官方提供的散列值进行比对,就知道这个文件有没有被修改过。

在我们平时的实际应用中,倒是很少会单独使用单向散列函数,一般都会结合其他技术一起使用。毕竟,单独使用的安全性不高。就举用户密码的安全来说吧,很多应用会将用户密码直接 MD5 之后传输给服务端。这种方案主要存在两个安全隐患,一是对于一些不够复杂的密码难以防范彩虹表,二是不同用户如果设置了相同密码那散列值无疑会一样。

先说第一点,首先,先了解下什么是彩虹表。彩虹表是一个用于单向散列函数逆运算的预先计算好的表,为了破解密码的散列值而准备。我们可以简单理解为彩虹表就是在明文和密文之间建立起对应关系的字典表,可以通过已知的密文反查出明文,虽然实际上其原理远比想象中复杂得多。比如说,密码“123456”的 MD5 结果是“E10ADC3949BA59ABBE56E057F20F883E”,那我监听到用户登录的请求,拿到“E10ADC3949BA59ABBE56E057F20F883E”这个密码串时,从准备好的彩虹表中就可以反查出原密码是“123456”。

再说第二点,设置了相同密码的不同用户,由于他们 MD5 后的散列值全都一样,那么只要破解了其中一个密码,就等于破解了多个用户的密码。

为了应对以上两个问题,比较好的方案就是 MD5 + salt,也称 MD5 加盐,即将原密码拼上一串盐值 salt 之后再进行 MD5。盐值 salt 是一个随机字符串,每个用户的 salt 值一般都是不同的,这样就可以保证不同用户最终 MD5 出来的散列值不一样,而且因为有一串随机字符串,彩虹表也很难发挥作用了。

消息认证码

虽然单向散列函数可以用来对消息进行完整性校验,但无法校验消息是否来自合法的发送者,即无法解决认证问题。要解决发送者的认证问题,最常用的有两种方案,一是采用消息认证码,二是使用数字签名。这一小节我们先来了解下消息认证码。

消息认证码(message authentication code)是一种确认完整性并进行认证的技术,简称为 MAC。消息认证码的输入包括任意长度的消息和一个发送者与接收者之间共享的密钥,它可以输出固定长度的数据,这个数据称为 MAC 值。

消息认证码的实现方式有很多种,最常用的实现方式就是 HMAC,再具体点,根据使用哪种单向散列函数可分为:HMAC-MD5、HMAC-SHA1、HMAC-SHA256 等等。HMAC 简单理解就是带有密钥的散列函数,因为有了密钥,就可以对发送者进行认证;也因为使用了散列函数,也具有完整性校验的性质。

认证的基本流程就是:

1、发送者使用共享密钥对消息计算 MAC 值;
2、发送者将消息和 MAC 值一起发送给接收者;
3、接收者收到消息和 MAC 值后,使用同一个共享密钥对消息计算 MAC 值;
4、对比计算出来的 MAC 值和接收到的 MAC 值是否一致,一致则认证成功。

现在,很多接口所添加的 URL 签名机制,其实就是对请求做 MAC 认证,我常用的也是 HMAC。具体的设计细节后面的文章再详细说明。不过,因为使用了共享密钥,因此也存在和对称加密一样的密钥安全问题。

数字签名

数字签名也可以解决发送者的认证问题,而且,数字签名还具有不可抵赖性。数字签名的原理也非常简单,其实就是将非对称加密反过来用。我们知道,非对称加密是用公钥加密,然后用私钥解密。而数字签名则是用私钥加密,生成的密文就是数字签名,再用公钥解密。用私钥进行加密这一行为只能由持有私钥的人完成,正是基于这一事实,才可以将用私钥加密的密文作为签名来对待。而由于公钥是对外公开的,因此任何人都可以用公钥进行解密,即任何人都能够对签名进行验证。

另外,我们也知道,非对称加密本身加密和解密是非常慢的,消息越长,性能越慢,因此,一般不用来加密和解密长消息。同样的,一般也不会直接对长消息签名,通常的做法是对消息的散列值进行签名,因为散列值比较短,所以加密签名相对就会快很多。因此,你会看到数字签名有类似 MD5withRSA、SHA1withRSA 这样的实现。

最后,需要注意一点,像 MD5withRSA 和 SHA1withRSA 这样的数字签名实现可以校验消息完整性、对发送者进行认证、还可防止抵赖,但却不能解决机密性的问题,不要妄想用一种密码技术就能解决所有问题。

不过,数字签名其实不太适合直接用在客户端上。因为客户端要对消息签名,那么客户端就需要保存私钥,那依然有私钥的安全配送和存储问题。数字签名使用最广泛的应该就是用在数字证书上了,这还涉及到 SSL/TLS 和 CA 等,后面的文章再聊这个话题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值