编码、散列和加密是常用的系统设计工具,但常常被混用、误用,我们需要明确区分他们的边界,并在合适的场景下使用。
很多安全事件往往是设计者对这三种对数据处理的概念理解不够造成的。一个典型的例子是,Base64 是一种数据编码方法,它很容易被反编码回人类容易识别的形式,因此不能被用来作为数据加密的方法(因为它就不是一个数据加密的方法)。
把 Base64 当做加密方法,会带来严重的潜在安全问题。
1 编码、散列和加密的异同
下面澄清这三个概念。
编码(Encoding)是指将数据从一种格式转换为另外一种形式,用于在不同的系统和平台中使用,避免传输过程中出现错误。例如,URL 编码可以让数据在 URL 作为参数传输,是一种典型的数据编码方法和编程技巧。编码后的数据都是可被还原的。
散列(Hashing)是指从源数据中提取特征转换为一个固定长度的字母编码,又被成为数字摘要。和编码不同的是,散列的结果无法被还原(但不同的数据可以散列出同样的结果)。散列结果往往非常短小,因此可以作为数据完整性或者正确性校验。所以通过加盐的散列方法,可以让数据库不存储用户口令的情况下,依然能验证用户的身份。
加密(Encryption)加密是指处于安全用途,将输入内容通过秘密转换的形式,生成无法识别的密文。早期的加密方法依赖秘密的编码形式,依赖加密方法的机密性,本质依然是一种编码技术。现代的加密方法依赖密匙的机密性,让破解变得更困难。
以上三种方式真正能保护数据安全的方式只有加密,散列可以被用于识别访问者身份,而编码几乎不能被当做保护数据安全的手段。
2 数据编码
常用的数据编码手段有Base 系列编码、URL 编码、HTML 实体编码、HEX 二进制编码、Unicode 编码等,下面分别介绍一下其用途。
Base 系列编码
我们可以使用 Base 系列编码将二进制数据编码层一段用于安全传输的文本数据。
Base 系列编码中,我们常常熟悉 Base64,其实还可以使用例如 Base62 的编码形式(甚至包括Base85、Base16、Base32、Base36)。那么为什么我们经常需要 Base64 编码呢?
因为这 64 个用于编码的符号是经过精心挑选的,在大多数场合下嵌入到其他的数据格式中不会带来破坏性的问题,而剩下的其它 ASCII 很多特殊符号被在各种场合下被用于控制符号。
这 64 个符号包括:
- 0-9 10 个数字。
- 26 个大写字母。
- 26 个小写字母。
- 2 个特殊符号,通常是 +、/。
- 其中我们常见的 = 号也会出现在编码结果中,不过是充当填充字符。
其中一个经典的场景是,HTTP 认证中的凭证传递就是通过"用户名:密码" Bas64 编码后在 HTTP 报文中传递的。
URL 编码
URL 编码是指当需要将数据在 URL 传输时,需要将其编码为 URL 友好,避免 URL 解析失败。
Base64 很好,但是不能在 URL 中使用。因为 Base64 中 +、/ 是 URL 不友好的。解决办法是
- 采用 Base62 编码进行设计。
- 将编码结果中的 +、/ 替换成其它安全字符,并在解码时候做相反的处理。
- 将 Base64 再次使用 URL 编码,让+、/ 转义为 URL 友好。
- 一些场景下,可以直接使用 URL 编码,避免 Base64 编码过程。
URL 编码其实又叫做百分号编码(percent-encoding),它的编码原理非常简单,将一些特殊字符转换成带 % 前缀的 16 进制。被转换的字符为 URL 需要用到的保留字符,例如:
! # $ & ' ( ) * + , / : ; = ? @ [ ]
而对于非 ASCII 字符会使用 Unicode 的编码转换为 16 进制,也会加上 % 前缀。
关于编码的总结
还有一些不太常用的编码形式,但是对于程序员来说也需要了解的有:
- HEX 二进制编码:使用 16 进制,用于让二进制内容变得让人容易理解和记录,其实是 Base16 编码。
- Unicode 编码:用于表示 ASCII 的文字、表情和字符。
- HTML 实体编码:用于将 HTML 标签转义为 HTML 友好的形式,避免破坏 DOM 文档流。
其实可以把上面的编码方式整理成两类:进制转换、转义。例如 Base 编码本质是进制转换,其实理解 Base 编码的好处是,可以设计任意进制的编码算法,在需要使用的合适的自定义编码。而转义可以让一些特殊内容在另外一种数据格式中友好使用,这样在不同的场景中,系统设计中就能很快找到合适的转换算法。
3 散列
聊完编码,我们可以进一步聊聊散列。
散列(哈希为音译)是一种将任意大小的数据转换为固定长度的唯一标识符的方法,不过这里有一个特例就是 CRC32 奇偶校验码,也属于这个特点,但是一般不被认为是散列算法。
正是因为散列算法将大的数据转换为固定长度的唯一标识符,这就意味着被映射的信息空间是有限的,那么就会出现不同数据散列出同样的结果,这就是散列算法的重要特征——碰撞(collisions)。
为了让本文更加实用,除了常见散列算法 MD5 和 SHA1 外,重点介绍一下 Bcrypt 和国密 SM3 SM4。
MD5
MD5(Message Digest Algorithm 5)是一种常见的哈希函数,曾经被大量广泛的使用,用于将任意长度的数据映射为128位的固定长度散列值。尽管MD5在过去被广泛使用,但现在不再被推荐使用,主要原因是:
- 由于 MD5 的碰撞容易性,它不再适用于密码存储。如果密码使用MD5进行哈希,即使是强密码也容易受到彩虹表攻击。这里可以参考王小云教授相关的论文。
- 计算速度过快,MD5的计算速度非常快,这使得暴力破解或彩虹表攻击变得更加容易。
SHA1
本来 SHA1 被设计出来就是为了解决更安全的散列需求,所以它的名称为 Secure Hash Algorithm 1。它能生成比 MD5 更长的散列结果,得到 160-bit(20 字节)的散列值。
所以某种意义上也不适合作为生成用户口令散列值的算法。
Bcrypt
计算机追求更快的计算,但是好玩的是在密码散列上,快并不是优势,所以我们需要更慢的散列算法,用来抵御重试攻击。在 OWASP Password Storage Cheat Sheet 中,推荐了几种算法:
- Argon2
- PBKDF2
- scrypt
- bcrypt
基于各方面的原因(平台支持),bcrypt 目前为主流的密码散列算法。在 spring security 被采用,而它主要的特点是慢且不容易碰撞,这样就可以增加穷举攻击难度。
国密散列算法 SM3
在国内软件开发领域,一些银行和政府会要求使用一套中国国家密码管理局提供的加密和散列算法,其中 SM3 就是散列算法。M3和SM4是中国国家密码管理局(National Cryptography Administration,简称:NCA)制定的密码学标准,分别用于散列算法和对称加密算法。
在国家标准全文公开系统能找到这篇标准文件《信息安全技术 SM3密码杂凑算法》。
参考地址:https://openstd.samr.gov.cn/bzgk/gb/newGbInfo?hcno=45B1A67F20F3BF339211C391E9278F5E。
HMAC
虽然说散列算法算不上严谨的加密算法,但是有一个算法介于加密算法和散列算法之间,而且使用非常广泛。 我们在设计一些开放 API 的时候,会用到 HMAC 算法,结合密匙来生成摘要信息,用于外部服务的签名。
所以 HMAC 算法常常用于安全签名用途。例如,在报文中需要明文传递一些信息,并在必要时回传,但是需要保证这个过程没有被篡改,就可以使用 HMAC 生成一个签名。
不过 HMAC 可以看做对其它散列算法的封装,你可以选择使用 MD5 或者 SHA256 之类的版本。
关于散列的总结
由于散列算法是不可逆的,所以严格来说不算是加密算法。但散列算法在信息安全中非常重要,甚至要比加密算法的重要性还高。
所以作为架构师需要了解和深入部分散列算法,在合适的场景下使用它们,保证信息安全。
4 加密
对于编码、散列来说,加密这件事情就要高级的多。从密码学的历史来看,我们可以简单的将其分为古典密码学和现代密码学。
古典密码学:主要是以替换和移位的方式实现加密,且非常依赖加密方式的机密性,例如凯撒密码、维吉尼亚密码。 现代密码学:主要以数学原理为基础,以秘钥的机密性保证信息安全。例如现代的 DES、AES、RSA 三大经典加密算法。
而现代密码学突出的是 RSA 非对称加密算法。
下面我们讨论一下 DES、AES、RSA 和 国密,以便在合适的时候使用它们(对于系统设计话题,仅仅讨论这些算法适合在什么场景下使用)。
DES 和 AES
从名字上看 DES(Data Encryption Standard)和 AES(Advanced Encryption Standard)这两个算法看起来后者就更高级。在算力宝贵的年代还可以使用 DES,但是现代系统已经基本上避免使用 DES 了(即使出现 3DES 这个变种),更推荐使用 AES。
AES 比 DES 提升的地方有:
- AES 使用较长的密钥(128位、192位或256位)。
- AES 通常比 DES 快。
- AES支持不同长度的密钥,可以根据需要选择128位、192位或256位的密钥长度。
另外值得一提的是 AES、DES 都有硬件支持加速,所以一般不推荐应用开发者自行实现加密算法。
AES 和 RSA
另外一组经常被用来比较的加密算法是 AES 和 RSA。
通常来说,AES 加密速度快,是一种对称加密的算法;RSA 加密速度慢,是一种非对称的加密算法。
对称是指使用相同的密钥进行加密和解密,发送方和接收方必须共享相同的密钥;非对称加密算法,使用一对公钥和私钥,公钥用于加密,私钥用于解密。
于是在系统设计上,我们需要在正确的场景下使用它们。
在常规的大量数据加密的场景下,优先使用 AES 而不是 RSA。甚至在一些场景下,RSA 只用来加密 AES 密匙使用,实际数据还是用 AES 加密。
RSA 最具有革命意义的地方是防伪造密文,因为当使用私钥加密公钥解密的场景下,只有持有私钥的发送方才可能加密数据。所以当把公钥分发给不信任的第三方系统时,RSA 的优势就非常大了,可以用来签名、加密凭证什么的。
国密系列
国密即国家密码局认定的国产商用密码算法,主要有 SM1、SM2、SM3、SM4、SM9,SM 为商密的拼音缩写。
除了 SM9 之外,其它四种可以和上述内容中的密码对应。
- SM1:从公开的描述看,该密码算法对标 DES 算法,中国国家标准加密算法。但是该算法并没有公开,由国密办资质认证的特定机构封装在硬件中实现。应用开发者需要购买其加密模块或者芯片。
- SM2:为非对称加密,对标 RSA 算法,RSA 的算法核心在于 2 个素数做乘积等数学运算结果得到公钥、私钥,反推困难的原理。SM2 采用了椭圆曲线数学的一种公钥加密算法(elliptic curve cryptography)。
- SM3:即为前面提到的国密散列算法。
- SM4:SM4 为另外一种分组加密算法,主要被设计用于 WAPI 技术。在 2021 年被 ISO 标准接纳,不过在作为 802.11i 协议的加密方案提交给 IEEE 的时候被拒绝了。
- SM7:一种和用户身份识别结合起来的分组密码算法,主要用于身份识别类应用(门禁卡、工作证、参赛证)。
关于加密算法的总结
加密算法中有一个比较显著的坑,很多加密库都会使用一个随机的 initialization vector 参数,也是经常看到的 IV。这会导致加密的结果不一致,当时能被正常解密。
另外就是加密和解密往往都是通过二进制的算法,因此的到的结果往往也是一个字节数组,所以需要再对其进行 Base64 或者 Hex 编码。
密码学是一个非常专业的领域,其中 RSA、ECC 算法比较难懂(很多知识,已经还给大学信息安全老师了)。我们大部分场景下无需深入探究其中的原理,只需要辨明在什么场景下需要使用什么密码即可。
参考资料
- https://www.packetlabs.net/posts/encryption-encoding-and-hashing
- https://stackoverflow.com/questions/3538021/why-do-we-use-base64
- https://stackoverflow.com/questions/3929325/why-is-aes-more-secure-than-des
- https://www.precisely.com/blog/data-security/aes-vs-des-encryption-standard-3des-tdea
- https://zhuanlan.zhihu.com/p/377362017?utm_id=0