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

来源:互联网 发布:淘宝2015版本下载安装 编辑:程序博客网 时间:2024/06/06 02:58

前言:以下代码中统一的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 0
原创粉丝点击