引言
这篇文章是我在公司内部分享中一部分内容的详细版本,如标题所言,我会通过文字、代码示例、带你完整的搞懂为什么我们不建议你使用cbc加密模式,用了会导致什么安全问题,即使一定要用需要注意哪些方面的内容。
注:本文仅从安全角度出发,未考虑性能与兼容性等因素
工作模式是个啥
分组加密的工作模式与具体的分组加密算法没有关系,所以只要使用了cbc模式,不限于AES、DES、3DES等算法都一样存在问题。
以AES-128-CBC
为例,可以屏蔽AES算法的内部实现,把AES算法当作一个黑盒,输入明文和密钥返回密文。
因为是分组加密算法,所以对于长的明文,需要按照算法约定的块大小进行分组,AES每一组为16B,不同组之间使用相同的密钥进行计算的话,会产生一些安全问题,所以为了将分组密码应用到不同的实际应用,NIST定义了若干的工作模式,不同模式对分块的加密处理逻辑会不同,常见的工作模式有:
模式 | 描述 |
---|---|
ECB(电码本) | 相同的密钥分队明文分组进行加密 |
CBC(分组链接) | 加密算法的输入是上一个密文组和当前明文组的异或 |
CFB(密文反馈) | 一次处理s位,上一块密文作为下一块加密算法输入,产生伪随机数与明文异或或作为下一单元的密文 |
OFB(输出反馈) | 类似CFB,仅加密算法的输入是上一次加密的输出,且使用整个分组 |
CTR(技数器) | 每个明文分组都与一个经过加密的计数器相异或。对每个后续分组计数器递增 |
ECB模式最为简单,假设存在明文分组a、b、c、d 每个分组分别在相同密钥k进行aes加密后的密文为A、B、C、D,最终明文abcd对应的密文为ABCD,如图所示:
ECB模式很简单可能从性能角度讲非常占优,因为分组之间没有关联,可以独立并行计算。但从安全角度来看这种直接将密文分组进行拼接的方式,很可能会被攻击者猜解出明文特征或替换丢弃部分密文块达到明文的替换与截取效果,以下的图非常清晰:
所以很容易理解ECB也不是推荐使用的工作模式。
CBC
有了ECB的前车之鉴,CBC( Cipher Block Chaining)模式就提出将明文分组先于一个随机值分组IV进行异或且本组的密文又与下一组的明文进行异或的方式,这种方式增加了密文的随机性,避免了ECB的问题,详细过程见图:
加密过程🔐
解释下这个图,存在明文分组a、b、c、d,cbc工作模式是存在执行顺序的,即第一个密文分组计算后才能计算第二个分组,第一个明文分组在加密前明文a需要和一个初始分组IV进行异或运算 即 a^IV
,然后再用密钥K进行标准的AES加密,E(a^IV,K)
得到第一组的密文分组A,密文分组A会参与第二组密文的计算,计算过程类似,只不过第二次需将IV替换为A,如此循环,最后得到的密文ABCD即为CBC模式。
解密过程
仔细观察CBC的加密过程,需要使用到一个随机分组IV,在标准的加密过程中,IV会被拼接到密文分组中去,假设存在两人甲和乙,甲方给到乙方的密文实际是 (IV)ABCD,乙在拿到密文后提取IV,然后进行下图的解密:
解密过程就是加密过程转变了下方向,留意两个图从abcd到ABCD的箭头方向。第一个密文分组先进行AES解密,得到的中间值我们计为M_A,M_A再于初始向量IV进行异或得到a,第二个分组重复同样的动作,还是将IV替换为密文分组A,最终可得到明文分组abcd。
CBC有什么问题
CBC增加了随机变量IV给密文增加了随机性,增大了密文分析的难度是不是就安全了呢? 答案当然是不,CBC又引入了新的问题——可以通过改变密文从而改变明文。
CBC字节翻转攻击
原理讲解
CBC字节翻转攻击原理非常简单,如图所示:
攻击往往发生在解密过程,黑客通过控制IV和密文分组可以达到修改明文的目的,图中黑客通过替换密文D分组为E分组可以篡改原本明文d为x(可能会涉及填充验证,这里先不管),或者同样的道理黑客可以通过控制IV达到修改明文分组a的目的。
举个例子🌰
接下来用一个实际例子来演示其原理及危害。
为了保证方便进行原理讲解,在加密时会将IV和key写死,避免每次运行的结果不一样。
假设存在一个web服务应用,前后端通过Cookie来进行权限校验,cookie的内容为明文admin:0
进行AES-128-CBC加密后的密文进行base64编码,数字0代表此时用户的权限为非管理员用户,当admin后面的数字为1时,后端会认为是一名管理员用户。
Cookie内容为:AAAAAAAAAAAAAAAAAAAAAJyycJTyrCtpsXM3jT1uVKU=
此时黑客在知道校验原理的情况下可利用字节翻转攻击对此服务发起攻击,在不知道密钥的情况下将cookie明文修改为admin:1
,具体过程:
AES以16B作为block size进行分块,admin:0
在ascii编码下对应的二进制仅为7B,所以在加密时还会对原始明文进行填充直到刚好为16B的整数倍,所以还需要填充9B(填充细节下面再讲),因为CBC还会有IV,所以最终的密文是IV+Cipher,IV16B,cipher16B,总共32B,这里因为只有一个密文分块,所以改变IV的第7个字节对应明文admin:0
数字的位置,或者密文的第7个字节即可改变明文数字部分的字段,通过不断的尝试,我们将原本密文IV分组 00
改为01
,即可成功翻转明文为1,即cookie明文变为admin:1
,从而达到权限提升的目的。
完整代码:
package com.example.springshiroproject;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Key;
import java.util.Arrays;
public class MyTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
AesCipherService aesCipherService = new AesCipherService();
// 写死密钥
byte[] key = new byte[128/8];
Arrays.fill(key,(byte) '\0'); // 写死的密钥,客户端及黑客未知
String plainText = "admin:0"; // cookie明文内容
byte[] plainTextBytes = plainText.getBytes();
// 写死IV
byte[] iv_bytes = new byte[128/8];
Arrays.fill(iv_bytes, (byte) '\0');
//
// // 通过反射调用可以自定义IV的AES-128-cbc加密方法(原方法为private)
Method encryptWithIV = aesCipherService.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredMethod("encrypt",new Class[]{
byte[].class, byte[].class,byte[].class,boolean.class});
encryptWithIV.setAccessible(true);
ByteSource cipherWithIV = (ByteSource) encryptWithIV.invoke(aesCipherService,new Object[]{
plainTextBytes, key,iv_bytes,true});
System.out.println("明文:" + ByteSource.Util.bytes(plainTextBytes