前面,已经对于一个vs 2019 的jni环境的搭建已经做了说明。
免费机票 Jni:使用openssl库进行rsa加密解密 (环境搭建篇)
安装Openssl
windows安装:
百度网盘下载
链接:https://pan.baidu.com/s/10WlgBJ3J25oIH4JXVgWLOA
提取码:ABCD
无脑安装版,略过。这里你要记住你安装好的位置。例如我的是 C:\Program Files\OpenSSL-Win64
Centos安装:
这里我是在网上摘抄的。安装这些就不写了,不是本篇重点
mkdir /opt/openssl -p
cd /opt/openssl
wget http://www.openssl.org/source/openssl-1.0.2j.tar.gz
tar -zxf openssl-1.0.2j.tar.gz
./config --prefix=/usr/local/openssl
make & make install
vs 2019 中引入openssl库
1、右键项目,选择Properties,之后把 openssl的安装目录下的include加到以下位置(偷懒直接贴图)
2、添加openssl的lib到lib directories中
3、添加libssl.lib和libcrypto.lib 到link中
至此,你的openssl环境基本搭建好了
RSA加密解密
公钥加密
注意:准备的公钥需要以 “-----BEGIN PUBLIC KEY-----\n"开头,以”\n-----END PUBLIC KEY-----\n"结尾,里面的’\n’表示换号符
注意:准备的私钥需要以 “-----BEGIN RSA PRIVATE KEY-----\n"开头,以”\n-----END RSA PRIVATE KEY-----\n"结尾,里面的’\n’表示换号符
Jni的转换操作类,主要是为了方便java跟c++的类型转换方便
MyJniConvertUtils.h
#pragma once
#include <iostream>
#include <jni.h>
#include <string.h>
class MyJniConvertUtils
{
public:
static int toCharArray(JNIEnv* env, jbyteArray byteArray, char*& dest);
static int toCharArray(JNIEnv* env, jstring param, char*& dest);
static jbyteArray toJbyteArray(JNIEnv* env, const char* data, const int length);
static std::string getProperty(JNIEnv* env, std::string key);
static std::string toString(const char* chars, const int length);
static jstring toJString(JNIEnv* env, const std::string input);
static std::string toString(JNIEnv* env, jstring param);
};
MyJniConvertUtils.cpp
#include "MyJniConvertUtils.h"
#include <string.h>
jstring MyJniConvertUtils::toJString(JNIEnv* env, const std::string input) {
const char* cs = input.c_str();
jclass strClass = (env)->FindClass("Ljava/lang/String;");
jmethodID ctorID = (env)->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
jbyteArray bytes = (env)->NewByteArray(strlen(cs));
(env)->SetByteArrayRegion(bytes, 0, strlen(cs), (jbyte*)cs);
jstring encoding = (env)->NewStringUTF("UTF8");
return (jstring)(env)->NewObject(strClass, ctorID, bytes, encoding);
}
int MyJniConvertUtils::toCharArray(JNIEnv* env, jbyteArray byteArray, char*& dest) {
jbyte* bytes;
bytes = env->GetByteArrayElements(byteArray, 0);
int byteLength = env->GetArrayLength(byteArray);
int length = byteLength + 1;
dest = new char[length];
memset(dest, 0, length);
memcpy(dest, bytes, byteLength);
env->ReleaseByteArrayElements(byteArray, bytes, 0);
return length;
}
int MyJniConvertUtils::toCharArray(JNIEnv* env, jstring param, char*& dest) {
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("UTF8");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray)env->CallObjectMethod(param, mid, strencode);
jsize alen = env->GetArrayLength(barr);
int size = ((int)alen + 1);
dest = new char[size];
memset(dest, 0, size);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0)
{
memcpy(dest, ba, alen);
}
env->ReleaseByteArrayElements(barr, ba, 0);
return size;
}
jbyteArray MyJniConvertUtils::toJbyteArray(JNIEnv* env, const char* charsData, const int length) {
jbyteArray data = env->NewByteArray(length);
env->SetByteArrayRegion(data, 0, length, (jbyte*)charsData);
env->ReleaseByteArrayElements(data, env->GetByteArrayElements(data, JNI_FALSE), 0);
return data;
}
std::string MyJniConvertUtils::getProperty(JNIEnv* env, std::string key) {
jclass systemClazz = env->FindClass("java/lang/System");
jmethodID mid = env->GetStaticMethodID(systemClazz, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");
jobject obj = env->CallStaticObjectMethod(systemClazz, mid, MyJniConvertUtils::toJString(env, key));
jstring result = (jstring)obj;
return MyJniConvertUtils::toString(env, result);
}
std::string MyJniConvertUtils::toString(const char* chars, const int length) {
std::string data(chars, length);
return data;
}
std::string MyJniConvertUtils::toString(JNIEnv* env, jstring param) {
char* chars;
int length = MyJniConvertUtils::toCharArray(env, param, chars);
std::string stemp(chars, length);
delete[] chars;
return stemp;
}
公钥加密+私钥方法
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <iostream>
#include <cassert>
#include "openssl/rsa.h"
#include "openssl/pem.h"
#include "openssl/err.h"
#include "openssl/bio.h"
#include <string.h>
using namespace std;
/**
* 公钥加密
**/
int EncryptWithPublicKey(std::string pubKey, const char* text, const int length, char*& output) {
std::string encrypt_text;
BIO* keybio = BIO_new_mem_buf((unsigned char*)pubKey.c_str(), -1);
RSA* rsa = RSA_new();
rsa = PEM_read_bio_RSA_PUBKEY(keybio, &rsa, NULL, NULL);
// 获取RSA单次可以处理的数据块的最大长度
int key_len = RSA_size(rsa);
// 因为填充方式为RSA_PKCS1_PADDING, 所以要在key_len基础上减去11
int block_len = key_len - 11;
// 申请内存:存贮加密后的密文数据
int len = key_len + 1;
char* sub_text = new char[len];
memset(sub_text, 0, len);
char* textCs = new char[block_len + 1];
int ret = 0;
int pos = 0;
int left = length;
int flagLength = 0;
bool error = false;
int lenText = 0;
while (pos < length) {
if (left >= block_len) {
flagLength = block_len;
}
else {
flagLength = left;
}
left -= flagLength;
memset(textCs, 0, block_len + 1);
memcpy(textCs, text + pos, flagLength);
memset(sub_text, 0, len);
ret = RSA_public_encrypt(flagLength, (const unsigned char*)textCs, (unsigned char*)sub_text, rsa, RSA_PKCS1_PADDING);
if (ret >= 0) {
encrypt_text.append(std::string(sub_text, ret));
lenText += ret;
}
else {
error = true;
break;
}
pos += block_len;
}
// 释放内存
BIO_free_all(keybio);
RSA_free(rsa);
delete[] sub_text;
delete[] textCs;
if (!error) {
const char* chars = encrypt_text.c_str();
int len = lenText + 1;
output = new char[len];
memset(output, 0, len);
memcpy(output, chars, len - 1);
return len-1;
}
else {
output = new char[1];
output[0] = 0;
return 0;
}
}
/**
* 私钥解密
**/
int DecryptByPrivateKey(std::string priKey, const char* encryptText, const int length, char*& output) {
std::string decrypt_text;
RSA* rsa = RSA_new();
BIO* keybio;
keybio = BIO_new_mem_buf((unsigned char*)priKey.c_str(), -1);
rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, NULL, NULL);
if (rsa == nullptr) {
return 0;
}
int key_len = RSA_size(rsa);
int len = key_len + 1;
char* sub_text = new char[len];
memset(sub_text, 0, len);
char* eTextCs = new char[key_len];
int ret = 0;
int pos = 0;
int count = 0;
bool error = false;
int left = length;
int flagLength = 0;
int textLength = 0;
// 对密文进行分段解密
while (pos < length) {
if (left >= key_len) {
flagLength = key_len;
}
else {
flagLength = left;
}
left -= flagLength;
memset(eTextCs, 0, key_len);
memcpy(eTextCs, (encryptText + pos), flagLength);
//sub_str = input.substr(pos, key_len);
memset(sub_text, 0, len);
ret = RSA_private_decrypt(flagLength, (const unsigned char*)eTextCs, (unsigned char*)sub_text, rsa, RSA_PKCS1_PADDING);
if (ret >= 0) {
decrypt_text.append(std::string(sub_text, ret));
textLength += ret;
pos += key_len;
}
else {
error = true;
break;
}
}
// 释放内存
delete[] sub_text;
delete[] eTextCs;
BIO_free_all(keybio);
RSA_free(rsa);
if (error) {
return 0;
}
textLength++;
output = new char[textLength];
memset(output, 0, textLength);
memcpy(output, decrypt_text.c_str(), textLength - 1);
return textLength-1;
}
这里有个有趣的东西,就是RSA_private_decrypt这个方法,会有返回-1的情况,这样就是一种异常的情况。一般来说,我之前遇到的就是因为加密出来的私钥是包含’\0’字符,所以如果直接用str.substr()就会得到非预期的正常结果(其实说到底就是我对c++不熟悉,毕竟我都是玩java为主)。
JNI调用
上面的方法都准备好了,java对象转c++对象/类型的工具类也准备好了,然后就可以完善下前面的MyJni.java类了。这里就不做过多的解释了。
至于为啥我要传个int length类型,在说RSA_private_decrypt的时候也提了下。
编译构建+调用
最后,一切准备完毕,代码没啥错误就开始编译 ctrl+shift+B
这里记得把你的dll文件覆盖之前在C:\Windows\System32里面的(其实就是你放到的java.library.path上门的)
Linux环境下编译
我们的java服务程序,一般都是放到linux环境下面去跑的。所以,后面还有一篇,就是关于在centos下面如何编译我们自己的c++文件的一个简单说明
免费机票 Jni:使用openssl库进行rsa加密解密(Linux编译篇)