对称密钥加密,是指数据发送和接收双方都必须使用相同密钥进行加密和解密的一种加密算法。从算法实现上,又可以细分为分组加密算法(Block Cipher)和流加密算法(Stream Cipher)。
分组加密通俗来说,就是加密时将原数据分块,按块分组分别加密最后组合在一起,生成最终的密文。
分块加密有一个问题,明文可能不一定恰好是块大小的整数倍,因此产生了Padding(补位)的概念,补位有多种方法,比如我们可能会想到不够块大小就填充0
等方式。
关于补位有PKCS#5和PKCS#7等标准,通用的加密算法都是按照标准实现补位的,具体可以参考相关文档。
分块密码加密时要分块迭代,因此就有了迭代模式的概念。
ECB(Electronic Code Book)是最简单直接的加密模式,流程如下:
ECB模式缺点显而易见,明文和密文分组存在顺序对应,可能会产生伪造密文顺序攻击;且ECB模式不能抹平原数据内容的概率分布,因此也无法抵抗已知明文攻击。
由此,ECB模式主要用于不太重要的短数据加密,如配置文件密码加密等场景。
CBC(Cipher Block Chaining)引入了一个初始化向量IV解决ECB模式的问题,流程如下:
CBC解决了ECB模式的问题,适合大多数数据加密场景,不过CBC模式有一个缺点是分组只能串行计算。
CFB(Cipher Feedback)/OFB(Output Feedback)/CTR(Counter)三种模式比较类似,其实现都用到了类似流密码的思想,引入了密钥流,既能解决ECB模式安全性不高的问题,也能解决CBC模式不能并行执行的问题。以CTR为例,流程如下:
实际上,CTR内只有加密运算没有解密运算,因为我们只需要加密运算获取密钥流,而明文通过XOR会得到密文,密文通过XOR又会得到明文。
CFB与CTR类似,区别只是使用的不是计数器,而是块0使用随机IV,后续使用前一步密文输出;OFB也类似,块0使用随机IV,后续使用前一步IV与密钥加密运算的输出。
常见的分组加密包括:DES、3DES、AES。
DES(Data Encryption Standard)算法为1973年,美国IBM公司团队提供,1977年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS)。最初的DES算法随着计算机算力的发展,其强度已经不能满足加密的需要,演变为了强度更高的3DES。
AES(Advanced Encryption Standard)为比利时密码学家Joan Daemen和Vincent Rijmen所设计,由美国国家标准与技术研究院 (NIST)在2002年确定成为有效的标准。
下面例子使用Java的BouncyCastle库,实现AES的CTR模式加解密:
Security.addProvider(new BouncyCastleProvider());
// 明文
byte[] data = "Hello, world!".getBytes();
// 指定加密算法为AES的CTR模式,补位模式NoPadding
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
// 获取密钥
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES", "BC");
keyGenerator.init(256);
SecretKey secretKey = keyGenerator.generateKey();
// 随机IV
SecureRandom randomSecureRandom = new SecureRandom();
byte[] iv = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
// CTR模式加密
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
byte[] dataEnc = cipher.doFinal(data);
// CTR模式解密
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
data = cipher.doFinal(dataEnc);
CBC等其它模式实现代码类似,这里就不多介绍了。
流加密算法原理比较简单,它依据种子密钥和伪随机生成算法,来生成无限长的密钥流,明文数据和密钥流做XOR运算得到密文,再次XOR运算得到明文。
下面例子使用Java的BouncyCastle库,实现RC4加解密:
byte[] data = "Hello, world!".getBytes();
byte[] key = "abc123".getBytes();
StreamCipher engine = new RC4Engine();
engine.init(true, new KeyParameter(key));
// RC4加密
byte[] dataEnc = new byte[data.length];
engine.processBytes(data, 0, data.length, dataEnc, 0);
// 重置密钥流
engine.reset();
// RC4解密
data = new byte[data.length];
engine.processBytes(dataEnc, 0, dataEnc.length, data, 0);
PBE(Password Based Encryption)不是一种新的加密算法,而是对消息摘要算法和对称加密算法的综合运用。
之前介绍的AES算法等密钥都是定长的二进制数据,比较难记。PBE能够根据短口令生成密钥,该过程被称为KDF(Key Derivation Function)函数,得到密钥后再进行加密。
下面例子代码使用BouncyCastle实现PBEWithSHA256And256BitAES-CBC
加解密:
Security.addProvider(new BouncyCastleProvider());
// 基于口令生成密钥
byte[] data = "Hello, world!".getBytes();
String password = "abc123";
String salt = "a8@!px";
PBEParameterSpec pbeParameterSpec = new PBEParameterSpec(salt.getBytes(), 100);
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBEWithSHA256And256BitAES-CBC-BC", "BC");
SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);
Cipher cipher = Cipher.getInstance("PBEWithSHA256And256BitAES-CBC-BC", "BC");
// 加密
cipher.init(Cipher.ENCRYPT_MODE, secretKey, pbeParameterSpec);
byte[] dataEnc = cipher.doFinal(data);
// 解密
cipher.init(Cipher.DECRYPT_MODE, secretKey, pbeParameterSpec);
data = cipher.doFinal(dataEnc);
PBE加密应用非常广泛,例如docx
文档设置密码保护,其实就是一种PBE加密。