JNI AES 文件加密解密实现

版权声明:转载请写明出处,谢谢! https://blog.csdn.net/wilson1068/article/details/87479935

JNI AES 文件加密解密实现

AES (高级加密标准)

前面的文字已经简单介绍了 AES ,这里就不在介绍了。

AES(高级加密标准)

JNI 层实现 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

先介绍 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);

这里的限制有:

  1. AES_KEYAES_IV 都必须是 128bit (16byte),不足请自行填充。
  2. 加密解密的数据必须是 128bit (16byte) 的整数倍,不足请自行填充。
  3. 加密结果直接占用传入的待加密数组中。解密也是占用传入的待解密数组。

总的来说 tiny-AES-c 为了提高性能和降低资源占用,不得已把接口搞得非常“原始”。

调用者需要做更多的工作来处理不对齐的数据和“污染”输入数据的情况。

JNI 实现 AES

综上所述,JNI 调用 C 的 AES 库还是有比较大的工作量的。

实现 padding

这里给出个人的 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 处理的同时,通过重新分配资源分离输入数据,也就避免了污染的问题。

猜你喜欢

转载自blog.csdn.net/wilson1068/article/details/87479935