本文实现在windows下编写C++代码并使用java进行调用
然后将该C++代码进行响应修改在centos环境下使用java进行调用
整体是基于JNI实现SM4加密。
windows下实现
1.编写java代码 CallSM4.java
package com.sm4;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
public class CallSM4 {
static {
//linux下的so
//System.load("/data/Sm4SDK/libsm4.so");
//windows下的dll,放在JDK包下的bin文件夹中
System.loadLibrary("CallSM4");
}
//ECB模式加密
public native String encryptData_ECB(String plainText);
//ECB模式解密
public native String decryptData_ECB(String cipherText);
//CBC模式加密
public native String encryptData_CBC(String plainText);
//CBC模式解密
public native String decryptData_CBC(String cipherText);
public static void main(String[] args) throws IOException {
List<String> plainTextList = Arrays.asList("20208888");
CallSM4 sm4 = new CallSM4();
plainTextList.forEach(plainText -> {
System.out.println("----------");
System.out.println("ECB模式");
String cipherText = sm4.encryptData_ECB(plainText);
System.out.println("密文: " + cipherText);
System.out.println("");
plainText = sm4.decryptData_ECB(cipherText);
System.out.println("明文: " + plainText);
System.out.println("");
System.out.println("CBC模式");
cipherText = sm4.encryptData_CBC(plainText);
System.out.println("密文: " + cipherText);
System.out.println("");
plainText = sm4.decryptData_CBC(cipherText);
System.out.println("明文: " + plainText);
});
}
}
- 找到CallSM4.java文件所在路径,在该路径下打开命令行,使用如下指令:
javac CallSM4.java
运行结束后,当前文件夹下出现同名的CallSM4.class文件
- 然后需要要使用javah对.java进行编译得到dll文件
步骤:在.java的目录下使用命令行,执行如下指令
javah -classpath F:\XXX\XXX\XXX\ com.ais.sdsec.util.sm4.CallSM4
这里要注意路径每个人都不同,因为我的CallSM4.class是有包名com.sm4,所以这段路径F:\XXX\XXX\XXX\,而不是
F:\XXX\XXX\XXX\com\sm4
- 运行完上面步骤后在class边上就会得到一个com_sm4_CallSM4.h文件
---------------java部分暂时告一段落---------------
-
新建一个C++项目,本文使用Visual Studio。
-
将上面得到的com_sm4_CallSM4.h引入到项目中
同时在github上找到SM4加密的C++代码并下载,将其中的SM4.cpp与SM4.h均引入到项目中 -
接下来就要写我们自己得SM4的详细实现接口
#pragma warning( disable : 4996)
#include "com_sm4_CallSM4.h"
#include "sm4.h"
#include<iostream>
#include"base64.h"
//jstring转string
std::string jstring2str(JNIEnv* env, jstring jstr)
{
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("GBK");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0)
{
rtn = (char*)malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
std::string stemp(rtn);
free(rtn);
return stemp;
}
//string转jstring
jstring str2jstring(JNIEnv* env, const char* pat)
{
//定义java String类 strClass
jclass strClass = (env)->FindClass("Ljava/lang/String;");
//获取String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String
jmethodID ctorID = (env)->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
//建立byte数组
jbyteArray bytes = (env)->NewByteArray(strlen(pat));
//将char* 转换为byte数组
(env)->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
// 设置String, 保存语言类型,用于byte数组转换至String时的参数
jstring encoding = (env)->NewStringUTF("GBK");
//将byte数组转换为java String,并输出
return (jstring)(env)->NewObject(strClass, ctorID, bytes, encoding);
}
//ECB模式:加密
JNIEXPORT jstring JNICALL Java_com_sm4_CallSM4_encryptData_1ECB(JNIEnv *env, jobject obj, jstring plainText)
{
sm4 s;
s.setType(sm4::ECB);
s.setKey("111");
s.setIv("222");
//jstring转string
std::string resTest = jstring2str(env, plainText);
//std::cout << "resTest::" + resTest << std::endl;
//string 加密
std::string cipherTextUTF = s.encrypt(resTest);
//加密后的string进行base64 encode
std::string encodedStr = base64_encode(reinterpret_cast<const unsigned char*>(cipherTextUTF.c_str()), cipherTextUTF.length());
//encoded后的string转换成jstring返回
jstring base64CryJTest = (env)->NewStringUTF(encodedStr.c_str());
return base64CryJTest;
}
//ECB模式:解密
JNIEXPORT jstring JNICALL Java_com_sm4_CallSM4_decryptData_1ECB(JNIEnv *env, jobject obj, jstring cipherText)
{
sm4 s;
s.setType(sm4::ECB);
s.setKey("111");
s.setIv("222");
std::string res = jstring2str(env, cipherText);
//std::cout << "cipherText::"+res << std::endl;
//jstring转string
std::string cipherTextStr = jstring2str(env, cipherText);
//std::cout << "cipherTextStr::" + cipherTextStr << std::endl;
//string 进行 base64的decode
std::string decodedStr = s.decrypt(base64_decode(cipherTextStr));
//std::cout << "decodedStr::" + decodedStr << std::endl;
return str2jstring(env, decodedStr.c_str());
}
//CBC模式:加密
JNIEXPORT jstring JNICALL Java_com_sm4_CallSM4_encryptData_1CBC(JNIEnv *env, jobject obj, jstring plainText)
{
sm4 s;
s.setType(sm4::CBC);
s.setKey("111");
s.setIv("222");
//jstring转string
std::string resTest = jstring2str(env, plainText);
//std::cout << "resTest::" + resTest << std::endl;
//string 加密
std::string cipherTextUTF = s.encrypt(resTest);
//加密后的string进行base64 encode
std::string encodedStr = base64_encode(reinterpret_cast<const unsigned char*>(cipherTextUTF.c_str()), cipherTextUTF.length());
//encoded后的string转换成jstring返回
jstring base64CryJTest = (env)->NewStringUTF(encodedStr.c_str());
return base64CryJTest;
}
//CBC模式:解密
JNIEXPORT jstring JNICALL Java_com_sm4_CallSM4_decryptData_1CBC(JNIEnv *env, jobject obj, jstring cipherText)
{
sm4 s;
s.setType(sm4::CBC);
s.setKey("111");
s.setIv("222");
std::string res = jstring2str(env, cipherText);
//std::cout << "cipherText::" + res << std::endl;
//jstring转string
std::string cipherTextStr = jstring2str(env, cipherText);
//std::cout << "cipherTextStr::" + cipherTextStr << std::endl;
//string 进行 base64的decode
std::string decodedStr = s.decrypt(base64_decode(cipherTextStr));
//std::cout << "decodedStr::" + decodedStr << std::endl;
return str2jstring(env, decodedStr.c_str());
}
要注意的点:
方法要和.h头文件中生成的方法名完全相同,到时候自动调用的时候就是通过相同的方法名进行对应调用。
- 然后生成dll文件
右键项目,点击重新生成,则自动生成dll文件
这里要注意的是,
一、要给你的c++项目添加上对java中jni文件的引用,如下图:(请根据自己的JDK安装目录对这个路径进行相应修改)
二、因为我的java 的jdk是64位的,所以我之后进行java对dll动态链接库进行调用时,dll动态链接库也要求是64位的。
那么在dll生成时就要注意生成64位。
图中的几个位置都要换成x64
-
将上面得到的dll文件放入JDK安装目录下的bin文件夹中,如我的地址是:D:\JDK\bin,这样运行java文件时才能自动的找到这个动态链接库
-
然后回到IDEA 中,运行main函数,就可以看到teminal中的输出
linux下实现
- 将上面的C++代码放入CentOS 中,使用makefile将其编译成so文件,生成的so文件一定是lib开头,so后缀的。
- 然后将java中的代码进行修改,只需要修改读取动态链接库部分,改成如下形式,(so文件的路径根据自己的进行修改)
static {
//windows下的写法,因为CallSM.dll文件已经被放在JDK下的bin文件夹中,会自动找到
//System.loadLibrary("CallSM4");
//在Linux环境下,使用绝对路径找到so文件,并进行加载
System.load("/data/Sm4SDK/libsm4.so");
}
- 在linux上想执行Java文件,同时又满足对so文件的调用,因为本文使用了com.sm4
因此需要新建相应的文件夹(即com→sm4),将java文件放在sm4文件夹中,然后在com的上一层目录进行java指令进行运行