版权归作者所有,如有转发,请注明文章出处:cyrus-studio.github.io/blog/
前言
AES(Advanced Encryption Standard,高级加密标准) 是一种 对称加密算法,用于加密和解密数据。AES 由 美国国家标准与技术研究院(NIST) 在 2001 年正式发布,取代了 DES 和 3DES,目前被广泛应用于 网络安全、金融、通信 等领域。
支持 128(16字节)、192(24字节)、256(32字节) 位密钥。
特性 | 描述 |
---|---|
加密方式 | 对称加密(加密和解密使用相同的密钥) |
分组长度 | 128 位(16 字节) |
密钥长度 | 128 位、192 位、256 位(分别对应 10、12、14 轮加密) |
安全性 | 目前无已知有效攻击,比 3DES 更安全 |
运算模式 | 支持 ECB、CBC、CFB、OFB、CTR 等模式 |
关于算法的详细介绍可以参考这篇文章:常用加解密算法介绍
LibTomCrypt
LibTomCrypt 是一个开源的轻量级加密库,提供了多种加密算法和密码学相关功能。它是用 C 语言编写的,专注于嵌入式系统和资源受限的设备,非常适合在 Android 等平台上使用。
特点:
-
算法支持丰富:包括对称加密(AES、DES 等)、非对称加密(RSA、ECC 等)、哈希算法(SHA-256、MD5 等)
-
轻量级,代码简单。
-
支持多种加密模式:ECB、CBC、CFB、OFB 等。
适用场景: 如果你需要一个开源、可自定义的库。
集成 LibTomCrypt 到 Android 工程
在 Android Studio 中集成 LibTomCrypt 并调用 JNI,可以按以下步骤操作:
1. 下载 LibTomCrypt
从官方 GitHub 仓库下载最新的源码:
解压后,将整个 libtomcrypt/src 文件夹复制到 Android 工程中的 app/src/main/cpp 目录下。
2. 配置 CMakeLists.txt
在 app/src/main/cpp/CMakeLists.txt 中添加以下配置:
bash
# 添加 libtomcrypt 头文件路径
include_directories(libtomcrypt/src/headers)
# 查找 libtomcrypt 所有的 C 文件
file(GLOB LIBTOMCRYPT_SOURCES
libtomcrypt/src/*.c
libtomcrypt/src/misc/*.c
libtomcrypt/src/misc/crypt/*.c
libtomcrypt/src/ciphers/aes/*.c
libtomcrypt/src/modes/cbc/*.c
libtomcrypt/src/modes/cfb/*.c
libtomcrypt/src/modes/ctr/*.c
libtomcrypt/src/modes/ecb/*.c
)
# 编译 LibTomCrypt
add_library(libtomcrypt STATIC ${LIBTOMCRYPT_SOURCES})
使用 LibTomCrypt 的实现 AES CBC 加解密
注意:
-
数据对齐:AES CBC 模式要求输入数据的长度是 16 的倍数,如果不是,需要使用 PKCS7 Padding 或其他填充方式。。
-
Key 和 IV 长度:AES 支持 128、192、256 位密钥,Key 长度需要符合实际配置。
LibTomCrypt 本身不提供直接的 PKCS5Padding 实现,但你可以自己实现它。
PKCS5Padding 的规则是:
-
每个填充值的值等于填充字节的数量。
-
如果数据刚好是 16 字节的倍数,则会再添加一整块填充。
-
填充值范围:0x01 ~ 0x10。
填充函数实现(PKCS5Padding)
ini
void pkcs5_pad(uint8_t *data, int dataLen, int blockSize, int *paddedLen) {
int padding = blockSize - (dataLen % blockSize);
for (int i = 0; i < padding; i++) {
data[dataLen + i] = (uint8_t)padding;
}
*paddedLen = dataLen + padding;
}
-
data:原始数据缓冲区,确保其有足够的空间存储填充后的数据。
-
dataLen:原始数据长度。
-
blockSize:通常为 16(AES)。
-
paddedLen:返回填充后的总长度。
去除填充函数(PKCS5UnPadding)
kotlin
int pkcs5_unpad(uint8_t *data, int dataLen) {
if (dataLen <= 0) return 0;
uint8_t padding = data[dataLen - 1];
if (padding > dataLen || padding > 16) return -1; // 不合法的填充
for (int i = 0; i < padding; i++) {
if (data[dataLen - 1 - i] != padding) return -1; // 填充不一致
}
return dataLen - padding;
}
-
data:解密后的数据。
-
dataLen:解密后的数据长度。
-
返回值:去除填充后的实际数据长度。
AES CBC 加密
方法解析
-
register_cipher:确保 AES 算法已注册(返回 index != -1)
-
cbc_start:初始化 CBC 模式
-
cbc_encrypt:加密数据
-
cbc_decrypt:解密数据
-
cbc_done:清理资源
scss
// AES CBC 加密方法
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_cyrus_example_aes_NativeAESUtils_aesCBCEncode(JNIEnv *env, jclass clazz, jbyteArray data) {
uint8_t *dataBytes = (uint8_t *) env->GetByteArrayElements(data, nullptr);
int dataLen = env->GetArrayLength(data);
// 转换密钥和 IV 为字节数组
uint8_t *keyBytes = stringToSecretKey(KEY);
uint8_t *ivBytes = stringToIV(IV);
// PKCS5Padding
int paddedLen = dataLen + 16 - (dataLen % 16);
uint8_t *paddedData = (uint8_t *) malloc(paddedLen);
memcpy(paddedData, dataBytes, dataLen);
pkcs5_pad(paddedData, dataLen, 16, &paddedLen);
// 加密
int cipher_index = register_cipher(&aes_desc);
symmetric_CBC cbc;
if (cbc_start(cipher_index, ivBytes, keyBytes, AES_KEYLEN, 0, &cbc) != CRYPT_OK) {
LOGD("cbc_start failed.");
free(paddedData);
return nullptr;
}
uint8_t *output = (uint8_t *) malloc(paddedLen);
if (cbc_encrypt(paddedData, output, paddedLen, &cbc) != CRYPT_OK) {
LOGD("cbc_encrypt failed.");
free(paddedData);
free(output);
return nullptr;
}
cbc_done(&cbc);
jbyteArray result = env->NewByteArray(paddedLen);
env->SetByteArrayRegion(result, 0, paddedLen, reinterpret_cast<jbyte *>(output));
// 释放资源
env->ReleaseByteArrayElements(data, (jbyte *) dataBytes, 0);
free(paddedData);
free(output);
// 释放资源
delete[] keyBytes;
delete[] ivBytes;
return result;
}
效果如下:
AES CBC 解密
scss
// AES CBC 解密方法
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_cyrus_example_aes_NativeAESUtils_aesCBCDecode(JNIEnv *env, jclass clazz, jbyteArray data) {
// 获取数据
uint8_t *dataBytes = (uint8_t *) env->GetByteArrayElements(data, nullptr);
int dataLen = env->GetArrayLength(data);
// 注册 AES 算法
if (register_cipher(&aes_desc) == -1) {
env->ReleaseByteArrayElements(data, (jbyte *) dataBytes, 0);
return nullptr;
}
// 转换密钥和 IV 为字节数组
uint8_t *keyBytes = stringToSecretKey(KEY);
uint8_t *ivBytes = stringToIV(IV);
// 解密
int cipher_index = register_cipher(&aes_desc);
symmetric_CBC cbc;
if (cbc_start(cipher_index, ivBytes, keyBytes, AES_KEYLEN, 0, &cbc) != CRYPT_OK) {
LOGD("cbc_start failed.");
return nullptr;
}
uint8_t *output = (uint8_t *) malloc(dataLen);
if (cbc_decrypt(dataBytes, output, dataLen, &cbc) != CRYPT_OK) {
LOGD("cbc_decrypt failed.");
free(output);
return nullptr;
}
cbc_done(&cbc);
// 去除 PKCS5Padding
int unpaddedLen = pkcs5_unpad(output, dataLen);
if (unpaddedLen < 0) {
LOGD("PKCS5 unpadding failed.");
free(output);
return nullptr;
}
// 返回结果
jbyteArray result = env->NewByteArray(unpaddedLen);
env->SetByteArrayRegion(result, 0, unpaddedLen, reinterpret_cast<jbyte *>(output));
// 释放资源
free(output);
env->ReleaseByteArrayElements(data, (jbyte *) dataBytes, 0);
delete[] keyBytes;
delete[] ivBytes;
return result;
}
效果如下:
完整代码
scss
#include <jni.h>
#include <android/log.h>
#include <tomcrypt.h>
#define AES_BLOCKLEN 16 // Block length in bytes AES is 128 b block only
#define AES_KEYLEN 16 // Key length in bytes
#define LOG_TAG "aes_jni.cpp"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
const char * KEY = "CYRUS STUDIO ";
const char * IV = "CYRUS STUDIO ";
// 将 const char* 转换为字节数组 (IV)
uint8_t *stringToIV(const char *str) {
auto iv = new uint8_t[AES_BLOCKLEN];
for (int i = 0; i < AES_BLOCKLEN; ++i) {
iv[i] = static_cast<uint8_t>(str[i]);
}
return iv;
}
// 将 const char* 转换为字节数组 (Key)
uint8_t *stringToSecretKey(const char *str) {
auto key = new uint8_t[AES_KEYLEN];
for (int i = 0; i < AES_KEYLEN; ++i) {
key[i] = static_cast<uint8_t>(str[i]);
}
return key;
}
void pkcs5_pad(uint8_t *data, int dataLen, int blockSize, int *paddedLen) {
int padding = blockSize - (dataLen % blockSize);
for (int i = 0; i < padding; i++) {
data[dataLen + i] = (uint8_t)padding;
}
*paddedLen = dataLen + padding;
}
int pkcs5_unpad(uint8_t *data, int dataLen) {
if (dataLen <= 0) return 0;
uint8_t padding = data[dataLen - 1];
if (padding > dataLen || padding > 16) return -1; // 不合法的填充
for (int i = 0; i < padding; i++) {
if (data[dataLen - 1 - i] != padding) return -1; // 填充不一致
}
return dataLen - padding;
}
// AES CBC 加密方法
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_cyrus_example_aes_NativeAESUtils_aesCBCEncode(JNIEnv *env, jclass clazz, jbyteArray data) {
uint8_t *dataBytes = (uint8_t *) env->GetByteArrayElements(data, nullptr);
int dataLen = env->GetArrayLength(data);
// 转换密钥和 IV 为字节数组
uint8_t *keyBytes = stringToSecretKey(KEY);
uint8_t *ivBytes = stringToIV(IV);
// PKCS5Padding
int paddedLen = dataLen + 16 - (dataLen % 16);
uint8_t *paddedData = (uint8_t *) malloc(paddedLen);
memcpy(paddedData, dataBytes, dataLen);
pkcs5_pad(paddedData, dataLen, 16, &paddedLen);
// 加密
int cipher_index = register_cipher(&aes_desc);
symmetric_CBC cbc;
if (cbc_start(cipher_index, ivBytes, keyBytes, AES_KEYLEN, 0, &cbc) != CRYPT_OK) {
LOGD("cbc_start failed.");
free(paddedData);
return nullptr;
}
uint8_t *output = (uint8_t *) malloc(paddedLen);
if (cbc_encrypt(paddedData, output, paddedLen, &cbc) != CRYPT_OK) {
LOGD("cbc_encrypt failed.");
free(paddedData);
free(output);
return nullptr;
}
cbc_done(&cbc);
jbyteArray result = env->NewByteArray(paddedLen);
env->SetByteArrayRegion(result, 0, paddedLen, reinterpret_cast<jbyte *>(output));
// 释放资源
env->ReleaseByteArrayElements(data, (jbyte *) dataBytes, 0);
free(paddedData);
free(output);
// 释放资源
delete[] keyBytes;
delete[] ivBytes;
return result;
}
// AES CBC 解密方法
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_cyrus_example_aes_NativeAESUtils_aesCBCDecode(JNIEnv *env, jclass clazz, jbyteArray data) {
// 获取数据
uint8_t *dataBytes = (uint8_t *) env->GetByteArrayElements(data, nullptr);
int dataLen = env->GetArrayLength(data);
// 注册 AES 算法
if (register_cipher(&aes_desc) == -1) {
env->ReleaseByteArrayElements(data, (jbyte *) dataBytes, 0);
return nullptr;
}
// 转换密钥和 IV 为字节数组
uint8_t *keyBytes = stringToSecretKey(KEY);
uint8_t *ivBytes = stringToIV(IV);
// 解密
int cipher_index = register_cipher(&aes_desc);
symmetric_CBC cbc;
if (cbc_start(cipher_index, ivBytes, keyBytes, AES_KEYLEN, 0, &cbc) != CRYPT_OK) {
LOGD("cbc_start failed.");
return nullptr;
}
uint8_t *output = (uint8_t *) malloc(dataLen);
if (cbc_decrypt(dataBytes, output, dataLen, &cbc) != CRYPT_OK) {
LOGD("cbc_decrypt failed.");
free(output);
return nullptr;
}
cbc_done(&cbc);
// 去除 PKCS5Padding
int unpaddedLen = pkcs5_unpad(output, dataLen);
if (unpaddedLen < 0) {
LOGD("PKCS5 unpadding failed.");
free(output);
return nullptr;
}
// 返回结果
jbyteArray result = env->NewByteArray(unpaddedLen);
env->SetByteArrayRegion(result, 0, unpaddedLen, reinterpret_cast<jbyte *>(output));
// 释放资源
free(output);
env->ReleaseByteArrayElements(data, (jbyte *) dataBytes, 0);
delete[] keyBytes;
delete[] ivBytes;
return result;
}
AES ECB 模式
ECB 模式没有 IV,因此只需要密钥即可。
scss
// AES ECB 加密
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_cyrus_example_aes_NativeAESUtils_aesECBEncode(
JNIEnv *env,
jclass clazz,
jbyteArray data
) {
uint8_t *dataBytes = (uint8_t *) env->GetByteArrayElements(data, nullptr);
int dataLen = env->GetArrayLength(data);
// PKCS5Padding
int paddedLen = dataLen + 16 - (dataLen % 16);
uint8_t *paddedData = (uint8_t *) malloc(paddedLen);
memcpy(paddedData, dataBytes, dataLen);
pkcs5_pad(paddedData, dataLen, 16, &paddedLen);
// 设置 AES ECB
symmetric_ECB ecb;
int cipher_index = register_cipher(&aes_desc);
// 转换密钥为字节数组
uint8_t *keyBytes = stringToSecretKey(KEY);
if (ecb_start(cipher_index, keyBytes, AES_KEYLEN, 0, &ecb) != CRYPT_OK) {
LOGD("ecb_start failed.");
delete[] keyBytes;
free(paddedData);
return nullptr;
}
// 加密
uint8_t *output = (uint8_t *) malloc(paddedLen);
if (ecb_encrypt(paddedData, output, paddedLen, &ecb) != CRYPT_OK) {
LOGD("ecb_encrypt failed.");
delete[] keyBytes;
free(paddedData);
free(output);
return nullptr;
}
ecb_done(&ecb);
jbyteArray result = env->NewByteArray(paddedLen);
env->SetByteArrayRegion(result, 0, paddedLen, reinterpret_cast<jbyte *>(output));
// 释放资源
delete[] keyBytes;
free(paddedData);
free(output);
env->ReleaseByteArrayElements(data, (jbyte *) dataBytes, 0);
return result;
}
// AES ECB 解密
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_cyrus_example_aes_NativeAESUtils_aesECBDecode(
JNIEnv *env,
jclass clazz,
jbyteArray data
) {
uint8_t *dataBytes = (uint8_t *) env->GetByteArrayElements(data, nullptr);
int dataLen = env->GetArrayLength(data);
// 初始化 ECB
symmetric_ECB ecb;
int cipher_index = register_cipher(&aes_desc);
// 转换密钥为字节数组
uint8_t *keyBytes = stringToSecretKey(KEY);
if (ecb_start(cipher_index, keyBytes, AES_KEYLEN, 0, &ecb) != CRYPT_OK) {
LOGD("ecb_start failed.");
delete[] keyBytes;
return nullptr;
}
// 解密
uint8_t *output = (uint8_t *) malloc(dataLen);
if (ecb_decrypt(dataBytes, output, dataLen, &ecb) != CRYPT_OK) {
LOGD("ecb_decrypt failed.");
delete[] keyBytes;
free(output);
return nullptr;
}
ecb_done(&ecb);
// 去除 PKCS5Padding
int unpaddedLen = pkcs5_unpad(output, dataLen);
if (unpaddedLen < 0) {
LOGD("PKCS5 unpadding failed.");
free(output);
return nullptr;
}
jbyteArray result = env->NewByteArray(unpaddedLen);
env->SetByteArrayRegion(result, 0, unpaddedLen, reinterpret_cast<jbyte *>(output));
// 释放资源
delete[] keyBytes;
free(output);
env->ReleaseByteArrayElements(data, (jbyte *) dataBytes, 0);
return result;
}
效果如下:
AES CTR 模式
CTR 模式需要 IV 和 Key,并且加密和解密使用相同的逻辑。
scss
// AES CTR 加密
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_cyrus_example_aes_NativeAESUtils_aesCTREncode(
JNIEnv *env,
jclass clazz,
jbyteArray data
) {
uint8_t *dataBytes = (uint8_t *) env->GetByteArrayElements(data, nullptr);
int dataLen = env->GetArrayLength(data);
// 设置 AES CTR
symmetric_CTR ctr;
int cipher_index = register_cipher(&aes_desc);
// 转换密钥和 IV 为字节数组
uint8_t *keyBytes = stringToSecretKey(KEY);
uint8_t *ivBytes = stringToIV(IV);
if (ctr_start(cipher_index, ivBytes, keyBytes, AES_KEYLEN, 0, CTR_COUNTER_LITTLE_ENDIAN, &ctr) != CRYPT_OK) {
LOGD("ctr_start failed.");
delete[] keyBytes;
delete[] ivBytes;
return nullptr;
}
// 加密 / 解密
uint8_t *output = (uint8_t *) malloc(dataLen);
if (ctr_encrypt(dataBytes, output, dataLen, &ctr) != CRYPT_OK) {
LOGD("ctr_encrypt failed.");
delete[] keyBytes;
delete[] ivBytes;
free(output);
return nullptr;
}
ctr_done(&ctr);
jbyteArray result = env->NewByteArray(dataLen);
env->SetByteArrayRegion(result, 0, dataLen, reinterpret_cast<jbyte *>(output));
// 释放资源
delete[] keyBytes;
delete[] ivBytes;
free(output);
env->ReleaseByteArrayElements(data, (jbyte *) dataBytes, 0);
return result;
}
// AES CTR 解密
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_cyrus_example_aes_NativeAESUtils_aesCTRDecode(
JNIEnv *env,
jclass clazz,
jbyteArray data
) {
return Java_com_cyrus_example_aes_NativeAESUtils_aesCTREncode(env, clazz, data);
}
效果如下:
AES 工作原理
AES 的加密过程分为多个轮次,每轮包含以下4个主要步骤:
-
字节代替 (SubBytes):使用 S-Box(替代盒)对每个字节进行非线性替换。
-
行移位 (ShiftRows):将数据分为4行,每行按特定规则循环移位。
-
列混合 (MixColumns):使用矩阵运算对每列的数据进行混合。
-
轮密钥加 (AddRoundKey):使用密钥进行 XOR 运算。
加密轮次:
-
128位密钥:10轮
-
192位密钥:12轮
-
256位密钥:14轮
解密过程与加密过程类似,只是顺序相反,并使用逆S-Box和逆变换。
实现 AES 算法变体
在 LibTomCrypt 中,AES 算法的 S-Box(替代盒)是通过查找表(lookup table)实现的,而 AES 的表(如 S-Box)主要是存在于 src/cipher/aes/aes_tab.c 文件中。
该 S-Box 是一个 256 字节的查找表,用于将每个输入字节替换成加密的字节。
理论上,你可以通过修改 LibTomCrypt 中的 S-Box 或其他查找表,来实现你自己的 AES 变体。
比如,把 0xc66363a5UL 改成 0xaa6363a5UL。
AES 算法变体加密结果:
标准 AES 算法加密结果:
可以看到加密结果已经和标准的 AES 不一样了,解密结果还是一样的。
完整源码
完整源码地址:github.com/CYRUS-STUDI...