jni使用openssl AES256位加解密(cbc模式),匹配java后端服务器算法,解决末尾乱码问题

13 篇文章 0 订阅
9 篇文章 0 订阅

前言:以下代码中统一的AES加密方式为”AES/CBC/PKCS7PADDING”,IV参数为”0102030405060708”(java中转为了byte数组,具体值看代码),之所以用CBC是因为它比ECB更安全
在使用openssl编写AES加解密算法代码时,发现c语言的AES加解密和JAVA的加解密并不能匹配,也就是说c语言加密的用c语言能解密,但是用java却解密不了,反之亦然;仔细对比发现,java中可以明确指定PKCS7PADDING,而C语言中(openssl)却没有相关函数实现,所以只能自己编写PKCS7PADDING填充算法,PCKS7PADDING填充代码如下:

unsigned char iv[17] = AES_CBC_IV;
    AES_KEY aes;
    int nLen = plainInLen;

    //16的倍数,AES_BLOCK_SIZE等于16,aes加密为每16个长度的区域为一个数据块
    int nBei = nLen / AES_BLOCK_SIZE + 1;
    int nTotal = nBei * AES_BLOCK_SIZE;
    char *enc_s = (char*)malloc((size_t) (nTotal + 1));
    //清空内存区域
    memset(enc_s, '\0', (size_t) (nTotal + 1));
    //计算PKCS7PADDING的值
    int nNumber;
    if (nLen % 16 > 0) {
        nNumber = nTotal - nLen;
    }
    else {
        nNumber = 16;
    }
    //进行PKCS7PADDING填充
    memset(enc_s, nNumber, nTotal);
    //将需要加密的数据放到数据块中
    memcpy(enc_s, plainIn, nLen);

PKCS7PADDING填充原理:AES来说,因其属于分组加密,且每组数据为16字节(AES_BLOCK_SIZE,其为openssl中宏定义的值),故当明文不为16的整数倍时,就需要对明文做填充,填充到16的整数倍,设明文的长度为nLen ,则分组数为int nBei = nLen / AES_BLOCK_SIZE + 1,填充后的长度为nTotal,需要填充的值为nNumber,其值为“当明文长度刚好整除16,则填充值为16,填充的长度也为16,当明文长度不能整除16时,nNumber = nTotal - nLen,填充的长度也为nTotal - nLen,也就是说填充的长度与填充的值是相等的”;
对明文进行填充然后加密后的密文,经过解密得到的结果当然也是填充过后的明文,并不是真正的明文,当然需要去掉填充的内容,不然末尾就会出现乱码的(因为填充了嘛),只需要知道填充用的方式,便可以解决乱码问题了,具体可以看解密部分代码decryptLen变量的使用,decryptLen变量的值就是真实明文的长度,只需要对解密后的结果取decryptLen长度就是真实明文,没有乱码了
最后贴上完整的openssl代码(jni部分用了动态注册方式,故jni函数不带java类的签名)和java代码,此代码能够让c语言和java后端之间互相加解密:

int aes_decrypt(char *cryptoIn, int cryptoInLen, char *key, char *plainOut, int *outl)
{
    char iv1[17] = AES_CBC_IV;
    if(!cryptoIn || !key || !plainOut) return 0;

    AES_KEY aes;
    if(AES_set_decrypt_key((unsigned char*)key, 256, &aes) < 0)
    {
        return 0;
    }
    AES_cbc_encrypt((unsigned char*)cryptoIn, (unsigned char*)plainOut, cryptoInLen, &aes, (unsigned char *) iv1, AES_DECRYPT);
    /**去掉padding字符串**/
    //获取padding后的明文长度
    int padLen = cryptoInLen;
    //获取pad的值
    int padValue = plainOut[padLen - 1];
    int b = AES_BLOCK_SIZE;
    //与opensslEVP_DecryptFinal_ex函数的if (n == 0 || n > (int)b) {判断一致
    if (padValue == 0 || padValue > AES_BLOCK_SIZE) {
        //错误的padding,解密失败
        return 0;
    }
    *outl = padLen - padValue;
    return 1;
}

