GmSSL 3.0 Java简易封装
3.0版编译说明
按照 GmSSL库 的说明,新版3.0的编译使用cmake。在Android上使用cmake编译基本上只改动一点点就可以很方便移植成功
这篇文章抛砖引玉,演示一下对gmssl命令行的java封装, AS版本为 2021.1.1 patch2,gradle 为 4.1.1,注意此demo只是把原先gmssl的字符串传给编译它的main函数,所以如果命令涉及其他命令会报错。比如下面的echo 是不支持,利用管道把结果当作gmssl的输入是行不通的
echo hello | gmssl sm2encrypt -pubkey sm2pub.pem -out sm2.der
一、移植步骤预览
1. 新建Android项目 和 Demo 测试用的UI界面
2. 新建Native Library Module
3. 改写CMakeLists.txt 文件
4. 编写JNI 和 java封装类
这里UI不是我们主要关心的,所以只讲第2、3、4点,具体的工程项目 https://download.csdn.net/download/b2259909/85708892
二、Native Library Module
- 新建C++库模块
- 配置C++模块的build.gradle
注意 ndkVersion 太高可能不支持 armeabi,另外可能报错so库重复之类的,加个packagingOption
4. 将 GmSSL-Master.zip 或者git clone后的文件拷贝到项目中
5. 修改 CMakeLists.txt 文件
注意: 由于我打算支持类似命令行的调用方式,所以需要把tools下的文件也编译进去,同时我们修改一下 gmssl.c中的 main函数为 gmssl_main方法,并新增一个 gmssl.h文件,在里面声明这个方法。等会在gmsslNative.c中调用它,具体看github工程
-
编写JNI的Java类
package com.zbh.gmssl; import android.util.Log; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; public class GmSSLv3 { public static StringBuffer resultBuffer; static { resultBuffer = new StringBuffer(); System.loadLibrary("gmssl.v3.Alpha"); } /** * 发送命令给 gmssl * 执行结果可以使用 resultBuffer.toString()获取 * @param cmdLine 终端的命令字符串 * @return */ public static int cmdLine(String cmdLine){ //TODO 这里提前用正则表达式处理字符串参数 --> 字符串数组。 Log.d("zbh","cmdLine = " + cmdLine); resultBuffer.setLength(0); //处理参数 ArrayList<String> list= new ArrayList<>(); Matcher matcher = Pattern.compile( "\"(\\\\\"|[^\"])*?\"|[^ ]+").matcher(cmdLine); while(matcher.find()) { list.add(matcher.group()); } String[] cmdArgs = new String[list.size()]; for (int i = 0; i < list.size(); i++) { cmdArgs[i] = list.get(i); } return cmdLine(cmdArgs,resultBuffer); } private static native int cmdLine(String[] cmdString,StringBuffer stringBuffer); }
-
编写JNI的C文件
这里比较有趣的是:
为了能像使用命令行一样执行,上层java已经将命令行参数按照空格分割成数组。
C层还需要组织成 二级指针的 argv 和 argc ,以便调用 int gmssl_main(int argc, char **argv)
而且为了不需要改动原GMSSL的代码就能从内存拿到结果,我这里使用了 dlsym方法将标准库的 printf 和 fprintf 给hook了,因为gmssl中的结果是输出到stdout和stderr了,之后我就能将结果(字符串)返回到java层了,当然如果某命令是涉及读写文件,文件路径必须是有权限的才能成功。
#include <jni.h>
#include <android/log.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gmssl/gmssl.h>
#include <dlfcn.h>
#define LOG_TAG "GMSSL_JNI"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define NUM_ARRAY_ELEMENTS(p) ((int) sizeof(p) / sizeof(p[0]))
//hook fprintf
typedef int(*fprintf_t)(FILE * __restrict , const char * __restrict, ...);
typedef int(*printf_t)(const char * __restrict, ...);
fprintf_t fprintf_old = NULL;
printf_t printf_f=NULL;
static JavaVM *sg_javaVm = NULL;
static jobject sg_strbuf=NULL;
int fprintf(FILE * __restrict file, const char * __restrict format, ...){
if(fprintf_old == NULL){
fprintf_old = dlsym(RTLD_NEXT, "fprintf"); //除了RTLD_NEXT 还有一个参数RTLD_DEFAULT
}
JNIEnv *env;
int isAttached = 0;
int getEnvStat = (*sg_javaVm)->GetEnv(sg_javaVm, (void**)&env, JNI_VERSION_1_6);
if (getEnvStat != JNI_OK) {
if (getEnvStat == JNI_EDETACHED) {
__android_log_print(ANDROID_LOG_ERROR,LOG_TAG,"GetEnv: not attached");
if ((*sg_javaVm)->AttachCurrentThread(sg_javaVm, (JNIEnv**)&env, 0) != 0) {
__android_log_print(ANDROID_LOG_ERROR,LOG_TAG,"Failed to attach");
return 0;
}
isAttached = 1;
__android_log_print(ANDROID_LOG_INFO,LOG_TAG,"AttachCurrentThread, GetEnv");
} else if (getEnvStat == JNI_EVERSION) {
__android_log_print(ANDROID_LOG_ERROR,LOG_TAG,"GetEnv: version not supported");
return 0;
}else{
__android_log_print(ANDROID_LOG_ERROR,LOG_TAG,"GetEnv: other error");
return 0;
}
}
//获取java StringBuffer类
jclass cls_sb = (*env)->GetObjectClass(env,sg_strbuf);
//获取StringBuffer的 setLength 和 add方法
jmethodID mid_setLength = (*env)->GetMethodID(env,cls_sb,"setLength","(I)V");
jmethodID mid_append = (*env)->GetMethodID(env,cls_sb,"append","(Ljava/lang/String;)Ljava/lang/StringBuffer;");
//调用StringBuffer 的 setLength方法,先清空
//(*env)->CallVoidMethod (env,sg_strbuf, mid_setLength, 0);
char *parg;
va_list argptr;
va_start(argptr,format);
vasprintf(&parg,format,argptr);
jstring rstring = (*env)->NewStringUTF(env,parg);
(*env)->CallObjectMethod (env,sg_strbuf, mid_append, rstring);
va_end(argptr);
if (isAttached==1){
(*sg_javaVm)->DetachCurrentThread(sg_javaVm);
}
(*env)->DeleteLocalRef(env, cls_sb);
(*env)->DeleteLocalRef(env, rstring);
return fprintf_old(file,"%s",parg);
}
int printf(const char * __restrict format, ...){
if(printf_old == NULL)
{
printf_old = dlsym(RTLD_NEXT, "printf");
}
char *parg;
va_list argptr;
va_start(argptr,format);
vasprintf(&parg,format,argptr);
LOGD(parg);
va_end(argptr);
return printf_old("%s",parg);
}
JNIEXPORT
jint JNICALL cmdLine(JNIEnv *env, jclass thiz, jobjectArray cmdString,jobject strbuf){
sg_strbuf = strbuf;
if (cmdString == NULL || (*env)->GetArrayLength(env,cmdString) == 0)
return -999;
jsize size = (*env)->GetArrayLength(env,cmdString);
int argc = size;
int** p = (int **)malloc(size*sizeof(int*));//申请 存放指针的数组
for(int i=0;i<size;i++)
{
jstring* obj = (jstring *)(*env)->GetObjectArrayElement(env,cmdString,i);
char *value = (char *)(*env)->GetStringUTFChars(env,obj,NULL);//得到字符串
int len = strlen(value)+1;
p[i] = (char*)malloc(len);//再申请字符串长度的内存空间
memset(p[i],0,len);
memcpy(p[i],value, strlen(value));
(*env)->ReleaseStringUTFChars(env,obj,value); //释放引用
}
int ret = gmssl_main(argc, p);
for(int i=0; i<size; i++)
{
//printf("cmd[%d] = %s",i,p[i]);
free(p[i]);//记住要释放内存
}
free(p);//释放两次,两次释放的不一样
return ret;
}
static JNINativeMethod methods[] = {
{"cmdLine", "([Ljava/lang/String;Ljava/lang/StringBuffer;)I", (void *)cmdLine},
};
jint registerNativeMethods(JNIEnv *env, const char *class_name, JNINativeMethod *methods,
int num_methods) {
jclass clazz = (*env)->FindClass(env,class_name);
if (clazz == NULL) {
LOGD("registerNativeMethods: class'%s' not found", class_name);
return JNI_FALSE;
}
jint result = (*env)->RegisterNatives(env,clazz, methods, num_methods);
if (result < 0) {
LOGD("registerNativeMethods failed(class=%s)", class_name);
return JNI_FALSE;
}
return result;
}
JNIEXPORT
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
LOGD("JNI_OnLoad");
sg_javaVm = vm;
JNIEnv *env = NULL;
//获取JNIEnv
if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) {
LOGD("JNI_OnLoad GetEnv fail");
return -1;
}
assert(env != NULL);
const char *className = "com/zbh/gmssl/GmSSLv3";
registerNativeMethods(env, className, methods, NUM_ARRAY_ELEMENTS(methods));
return JNI_VERSION_1_6;
}
- 写个测试
当然如果你想调用Gmssl的方法,可以在jni的c和java文件中自行增加接口和实现。