Jni:使用openssl库进行rsa加密解密(实现篇)

前面,已经对于一个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编译篇)

猜你喜欢

转载自blog.csdn.net/sail331x/article/details/108226622