/**
 * 加密测试
 * @param plainIn 明文输入
 * @param plainInLen 明文长度
 * @param key key密钥
 * @param cryptoOut 密文输出
 * @param cryptoOutLen 密文长度
 * @return 非0:加密成功,0:失败
 */
int aes_encrypt(char *plainIn, int plainInLen, char *key, char *cryptoOut, size_t *cryptoOutLen) {
    unsigned char iv[17] = AES_CBC_IV;
    AES_KEY aes;
    int nLen = plainInLen;

    //16的倍数,AES_BLOCK_SIZE等于16,aes加密为每16个长度的区域为一个数据块
    int nBei = nLen / AES_BLOCK_SIZE + 1;
    int nTotal = nBei * AES_BLOCK_SIZE;
    char *enc_s = (char*)malloc((size_t) (nTotal + 1));
    //清空内存区域
    memset(enc_s, '\0', (size_t) (nTotal + 1));
    //计算PKCS7PADDING的值
    int nNumber;
    if (nLen % 16 > 0) {
        nNumber = nTotal - nLen;
    }
    else {
        nNumber = 16;
    }
    //进行PKCS7PADDING填充
    memset(enc_s, nNumber, nTotal);
    //将需要加密的数据放到数据块中
    memcpy(enc_s, plainIn, nLen);

    if (AES_set_encrypt_key((unsigned char*)key, 256, &aes) < 0) {
        fprintf(stderr, "Unable to set encryption key in AES\n");
        goto error;
    }

    AES_cbc_encrypt((unsigned char *)enc_s, (unsigned char*)cryptoOut, nBei * 16, &aes, (unsigned char*)iv, AES_ENCRYPT);
    *cryptoOutLen = (size_t) (nBei * 16);
//    std::string enstr;
//    enstr.assign(cryptoOut, (unsigned int) (nBei * 16));
//    std::string base64 = TDBASE64::base64_encodestring(enstr);
//    LOGD("aes_encrypt_test加密结果:%s",base64.c_str());
    free(enc_s);
    return 1;
    error:
    free(enc_s);
    return 0;
}

/**
 *
 * @param env
 * @param thiz
 * @param plainText
 * @return
 */
JNIEXPORT jstring tdOpenAesEncrypt(JNIEnv *env, jobject thiz, jstring keyText, jstring plainText) {
    const char *cPlainText = env->GetStringUTFChars(plainText,NULL);

    //需要加密的明文长度
    int plainTextLen = env->GetStringUTFLength(plainText);
    std::string errMsg = "";
    char key[32] = "12345678";
    if (keyText != NULL) {
        int keyLen = env->GetStringUTFLength(keyText);
        keyLen = keyLen <= 32 ? keyLen : 32;
        const char *c_key = env->GetStringUTFChars(keyText,NULL);
        memset(key,'\0',32);
        memcpy(key, c_key, (size_t) keyLen);
        env->ReleaseStringUTFChars(keyText,c_key);
    }
    //保存密文的buf
    char cryptoTextArray[TD_OPEN_AES_CRYPTO_LEN] = {0};
    std::string cryptoText;
    //加密后密文的长度
    size_t cryptoTextLen;
    if (plainTextLen >= TD_OPEN_AES_CRYPTO_LEN) {
        errMsg.assign("PlainText length is limited to less than 4096!");
        //长度限制
        goto enErr;
    }
    if (!aes_encrypt((char *) cPlainText, plainTextLen, key, cryptoTextArray, &cryptoTextLen)) {
        errMsg.assign("Encrypt failed!");
        //加密失败
        goto enErr;
    }
    cryptoText.assign(cryptoTextArray,cryptoTextLen);
    //密文做base64编码
    cryptoText = TDBASE64::base64_encodestring(cryptoText);
    env->ReleaseStringUTFChars(plainText,cPlainText);
    return env->NewStringUTF(cryptoText.c_str());
    enErr:
    env->ReleaseStringUTFChars(plainText,cPlainText);
    jclass clz = env->FindClass("java/security/GeneralSecurityException");
    jmethodID methodId = env->GetMethodID(clz, "<init>", "(Ljava/lang/String;)V");
    jthrowable throwable = (jthrowable) env->NewObject(clz, methodId,env->NewStringUTF(errMsg.c_str()));
    env->Throw(throwable);
    return NULL;
}

