logo
25
3
WeChat Login

feat: Add SM3MessageDigest JNI implementation with OpenSSL."#13

Closed
created 2025-07-12
RhinoBird2025
lucifer-2025/lucifer-TencentKona-17:RhinoBird2025
Edit
OverviewCommits
11
Files changed
8
Attachments

[Implement SM3 with OpenSSL]开发流程(已完成,优化中)

1. 需求概述

(一)功能需求描述

根据原来的实现和SM3算法的实现细节,由于之前是纯Java实现算法,每个操作都是JVM字节码执行的,要经过64轮循环,每次循环要包括多次位运算、数组访问、函数调用,有很明显的性能限制。所以产生了现在的需求——为现有的 SM3MessageDigest 实现添加基于 JNI 和 OpenSSL 3.5.0 的高性能支持,在保持兼容的前提下提供显著的性能提升。

2. 开发流程

(一) Java层实现

  1. 修改文件src/java.base/share/classes/sun/security/provider/SM3MessageDigest.java
    • 保留原来逻辑:JNI 可用时使用 OpenSSL,不可用时回退到纯 Java 实现
  2. 修改文件: src/java.base/share/classes/sun/security/provider/SMEntries.java
    • 添加了 SM3 算法注册:
      javaApplyCancelmap.put("MessageDigest.SM3", "sun.security.provider.SM3MessageDigest");
      map.put("Alg.Alias.MessageDigest.1.2.156.10197.1.401", "SM3");

(二) Native层实现

  1. 创建目录: src/java.base/share/native/libsm3digest/

    文件1: src/java.base/share/native/libsm3digest/sm3_digest.h
    // JNI头文件,包含函数声明和结构体定义
    文件2: src/java.base/share/native/libsm3digest/sm3_digest.c
    // JNI实现,使用OpenSSL 3.5.0的EVP接口

总体实现概述

本实现基于 JNI 将 OpenSSL 的 SM3 哈希算法封装为 Java 的 MessageDigest 接口,主要遵循了三种设计模式:Wrapper Pattern、Resource Management 和 Error Handling。通过Wrapper Pattern,我们将 OpenSSL 的 EVP API 封装为 JNI 接口,使得 Java 可以方便地调用 C/C++ 编写的加密算法。资源管理方面,使用RAII 模式 来管理 OpenSSL 的上下文,确保在 JNI 层创建的资源能够得到及时释放。异常处理方面,采用统一的异常处理机制,确保当出现问题时,能够通过抛出适当的 Java 异常来反馈错误。核心的依赖包括 JNI 接口(jni.h)、OpenSSL 的高级接口(evp.h)、以及 OpenSSL 的错误处理(err.h)。这些依赖构成了 SM3 哈希算法的底层实现基础。

大数据小数据的优化

在 SM3 的 JNI 实现中,关键常量包括 SM3 哈希长度(SM3_DIGEST_LENGTH,即 32 字节)以及 小数据优化阈值(SMALL_DATA_THRESHOLD,即 512 字节)。在性能优化方面,对于 小数据(长度不超过 512 字节),采用 栈缓冲区 来避免 JNI 调用的开销,而对于大数据,则直接通过 JNI 数组指针进行处理,避免不必要的内存拷贝。此外,内存管理也做了优化,确保及时释放资源,以避免内存泄漏。

错误处理相关完善

在 JNI 层实现的核心方法包括初始化、数据更新、最终计算等,所有这些方法都通过 OpenSSL EVP 接口进行加密计算。每个 JNI 方法都附带详细的异常处理逻辑,确保在资源分配失败或操作异常时,能够抛出合适的 Java 异常并进行资源清理。例如,在初始化过程中,如果 OpenSSL 的上下文创建失败,代码会抛出 OpenSSL 异常并回滚。

  1. 构建配置, 修改文件: make/lib/Lib-java.base.gmk

    # 添加libsm3digest库的构建规则
      $(eval $(call SetupJdkLibrary, BUILD_LIBSM3DIGEST, \
          NAME := sm3digest, \
          CFLAGS := $(CFLAGS_JDKLIB) $(OPENSSL_CFLAGS), \
          LIBS := $(OPENSSL_LIBS), \
      ))
    

3. 性能测试

测试文件路径

目前已完善基准测试路径为:/Users/dinghaoran02/txJDK_lab/TencentKona-17/test/micro/org/openjdk/bench/java/security/SM3JMHTest.java(已提交)

测试效果

41e5acfa-1cb2-473c-9a5a-cb6fabffb4a8.png 707422be-317e-4f8b-bcde-175ef59b1d1c.png

