分组密码-AES
本章主要是学习AES如何实现,如何对java中的AES方法进行hook,以及定位so文件中的AES
这里以AES-128为例,会对明文分组进行10轮迭代运算,加密的第1轮到第9轮的函数一样,包括4个操作:字节替换、行位移、列混合和轮密钥加。最后一轮迭代不执行行列混合。另外,在第一轮迭代之前,先将明文和原始密钥进行一次异或加密操作。
AES加解密理论
AES的加解密,跟DES一样由左右两部分组成
左边是对明文(16字节)进行的一个10轮的迭代处理
第一轮到第9轮每一轮都有四个操作:
AddRoundKey 为 异或
SubBytes 为 查表替换
ShiftRows 为 按字节循环左移
MixColumns 为 矩阵乘法
右边是对密钥(16字节)进行编排,生成每一轮对应的子密钥。
查表替换(字节替换、字节代换)
查表替换的主要功能是通过S盒完成一个字节到另一个字节的映射。本质上就是一个查表操作。有两个表一个是S盒一个是S盒逆。S盒用于提高密码算法的混淆性,S盒是快速判断AES加密算法的最有效的特征。
AES算法流程分析
这里介绍的是AES-128,也就是密钥的长度为128位,加密轮数为10轮
AES的加密公式为**C = E(K,P)**,在加密函数E中,会执行一个函数,并且执行10次这个轮函数,这个轮函数的前9次执行的操作是一样的,只有第10次有所不同。也就是说,一个明文分组会被加密10轮。AES的核心就是实现一轮中的所有操作。
明文矩阵填充
AES的处理单位是字节,128位的输入明文分组P和输入密钥K都被分成16个字节,分别记为P = P0 P1 … P15和 K = K0 K1 … K15。如,明文分组为P = abcdefghijklmnop,其中的字符a对应P0,p对应P15。一般地,明文分组用字节为单位的正方形矩阵描述,称为状态矩阵。在算法的每一轮中,状态矩阵的内容不断发生变化,最后的结果作为密文输出。该矩阵中字节的排列顺序为从上到下、从左至右依次排列。
密钥矩阵填充
类似地,128位密钥也是用字节为单位的矩阵表示,矩阵的每一列被称为1个32位比特字。通过密钥编排函数该密钥矩阵被扩展成一个44个字组成的序列W[0],W[1], … ,W[43],该序列的前四个元素W[0],W[1],W[2],W[3]是原始密钥,用于加密运算中的初始密钥(下面介绍);后面40个字分为10组,每组4个字(4x4x8 = 128bit)分别用于10轮加密运算中的轮密钥加。后面的10组密钥通过初始密钥进行密钥扩展得到。
加解密流程
AES加解密主要要以下几步操作,完整的加解密流程是通过循环以下步骤来完成的,具体如下图表示
- 轮密钥加
- 字节替换
- 行位移
- 列混合
加解密步骤分析
字节替换
AES的字节替换其实就是通过一个简单的查表操作来进行非线性运算。AES定义了一个S盒和一个逆S盒。分别用来加解密操作。
状态矩阵中的元素按照下面的方式映射为一个新的字节:把该字节的高4位作为行值,低4位作为列值,取出S盒或者逆S盒中对应的行的元素作为输出。例如,加密时输出的字节S1=0X12,则查找S盒的第0X01行和0X02列,得到值0XC9,然后替换S1原有的值0X12为0XC9。状态矩阵经字节代换后的图如下
逆字节代换也就是查逆S盒来变换,逆S盒如下
行移位
行移位是一个简单的左循环移位操作。当密钥长度为128bit时,状态矩阵的第0行左移0字节,第1行左移1字节,第2行左移2字节。。。如下图所示
行移位的逆变换是将状态矩阵中的每一行执行相反的移位操作,例如AES-128中,状态矩阵的第0行右移0字节,第1行右移1字节,第2行右移2字节。。。
列混合
列混合变换是通过矩阵相乘来实现的,经行移位后的状态矩阵与固定的矩阵相乘,得到混淆后的状态矩阵,如下图的公式所示
状态矩阵中的第j列(0 ≤j≤3)的列混合可以表示为下图所示
其中,矩阵运算的乘法和加法都是定义在基于GF(2^8)上的二元运算,并不是通常意义上的乘法和加法。
逆向列混合变换同样可由下图的矩阵乘法定义
轮密钥加
轮密钥加是将128位轮密钥Ki同状态矩阵中的数据进行逐位异或操作,如下图所示。其中,密钥Ki中每个字W[4i],W[4i+1],W[4i+2],W[4i+3]为32位bit字,包含4个字节,轮密钥加过程可以看成是字逐位异或的结果,也可以看成字节级别或者位级别的操作。也就是说,可以看成S0、S1、S2、S3组成的32位字与W[4i]的异或运算。
密钥扩展
AES首先将初始密钥输入到一个4×4的状态矩阵中,如下图所示
4×4矩阵的每一列的4个字节组成一个字,矩阵4列的4个字依次命名为W[0]、W[1]、W[2]、W[3],它们构成一个以字为单位的数组W。
接着,对W数组扩充40个新列,构成总共44列的扩展密钥数组。新列一如下的递归方式产生
如果i不是4的倍数,那么i列由如下等式确定:
$W[i]=W[i-4]⨁W[i-1]$
如果i是4的倍数,那么第i列由如下等式确定:
$W[i]=W[i-4]⨁T(W[i-1])$
T是一个有点复杂的函数,由3部分组成:自循环、字节代换和轮常量异或,这3部分的作用分别如下
- 字循环:将1个字中的4个字节循环左移一个字节。即将输入字[b0, b1, b2, b3]变换成[b1,b2,b3,b0]
- 字节代换:对字循环的结果使用S盒进行字节代换
- 轮常量异或:将前两步的结果同轮常量Rcon[j]进行异或,其中j表示轮数
轮常量Rcon[j]是一个字,其值见下表
j | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
Rcon[j] | 01 00 00 00 | 02 00 00 00 | 04 00 00 00 | 08 00 00 00 | 10 00 00 00 |
j | 6 | 7 | 8 | 9 | 10 |
Rcon[j] | 20 00 00 00 | 40 00 00 00 | 80 00 00 00 | 1B 00 00 00 | 36 00 00 00 |
总的来说,密钥扩展的复杂性是确保算法安全性的重要部分。当分组长度和密钥长度都是128bit时,AES的加密算法共迭代10轮,每一轮最后要与一个子密钥进行轮密钥加,再加上最开始的一次轮密钥加,即一个分组共需要扩展为11个128bit的轮密钥。AES的密钥扩展算法是以字节为一个基本单位(一个字为4个字节),刚好是密钥矩阵的一列。因此4个字(128bit)密钥需要扩展成11个子密钥,共44个字。
AES hook
对java层的AES进行hook
编写hook脚本通过参数以及返回值获取到加解密前后以及key的值
package com.xiaoeryu.aes;
import android.os.Build;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
public class AES {
public static String encrypt(String content, String sKey) throws UnsupportedEncodingException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
if (sKey == null) {
System.out.print("Key is null");
return null;
}
// Determine the key length
if (sKey.length() != 16) {
System.out.print("Key length is not 16");
return null;
}
byte[] raw = sKey.getBytes("utf-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // "算法/模式/补码方式"
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(content.getBytes("utf-8"));
String result = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
result = Base64.getEncoder().encodeToString(encrypted);
} else {
result = android.util.Base64.encodeToString(encrypted, android.util.Base64.DEFAULT);
}
return result;
}
public static String decrypt(String content, String sKey) throws UnsupportedEncodingException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
if (sKey == null) {
System.out.print("Key is null");
return null;
}
if (sKey.length() != 16) {
System.out.print("Key length is not 16");
return null;
}
byte[] raw = sKey.getBytes("utf-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] encrypted = android.util.Base64.decode(content, android.util.Base64.DEFAULT);
byte[] original = cipher.doFinal(encrypted);
String originalString = new String(original, "utf-8");
return originalString;
}
}
- 对java层的AES进行hook跟之前hook DES使用的方法基本都是一样的,区别只是在于它们实现的时候调用的方法不同而已。所以相同的代码更改一下hook的函数和重载就可以了
定位SO中的AES算法
跟之前一样,使用findcrypt插件直接就可以根据S盒定位到算法的位置,如果定位不到可能是因为插件中没有S盒的特征跟上一章同样的方法添加进去即可。
- 通过交叉引用定位到调用S盒的函数
- 跟进去分析
跟据汇编代码可知,这里是根据在获取S盒的值
附件:
定位SO中AES算法的测试demo
- 标题: 分组密码-AES
- 作者: xiaoeryu
- 创建于 : 2024-02-29 11:05:35
- 更新于 : 2024-03-01 18:01:29
- 链接: https://github.com/xiaoeryu/2024/02/29/分组密码-AES/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。