Android 中实现一个自定义的 AES 算法

版权归作者所有,如有转发,请注明文章出处: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 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个主要步骤:

  1. 字节代替 (SubBytes):使用 S-Box(替代盒)对每个字节进行非线性替换。

  2. 行移位 (ShiftRows):将数据分为4行,每行按特定规则循环移位。

  3. 列混合 (MixColumns):使用矩阵运算对每列的数据进行混合。

  4. 轮密钥加 (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...

相关推荐
每次的天空13 分钟前
Android Jetpack学习总结(源码级理解)
android·学习·android jetpack
啥都鼓捣的小yao16 分钟前
Python使用SVC算法解决乳腺癌数据集分类问题——寻找最佳核函数
python·算法·分类
木子庆五22 分钟前
Android设计模式之代理模式
android·设计模式·代理模式
在雨季等你38 分钟前
创业之旅 - 反思 - 整改 - 新的方向 - 诚邀
android
Long_poem1 小时前
【自学笔记】PHP语言基础知识点总览-持续更新
android·笔记·php
Java版蜡笔小新2 小时前
算法-贪心算法
算法·贪心算法
LuckyAnJo2 小时前
Leetcode-100 回溯法-电话号码的字母组合
python·算法·leetcode
HR Zhou2 小时前
群体智能优化算法-鹈鹕优化算法(Pelican Optimization Algorithm, POA,含Matlab源代码)
算法·机器学习·matlab·优化·群体智能优化
LuckyLay2 小时前
LeetCode算法题(Go语言实现)_20
算法·leetcode·职场和发展·golang
fatiaozhang95272 小时前
晶晨S905L3A(B)-安卓9.0-开启ADB和ROOT-支持IPTV6-支持外置游戏系统-支持多种无线芯片-支持救砖-完美通刷线刷固件包
android·游戏·adb·华为·电视盒子·机顶盒rom·魔百盒固件