JNI加速效果随数据大小递增
1KB: 25.0% 性能提升
64KB: 39.0% 性能提升
1MB: 40.3% 性能提升

可以看出随着数据量提升,性能随之提升

测试步骤

  1. 在编译好JDK之后,下载JMH相关依赖
mkdir -p jmh-libs
cd jmh-libs
curl -L -o jmh-core-1.37.jar https://repo1.maven.org/maven2/org/openjdk/jmh/jmh-core/1.37/jmh-core-1.37.jar
curl -L -o jmh-generator-annprocess-1.37.jar https://repo1.maven.org/maven2/org/openjdk/jmh/jmh-generator-annprocess/1.37/jmh-generator-annprocess-1.37.jar
curl -L -o jopt-simple-5.0.4.jar https://repo1.maven.org/maven2/net/sf/jopt-simple/jopt-simple/5.0.4/jopt-simple-5.0.4.jar
curl -L -o commons-math3-3.6.1.jar https://repo1.maven.org/maven2/org/apache/commons/commons-math3/3.6.1/commons-math3-3.6.1.jar
cd ..
  1. 下载JMH生成器
curl -L -o jmh-generator-bytecode-1.37.jar https://repo1.maven.org/maven2/org/openjdk/jmh/jmh-generator-bytecode/1.37/jmh-generator-bytecode-1.37.jar
curl -L -o jmh-generator-reflection-1.37.jar https://repo1.maven.org/maven2/org/openjdk/jmh/jmh-generator-reflection/1.37/jmh-generator-reflection-1.37.jar
  1. 编译SM3JMHTest
./build/macosx-aarch64-server-release/images/jdk/bin/javac \
      -cp jmh-libs/jmh-core-1.37.jar:jmh-libs/jmh-generator-annprocess-1.37.jar \
      test/micro/org/openjdk/bench/java/security/SM3JMHTest.java
  1. 生成JMH基准测试类
./build/macosx-aarch64-server-release/images/jdk/bin/java \
      -cp jmh-generator-bytecode-1.37.jar:jmh-generator-reflection-1.37.jar:jmh-libs/jmh-core-1.37.jar:test/micro \
      org.openjdk.jmh.generators.bytecode.JmhBytecodeGenerator test/micro test/micro test/micro
  1. 编译生成的JMH文件
