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;}
- jni使用openssl AES256位加解密(cbc模式),匹配java后端服务器算法,解决末尾乱码问题
- 使用openssl的aes256加解密算法(带例程 兼容openssl命令)
- 使用PHP7.1的openssl加解密AES-128-CBC,与7.0之前的版本匹配
- Openssl中AES加解密——CBC模式
- Java实现AES256加解密
- Java实现AES256加解密
- 提供一个AES128位/CBC模式加解密工具类
- Java 使用AES/CBC/PKCS7Padding 加解密字符串
- C++ 和 java 使用 AES CBC 128 加解密
- Aes256的CBC模式加密
- 在java项目中使用AES256 CBC加密
- AES/CBC/PKCS5Padding 对称算法加解密
- java aes解密cbc模式
- 关于AES256算法java端加密,ios端解密出现无法解密问题的解决方案
- 【转】关于AES256算法java端加密,ios端解密出现无法解密问题的解决方案
- 关于AES256算法java端加密,ios端解密出现无法解密问题的解决方案
- 关于AES256算法java端加密,ios端解密出现无法解密问题的解决方案
- Java实现AES的128、256位密钥加解密算法 并 解决Illegal key size or default parameters问题
- rpmbuild ERROR: No build ID note found in
- Android Monkey 压力测试 介绍
- JAVA8新特性之Stream如何“骚”起来
- Perfect Cut 5.0 玻璃优化软件\
- Fiori2.0学习笔记-Pages&panels
- jni使用openssl AES256位加解密(cbc模式),匹配java后端服务器算法,解决末尾乱码问题
- Java Web学习之旅开始
- MD5加密之简单程序
- 教程篇(5.4) NSE4 16. 高级IPsec VPN ❀ 飞塔 (Fortinet) 网络安全专家
- Python与MySQL交互之用户登录
- 单例模式简单实例
- 1.1.2 编程的开始
- XGTD v2.1 1CD(电磁仿真和分析软件)\
- jQuery 对checkbox的操作