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

时间:2020-12-10 18:29:09

前言:以下代码中统一的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;
}