GmSSL3.0 在Android上的命令行风格封装

这篇博客介绍了如何在Android项目中对GmSSL3.0进行Java简易封装,通过CMakeLists.txt文件配置和JNI接口实现命令行调用。文章详细展示了从新建模块、修改CMakeLists.txt、编写JNI和Java类的全过程,特别地,通过hook标准输出来获取命令执行结果,并提供了测试示例。
摘要由CSDN通过智能技术生成

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

  1. 新建C++库模块
    create
  2. 配置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工程

  1. 编写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);
    	}
    
  2. 编写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;
}
  1. 写个测试
    在这里插入图片描述
    当然如果你想调用Gmssl的方法,可以在jni的c和java文件中自行增加接口和实现。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值