本文重点在于如何在JAVA和Python中使用AES,以及相关的重要概念,而不是专门讲AES算法原理。
AES作为一个块加密算法 [block cipher],每次加密的明文大小固定为128bit,所以明文比较长的时候需要先分组再加密然后整合,这个过程中就会出现两个重要的因素:模式 和 填充方式。
1. 模式
分组密码工作模式,常用的包含ECB,CBC,OFB,CFB和CTR,详见 Block_cipher_mode_of_operation
1)ECB模式作为最简单的工作模式,直接将明文分组,每组分别加密,每个分组独立且前后文无关。
2)CBC模式,将明文分组与前一个密文分组进行XOR运算,然后再进行加密。每个分组的加解密都依赖于前一个分组。而第一个分组没有前一个分组,因此需要一个初始化向量 IV。
3)其他模式不再详述,ECB模式因为其简洁性(不需要额外的初始化向量IV)仍然被广泛使用,但是已经被认为是不安全的工作模式,具体原因见上文引用的wiki,为了保证传输信息的安全性,CBC目前是个推荐的选择,而且建议每次加密使用不同的 Key 和 IV 。
2. 填充方式
鉴于明文长度随机,分组之后,最后一个组往往需要补齐。
填充方式很多,详见 Padding (cryptography)
以下是在Java中常用的 PKCS7, 它是基于字节的填充方案 [Byte padding],其特点为:
1)填充的都是相同的字节
2)该字节的值,就是要填充的字节的个数
举例,
假定加密块长度要求为 8 byte,数据长度为 1 byte,那么需要填充7个byte,每个byte值为0x07
数据: FF
PKCS7 填充: FF 07 07 07 07 07 07 07
如果明文正好被整除了,需要增加一个完整的填充块,方便解密时判断最后一个字节是消息字节还是填充字节。
PKCS7支持长度小于256字节的加密块,而Java中经常出现的是PKCS5,PKCS5可以认为是PKCS7的一个特例,只处理8字节的加密块,理论上来说PKCS5是不适用于AES加密的,但是JCA (Java Cryptography Architecture) 标准中却只有PKCS5,这个可以认为是历史原因,实际上在加密块大于8字节时,PKCS5内部就用PKCS7方式填充。
3. 关于秘钥Key,长度可以是 128, 192 和 256 bits(注意加密块始终是 128 bits),Java默认只支持 128 bits,如果需要支持更长的key,需要添加扩展包 Java Cryptography Extension (JCE)。
4. Java中加密和解密的流程
创建Cipher实例 -> 用key初始化 -> 执行加解密,分组的操作只需要在创建实例时指定模式即可,不需要手动处理
以 CBC 模式加密为例
public byte[] encryptCBCMode(String key, String vector, String plainText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { IvParameterSpec iv = new IvParameterSpec(vector.getBytes(StandardCharsets.UTF_8)); SecretKey keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); return cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8)); }
解密时将 Cipher.ENCRYPT_MODE 替换为 Cipher.DECRYPT_MODE。
5. Python中加密和解密的流程
使用 cryptography 库,和Java流程类似,同样以 CBC 模式为例
def encrypt_cbc_mode(key, iv, plain_bytes): backend = default_backend() cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend) encryptor = cipher.encryptor() # add pkcs#7 padding before encrypt padder = padding.PKCS7(128).padder() padded_data = padder.update(plain_bytes) + padder.finalize() return encryptor.update(padded_data) + encryptor.finalize()
更多代码可以参考 Information-Security