前面的文字已经简单介绍了 AES ,这里就不在介绍了。
方法 1
你可以调用 Java 端接口实现。
这种方式这里就不介绍了。
方法 2
在 JNI 层加入 AES 的 C/C++ 算法,对外提供一个输入输出接口即可。
这里采用 tiny-AES-c
实现。tiny-AES-c
但是还有其它问题,tiny-AES-c
只支持基本数据大小的倍数的数据(AES 128 则是 128bit 即 16Byte 的整数倍)。
也就是 tiny-AES-c
没有 padding 功能。
在调用 tiny-AES-c
提供的 AES 加密前需要自行增加 padding 数据;而 AES 解密后需要自己移除 padding 数据。
先介绍 tiny-AES-c
的调用方式。
前提:
字段 | 参数 |
---|---|
填充模式 | CBC |
AES 协议 | 128 |
关于 tiny-AES-c
的文件怎么加入项目中,这里不做介绍(简单的 C 文件调用)。
简单的加密解密调用方式如下:
void encryptAES(uint8_t *in, const int len, const uint8_t *key, const uint8_t *iv) {
struct AES_ctx ctx;
AES_init_ctx_iv(&ctx, key, iv);
AES_CBC_encrypt_buffer(&ctx, in, outTmpLen);
}
void decryptAES(uint8_t *in, const int len, const uint8_t *key, const uint8_t *iv) {
struct AES_ctx ctx;
AES_init_ctx_iv(&ctx, key, iv);
AES_CBC_decrypt_buffer(&ctx, in, outTmpLen);
}
测试数据
const int DATA_LEN = 16;
uint8_t data[DATA_LEN] = {
0x01, 0x02, 0x33, 0x04, 0x05, 0x66, 0x07, 0x08,
0x99, 0x0A, 0x0B, 0xCC, 0x0D, 0x0E, 0xFF, 0x10,
};
const static uint8_t AES_KEY[] = "asdaiy8889qydkl."; // 必须 16 个(128bit)
const static uint8_t AES_IV[] = "H*Aa89a7(a977sda"; // 必须 16 个(128bit)
uint8_t encrypt_data[DATA_LEN];
memcpy(encrypt_data, data, DATA_LEN);
encryptAES(data, AES_KEY, AES_IV);
uint8_t decrypt_data[DATA_LEN];
memcpy(decrypt_data, encrypt_data, DATA_LEN);
decryptAES(encrypt_data, AES_KEY, AES_IV);
这里的限制有:
AES_KEY
和 AES_IV
都必须是 128bit (16byte),不足请自行填充。总的来说 tiny-AES-c
为了提高性能和降低资源占用,不得已把接口搞得非常“原始”。
调用者需要做更多的工作来处理不对齐的数据和“污染”输入数据的情况。
综上所述,JNI 调用 C 的 AES 库还是有比较大的工作量的。
这里给出个人的 padding 方式,仅供参考。
#define AES_BLOCKLEN 16 // AES-128 是 16 byte
static struct AES_ctx ms_ctx;
static int ms_ctx_init = 0;
struct AES_ctx * getAESctx() {
if (ms_ctx_init == 0) {
ms_ctx_init = 1;
AES_init_ctx_iv(p_ctx, AES_KEY, AES_IV);
}
return &ms_ctx;
}
// 计算需要 padding 的数量
int getNewLengthFromPKCS7Padding(int inLen) {
return ( AES_BLOCKLEN * (inLen / AES_BLOCKLEN + 1) );
}
// 根据新增 padding 的数量,重新分配 padding 后的数据大小。
uint8_t * mallocFromPKCS7Padding(uint8_t * data, int len, int * p_lenMax) {
int lenMax = getNewLengthFromPKCS7Padding(len); // 加上 padding 处理后的长度。
uint8_t * p_data = (uint8_t *) malloc(lenMax);
memcpy(p_data, data, len);
if (p_lenMax != NULL) {
*p_lenMax = lenMax;
}
return p_data;
}
// 增加 padding 数据(in 必须进过 mallocFromPKCS7Padding 申请 padding 后的空间)
int addPKCS7Padding(uint8_t * in, int inLen, int inLenMax) {
int outLen = getNewLengthFromPKCS7Padding(inLen);
if (inLenMax < outLen) {
return -1;
}
uint8_t padVal = AES_BLOCKLEN - (inLen % AES_BLOCKLEN);
uint8_t * ptr = in + inLen;
int count = padVal;
while(count-- > 0) {
*ptr++ = padVal;
}
return outLen;
}
// 移除 padding 数据
int removePKCS7Padding(uint8_t * in, int inLen) {
// pad 值为 [ 0x01 ~ 0x10 ], padding 后的数据必然是 16 的倍数,会填充 pad 个值为 pad 的数据。
uint8_t padVal = in[inLen - 1];
if ((padVal < 1) /* 0x01 */
|| (padVal > AES_BLOCKLEN) /* 0x10 */
|| (padVal > inLen) /* 填充数据量不足 */
|| ((inLen % AES_BLOCKLEN) != 0) /* 不是 16 的倍数 */
|| (padVal != in[inLen - padVal]) /* 非连续填充数据 */
) {
return inLen; // 没有 padding ,不需要处理
}
for (int i = padVal; i > 1; -- i) {
if (padVal != in[inLen - i]) {
return inLen;
}
}
int outLen = inLen - padVal;
return outLen;
}
那么增加 padding 处理后的 AES 加密解密为:
static int encryptAES(struct AES_ctx * ctx, uint8_t * in, int inLen, int inLenMax) {
struct AES_ctx * p_ctx = getAESctx(ctx);
int outTmpLen = addPKCS7Padding(in, inLen, inLenMax);
if (outTmpLen < 0) { // 如果采用 padding 填充,必须有足够的空间
return outTmpLen;
}
AES_CBC_encrypt_buffer(p_ctx, in, outTmpLen);
return outTmpLen;
}
static int decryptAES(struct AES_ctx * ctx, uint8_t * in, int inLen) {
AES_CBC_decrypt_buffer(p_ctx, in, inLen);
int outTmpLen = removePKCS7Padding(in, inLen);
if (outTmpLen < inLen) {
in[outTmpLen] = 0;
}
return outTmpLen;
}
因为 tiny-AES-c
加密解密会污染输入数据,且没有处理 padding。
所以增加 padding 处理的同时,通过重新分配资源分离输入数据,也就避免了污染的问题。