./build/macosx-aarch64-server-release/images/jdk/bin/javac \
      -cp jmh-libs/jmh-core-1.37.jar:test/micro \
      --add-exports=java.base/sun.security.provider=ALL-UNNAMED \
      test/micro/org/openjdk/bench/java/security/jmh_generated/*.java
  1. 进行测试
./build/macosx-aarch64-server-release/images/jdk/bin/java \
      -cp test/micro:jmh-libs/jmh-core-1.37.jar:jmh-libs/jopt-simple-5.0.4.jar:jmh-libs/commons-math3-3.6.1.jar \
      --add-exports=java.base/sun.security.provider=ALL-UNNAMED \
      org.openjdk.jmh.Main "SM3JMHTest" 
  1. 运行基准测试时的gc情况
[0.006s][info][gc] Using G1
[0.006s][info][gc,init] Version: 17.0.15-internal+0-adhoc.dinghaoran02.TencentKona-17 (release)
[0.006s][info][gc,init] CPUs: 8 total, 8 available
[0.006s][info][gc,init] Memory: 16384M
[0.006s][info][gc,init] Large Page Support: Disabled
[0.006s][info][gc,init] NUMA Support: Disabled
[0.006s][info][gc,init] Compressed Oops: Enabled (Zero based)
[0.006s][info][gc,init] Heap Region Size: 2M
[0.006s][info][gc,init] Heap Min Capacity: 8M
[0.006s][info][gc,init] Heap Initial Capacity: 256M
[0.006s][info][gc,init] Heap Max Capacity: 4G
[0.006s][info][gc,init] Pre-touch: Disabled
[0.006s][info][gc,init] Parallel Workers: 8
[0.006s][info][gc,init] Concurrent Workers: 2
[0.006s][info][gc,init] Concurrent Refinement Workers: 8
[0.006s][info][gc,init] Periodic GC: Disabled
[0.010s][info][gc,metaspace] CDS archive(s) mapped at: [0x0000009000000000-0x0000009000bc0000-0x0000009000bc0000), size 12320768, SharedBaseAddress: 0x0000009000000000, ArchiveRelocationMode: 1.
[0.010s][info][gc,metaspace] Compressed class space mapped at: 0x0000009001000000-0x0000009041000000, reserved size: 1073741824
[0.010s][info][gc,metaspace] Narrow klass base: 0x0000009000000000, Narrow klass shift: 0, Narrow klass range: 0x100000000
[10.574s][info][gc,start    ] GC(0) Pause Young (Normal) (G1 Evacuation Pause)
[10.574s][info][gc,task     ] GC(0) Using 6 workers of 8 for evacuation
[10.575s][info][gc,phases   ] GC(0)   Pre Evacuate Collection Set: 0.1ms
[10.575s][info][gc,phases   ] GC(0)   Merge Heap Roots: 0.0ms
[10.575s][info][gc,phases   ] GC(0)   Evacuate Collection Set: 1.3ms
[10.575s][info][gc,phases   ] GC(0)   Post Evacuate Collection Set: 0.1ms
[10.576s][info][gc,phases   ] GC(0)   Other: 0.2ms
[10.576s][info][gc,heap     ] GC(0) Eden regions: 11->0(17)
[10.576s][info][gc,heap     ] GC(0) Survivor regions: 0->1(2)
[10.576s][info][gc,heap     ] GC(0) Old regions: 0->0
[10.576s][info][gc,heap     ] GC(0) Archive regions: 2->2
[10.576s][info][gc,heap     ] GC(0) Humongous regions: 1->1
[10.576s][info][gc,metaspace] GC(0) Metaspace: 3479K(3648K)->3479K(3648K) NonClass: 3094K(3136K)->3094K(3136K) Class: 385K(512K)->385K(512K)
[10.576s][info][gc          ] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 25M->5M(260M) 1.880ms
[10.576s][info][gc,cpu      ] GC(0) User=0.00s Sys=0.00s Real=0.00s
[25.694s][info][gc,start    ] GC(1) Pause Young (Normal) (G1 Evacuation Pause)
[25.695s][info][gc,task     ] GC(1) Using 6 workers of 8 for evacuation
[25.696s][info][gc,phases   ] GC(1)   Pre Evacuate Collection Set: 0.1ms
[25.696s][info][gc,phases   ] GC(1)   Merge Heap Roots: 0.1ms
[25.696s][info][gc,phases   ] GC(1)   Evacuate Collection Set: 1.0ms
[25.696s][info][gc,phases   ] GC(1)   Post Evacuate Collection Set: 0.2ms
[25.696s][info][gc,phases   ] GC(1)   Other: 0.1ms
[25.696s][info][gc,heap     ] GC(1) Eden regions: 17->0(75)
[25.696s][info][gc,heap     ] GC(1) Survivor regions: 1->1(3)
[25.696s][info][gc,heap     ] GC(1) Old regions: 0->0
[25.696s][info][gc,heap     ] GC(1) Archive regions: 2->2
[25.696s][info][gc,heap     ] GC(1) Humongous regions: 1->1
[25.696s][info][gc,metaspace] GC(1) Metaspace: 3485K(3648K)->3485K(3648K) NonClass: 3098K(3136K)->3098K(3136K) Class: 386K(512K)->386K(512K)
[25.696s][info][gc          ] GC(1) Pause Young (Normal) (G1 Evacuation Pause) 39M->5M(260M) 1.509ms
[25.696s][info][gc,cpu      ] GC(1) User=0.00s Sys=0.00s Real=0.00s
[45.374s][info][gc,heap,exit] Heap
[45.374s][info][gc,heap,exit]  garbage-first heap   total 266240K, used 60154K [0x0000000700000000, 0x0000000800000000)
[45.374s][info][gc,heap,exit]   region size 2048K, 28 young (57344K), 1 survivors (2048K)
[45.374s][info][gc,heap,exit]  Metaspace       used 3584K, committed 3776K, reserved 1114112K
[45.374s][info][gc,heap,exit]   class space    used 397K, committed 512K, reserved 1048576K

日志中包含了两次较为显著的 年轻代回收信息。

采用JMH的profilers进行观测(7.23):

  • 纯Java: 44801904-aa15-463d-89ce-8473ee8a7767.png
  • 采用JNI
2894e733-06a7-4e6f-a96d-fd1fc0661723.png
referenced pull request
added reviewers
administrator

你的commit似乎没有修改JDK的make文件。
请同时提供性能测试用的JMH程序,并展示修改前后的性能对比。

reviewed
TestSM3Simple.java
jiangsha

看起来,这是一个测试文件。但它的位置并不正确。
目前SM3的测试文件在目录test/jdk/sm/crypto/SM3中。

momo

好的导师,在新的提交中我将删去

momo

看起来,这是一个测试文件。但它的位置并不正确。
目前SM3的测试文件在目录test/jdk/sm/crypto/SM3中。

目前已完善基准测试,并移至pr描述之中。

reviewed
src/java.base/share/native/libsm3jni/Makefile
jiangsha

集成使用JDK本身的make文件,确保在构建JDK同时,也能够自动地构建出这个JNI库。
只需要支持Linux x86_64和aarch64平台。

momo

集成使用JDK本身的make文件,确保在构建JDK同时,也能够自动地构建出这个JNI库。
只需要支持Linux x86_64和aarch64平台。

我之前的提交对Lib.gmk做了如下更改,请问是否满足导师您的要求呢:

ifneq ($(filter $(call isTargetOs, linux) $(call isTargetOs, macosx), true), )
    ifneq ($(filter x86_64 aarch64, $(OPENJDK_TARGET_CPU)), )
      $(eval $(call SetupJdkLibrary, BUILD_LIBSM3JNI, \
          NAME := sm3jni, \
          OPTIMIZATION := HIGH, \
          CFLAGS := $(CFLAGS_JDKLIB) -O3 \
              $(addprefix -I, $(call FindSrcDirsForLib, java.base, sm3jni)) \
              $(if $(KONA_OPENSSL_HOME), -I$(KONA_OPENSSL_HOME)/include), \
          LDFLAGS := $(LDFLAGS_JDKLIB) \
              $(call SET_SHARED_LIBRARY_ORIGIN) \
              $(if $(KONA_OPENSSL_HOME), -L$(KONA_OPENSSL_HOME)/lib), \
          LIBS_linux := -lcrypto -lssl, \
          LIBS_macosx := -lcrypto -lssl, \
      ))

reviewed
src/java.base/share/classes/sun/security/provider/SunEntries.java
jiangsha

SMEntries注册了SM3MessageDigest

momo

新提交中已移除

reviewed
src/java.base/share/native/libsm3jni/sm3_jni.h
jiangsha

参考其它的JNI实现,是否有必要为JNI函数提供.h文件?

momo

感谢导师提醒🙏。经过查阅资料,我看到OpenJDK构建系统在编译过程中使用javac-h自动生成JNI头文件。其原理为当OpenJDK编译包含本机方法的Java类时,它会使用-h标志自动生成相应的C头文件。比如像WinCAPISeedGenerator.c的头文件就没找到。

createor

你的commit似乎没有修改JDK的make文件。
请同时提供性能测试用的JMH程序,并展示修改前后的性能对比。

@johnjiang 目前已提交Lib.gmk文件,这个文件集成了SM3 JNI库到JDK构建系统

administrator

我刚才提交了如下的commit,
johnshajiang@3682ce31

它支持了在构建JDK时使用--with-openssl指定一个本地的OpenSSL目录。
并且提供了OpenSSLUtil用于帮助加载libcrypto.so。在运行时,需要使用系统属性jdk.openssl.cryptoLibPath去指定该文件的位置。
这样,你的PR只需要关注JNI部分,而不需要关注OpenSSL的加载了。

请先同步代码,然后再更新你的PR。

createor

我刚才提交了如下的commit,
johnshajiang@3682ce31

它支持了在构建JDK时使用--with-openssl指定一个本地的OpenSSL目录。
并且提供了OpenSSLUtil用于帮助加载libcrypto.so。在运行时,需要使用系统属性jdk.openssl.cryptoLibPath去指定该文件的位置。
这样,你的PR只需要关注JNI部分,而不需要关注OpenSSL的加载了。

请先同步代码,然后再更新你的PR。

@johnjiang 已同步相关代码

administrator

@johnjiang 已同步相关代码

我的commit不应该成为你的PR的一部分。

createor

我的commit不应该成为你的PR的一部分。

@johnjiang 收到🫡

createor

我的commit不应该成为你的PR的一部分。

@johnjiang 已移除您的提交,并将基准测试结果等内容整合至pr描述之中

administrator

./build/macosx-aarch64-server-release/images/jdk/bin/java
-cp test/micro:jmh-libs/jmh-core-1.37.jar:jmh-libs/jopt-simple-5.0.4.jar:jmh-libs/commons-math3-3.6.1.jar
--add-exports=java.base/sun.security.provider=ALL-UNNAMED
org.openjdk.jmh.Main "SM3JMHTest"

其实,OpenJDK的make是可以直接执行microbenchmark的。

administrator

我刚才提交了如下的commit,
johnshajiang@3682ce31

它支持了在构建JDK时使用--with-openssl指定一个本地的OpenSSL目录。
并且提供了OpenSSLUtil用于帮助加载libcrypto.so。在运行时,需要使用系统属性jdk.openssl.cryptoLibPath去指定该文件的位置。
这样,你的PR只需要关注JNI部分,而不需要关注OpenSSL的加载了。

请先同步代码,然后再更新你的PR。

之前以为已经合并了我的commit,刚才合并进去了 :-(
9d01dbc6

createor

之前以为已经合并了我的commit,刚才合并进去了 :-(
9d01dbc6

@johnjiang 好嘞,我将继续根据您对该issue下pr的点评优化代码

createor
  -cp test/micro:jmh-libs/jmh-core-1.37.jar:jmh-libs/jopt-simple-5.0.4.jar:jmh-libs/commons-math3-3.6.1.jar \
  --add-exports=java.base/sun.security.provider=ALL-UNNAMED \
  org.openjdk.jmh.Main "SM3JMHTest"

其实,OpenJDK的make是可以直接执行microbenchmark的。

@johnjiang 收到;我使用“-Xlog:gc*”参数观测了基准测试的gc情况,测试结果放在了pr的概述;我想请问下,我需要对两种实现情况分别基准测试来观测gc情况嘛,还是有其他更好的方案呢?感谢导师解答🙏

changed title
feat: Add SM3MessageDigest JNI implementation with OpenSSL integration Tested on macOS with OpenSSL 3.x, all standard test vectors pass."
feat: Add SM3MessageDigest JNI implementation with OpenSSL."
administrator

684c2011
上面的commit在test目录下增加了OpenSSL的crypto lib,可用于测试。

administrator

我使用“-Xlog:gc*”参数观测了基准测试的gc情况,测试结果放在了pr的概述;我想请问下,我需要对两种实现情况分别基准测试来观测gc情况嘛,还是有其他更好的方案呢?感谢导师解答

不是使用GC log。JMH提供的profilers中支持展示gc时间。你可以去查一下。

createor

不是使用GC log。JMH提供的profilers中支持展示gc时间。你可以去查一下。

@johnjiang
好的导师,结果已放在pr详情🙏

createor

@johnjiang
好的导师,结果已放在pr详情🙏

@johnjiang(jiangsha) 导师好,有空帮忙code review一下看看有什么问题,我继续改进🙏

reviewed
test/micro/org/openjdk/bench/java/security/SM3JMHTest.java
jiangsha

可以使用make执行JMH测试,并不需要main方法。

reviewed
src/java.base/share/classes/sun/security/provider/SM3Engine.java
jiangsha

sun.security.ec.SM2KeyAgreement目前正在使用SM3Engine。
如果删除了public,编译不会报错吗?

maple

SM2Engine也会使用,从目前PR的改动来看,不能删除public,否则会编译报错

momo

sun.security.ec.SM2KeyAgreement目前正在使用SM3Engine。
如果删除了public,编译不会报错吗?

看了下SM2KeyAgreement 确实在直接使用 SM3Engine(第59行 private final SM3Engine sm3 = new SM3Engine(); )感觉可以和实现SM2KeyAgreement 的同学探讨下更好的方案🙏

momo

看了下SM2KeyAgreement 确实在直接使用 SM3Engine(第59行 private final SM3Engine sm3 = new SM3Engine(); )感觉可以和实现SM2KeyAgreement 的同学探讨下更好的方案🙏

我认为理想的解决方案是重构 SM2KeyAgreement 使用 MessageDigest 接口而不是直接依赖 SM3Engine 实现类,但是考虑到那边的同学也在进行开发,可以先告知他们这个信息。

jiangsha

我在另一个PR中也提到了使用MessageDigest以对SM3Engine解偶。

你可能直接修改SM2KeyAgreement和SM2Engine,而不用关心其它的PR会作何种修改。
这种潜在的冲突在现实工作中本来就是存在的。
如果你的PR先合入了,其他人就得先同步你的代码,解决文件冲突;反之亦然。

reviewed
src/jdk.crypto.ec/share/classes/sun/security/ec/SM2Engine.java
jiangsha

没有导入这个类。

reviewed
src/jdk.crypto.ec/share/classes/sun/security/ec/SM2KeyAgreement.java
jiangsha

没有导入这个类。

administrator

修改代码之后,请务必进行构建与测试。

administrator

@cnb.awOE5sooQFA(momo)
由于 #12 已经解决了issue #7,故关闭该PR。
仍然非常感谢你的关注与工作!

closed the pull request
withdrew a comment.
Pull request has conflict
Reviewer
(jiangsha)
Assignee
None yet
Label
None yet
Participant