/**
 *
 * @param env
 * @param thiz
 * @param cryptoText
 * @return
 */
JNIEXPORT jstring tdOpenAesDecrypt(JNIEnv *env, jobject thiz, jstring keyText, jstring cryptoText) {
    const char *cCryptoText = env->GetStringUTFChars(cryptoText,NULL);
    std::string deBase64Crypto;
    //需要解密的密文长度
    int cryptoTextLen = env->GetStringUTFLength(cryptoText);
    deBase64Crypto.assign(cCryptoText, (unsigned int) cryptoTextLen);
    //将密文解base64,结果为aes加密后的密文块,长度为16的倍数
    deBase64Crypto = TDBASE64::base64_decodestring(deBase64Crypto);
    cryptoTextLen = deBase64Crypto.size();
    //定义解密后的明文
    std::string destr;
    std::string errMsg = "";
    char key[32] = "12345678";
    if (keyText != NULL) {
        int keyLen = env->GetStringUTFLength(keyText);
        keyLen = keyLen <= 32 ? keyLen : 32;
        const char *c_key = env->GetStringUTFChars(keyText,NULL);
        memset(key,'\0',32);
        memcpy(key, c_key, (size_t) keyLen);
        env->ReleaseStringUTFChars(keyText,c_key);
    }
    //定义解密后的明文缓冲
    char decrypt_string[TD_OPEN_AES_CRYPTO_LEN] = { 0 };
    //解密后的明文长度
    int decryptLen = 0;
    if (cryptoTextLen < 16) {
        errMsg.clear();
        errMsg.assign("Bad cryptoText!");
        goto deErr;
    }
    if (cryptoTextLen >= TD_OPEN_AES_CRYPTO_LEN) {
        errMsg.clear();
        errMsg.assign("CryptoText length is limited to less than 4096!");
        goto deErr;
    }
    if (!aes_decrypt((char *) deBase64Crypto.c_str(), cryptoTextLen, key, decrypt_string, &decryptLen)) {
        errMsg.clear();
        errMsg.assign("Decrypt failed!");
        goto deErr;
    }
    if (decryptLen <= 0) {
        errMsg.clear();
        errMsg.assign("Decrypt failed!");
        goto deErr;
    }
    destr.assign(decrypt_string, (unsigned int) decryptLen);
    env->ReleaseStringUTFChars(cryptoText,cCryptoText);
    return env->NewStringUTF(destr.c_str());
    deErr:
    env->ReleaseStringUTFChars(cryptoText,cCryptoText);
    jclass clz = env->FindClass("java/security/GeneralSecurityException");
    jmethodID methodId = env->GetMethodID(clz, "<init>", "(Ljava/lang/String;)V");
    jthrowable throwable = (jthrowable) env->NewObject(clz, methodId,env->NewStringUTF(errMsg.c_str()));
    env->Throw(throwable);
    return NULL;
}
private static final byte[] iv = { 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30, 0x34, 0x30, 0x35, 0x30, 0x36, 0x30, 0x37, 0x30, 0x38 };

    private static SecretKeySpec getKey(String aesPassword) throws UnsupportedEncodingException {
        int keyLength = 256;
        byte[] keyBytes = new byte[keyLength / 8];
        Arrays.fill(keyBytes, (byte) 0x0);
        byte[] passwordBytes = aesPassword.getBytes("UTF-8");
        int length = passwordBytes.length < keyBytes.length ? passwordBytes.length : keyBytes.length;
        System.arraycopy(passwordBytes, 0, keyBytes, 0, length);
        SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
        return key;
    }

    public static String encrypt(String clearText, String aesPassword) {
        try {


            SecretKeySpec skeySpec = getKey(aesPassword);
            byte[] clearTextBytes = clearText.getBytes("UTF8");
            IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivParameterSpec);
            return Base64.encodeToString(cipher.doFinal(clearTextBytes),Base64.NO_WRAP);
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String decrypt(String cipherText, String aesPassword) {
        if (aesPassword != null && aesPassword.length() > 0) {
            try {
                /**
                 * 这个地方调用BouncyCastleProvider
                 *让java支持PKCS7Padding
                 */
                Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

                SecretKeySpec skeySpec = getKey(aesPassword);
                IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
                cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivParameterSpec);
                return new String(cipher.doFinal(Base64.decode(cipherText,Base64.NO_WRAP)));
            } catch (InvalidKeyException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (BadPaddingException e) {
                e.printStackTrace();
            } catch (NoSuchPaddingException e) {
                e.printStackTrace();
            } catch (IllegalBlockSizeException e) {
                e.printStackTrace();
            } catch (InvalidAlgorithmParameterException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

其实后面也发现openssl中的evp方式默认用的就是PKCS7PADDING填充方式,不过由于用evp方式会增加编译后的动态库体积(大概也就几十kb,不算多),所有不打算用EVP方式,最后也附上evp方式的测试代码:

unsigned char *testEvpAesPkcs7Padding() {
    unsigned char key[32] = "12345678";
    unsigned char iv[17] = AES_CBC_IV;
    unsigned char *inStr = (unsigned char *) "ceshi123456789\1";
    int inLen = strlen((const char *) inStr);
    int encLen = 0;
    int outlen = 0;
    unsigned char encData[1024] = {0};


    //加密
    EVP_CIPHER_CTX *ctx;
    ctx = EVP_CIPHER_CTX_new();

    EVP_CipherInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv, 1);
    EVP_CipherUpdate(ctx, encData, &outlen, inStr, inLen);
    encLen = outlen;
    EVP_CipherFinal(ctx, encData+outlen, &outlen);
    encLen += outlen;
    EVP_CIPHER_CTX_free(ctx);

    std::string enstr;
    enstr.assign((char*)encData, (unsigned int) outlen);
    std::string base64 = TDBASE64::base64_encodestring(enstr);
    LOGD("evp:%s:end",base64.c_str());
    //解密
    std::string cryptoText = TDBASE64::base64_decodestring("4KioIpEMfoHYZvrPWL5Gnw==");
    int cryptoTextLen = cryptoText.length();
    int decLen = 0;
    outlen = 0;
    unsigned char decData[1024] = {0};
    EVP_CIPHER_CTX *ctx2;
    ctx2 = EVP_CIPHER_CTX_new();
    EVP_CipherInit_ex(ctx2, EVP_aes_256_cbc(), NULL, key, iv, 0);
    EVP_CipherUpdate(ctx2, decData, &outlen, (const unsigned char *) cryptoText.c_str(), cryptoTextLen);
    decLen = outlen;
    EVP_CipherFinal(ctx2, decData+outlen, &outlen);
    decLen += outlen;
    EVP_CIPHER_CTX_free(ctx2);
    std::string decodeResult;
    decodeResult.append((const char *) decData, (unsigned int) decLen);
    int a = decData[strlen((const char *) decData) - 1];
    LOGD("解密:%s",decodeResult.c_str());
    unsigned char *t = (unsigned char *) malloc(strlen((const char *) decData));
    memcpy(t,decData,strlen((const char *) decData));
    return t;
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值