├── AUTHORS ├── .gitignore ├── openssl4j ├── src │ ├── main │ │ ├── resources-filtered │ │ │ └── META-INF │ │ │ │ └── openssl4j.properties │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── services │ │ │ │ └── java.security.Provider │ │ └── java │ │ │ └── de │ │ │ └── sfuhrm │ │ │ └── openssl4j │ │ │ ├── PropertyAccessor.java │ │ │ ├── package-info.java │ │ │ ├── NativeLoader.java │ │ │ ├── PhantomReferenceCleanup.java │ │ │ ├── ObjectTransfer.java │ │ │ ├── MessageDigest.java │ │ │ ├── OpenSSL4JProvider.java │ │ │ └── OpenSSLMessageDigestNative.java │ └── test │ │ └── java │ │ └── de │ │ └── sfuhrm │ │ └── openssl4j │ │ ├── GcTest.java │ │ ├── BaseTest.java │ │ ├── OpenSSLMessageDigestNativeTest.java │ │ ├── SunProviderListTest.java │ │ ├── ObjectTransferTest.java │ │ ├── Formatter.java │ │ ├── MessageDigestWithKnownHashesTest.java │ │ ├── SpeedTest.java │ │ └── MessageDigestWithReferenceMDTest.java └── pom.xml ├── images └── bc-sun-ossl-performance.png ├── .github ├── dependabot.yml ├── settings.xml └── workflows │ ├── release.yml │ ├── build-singleplatform.yml │ ├── build-java.yml │ ├── codeql.yml │ └── build-crossplatform.yml ├── Dockerfile ├── openssl4j-objects ├── src │ └── main │ │ └── c │ │ ├── openssl4j_common.c │ │ ├── openssl4j.h │ │ └── openssl4j_messagedigest.c └── pom.xml ├── Makefile ├── pom.xml ├── README.md └── LICENSE /AUTHORS: -------------------------------------------------------------------------------- 1 | Stephan Fuhrmann 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /**/target 2 | .idea 3 | *.iml 4 | *~ 5 | *.versionsBackup 6 | -------------------------------------------------------------------------------- /openssl4j/src/main/resources-filtered/META-INF/openssl4j.properties: -------------------------------------------------------------------------------- 1 | version=${project.version} 2 | -------------------------------------------------------------------------------- /openssl4j/src/main/resources/META-INF/services/java.security.Provider: -------------------------------------------------------------------------------- 1 | de.sfuhrm.openssl4j.OpenSSL4JProvider 2 | -------------------------------------------------------------------------------- /images/bc-sun-ossl-performance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sfuhrm/openssl4j/HEAD/images/bc-sun-ossl-performance.png -------------------------------------------------------------------------------- /openssl4j/src/test/java/de/sfuhrm/openssl4j/GcTest.java: -------------------------------------------------------------------------------- 1 | package de.sfuhrm.openssl4j; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | 6 | /** 7 | * Manual test for {@linkplain PhantomReferenceCleanup}. 8 | * @author Stephan Fuhrmann 9 | */ 10 | public class GcTest { 11 | 12 | @Test 13 | @Disabled 14 | public void gc() { 15 | for (int i=0; i< 10000000; i++) { 16 | MessageDigest.MD5 md5Native = new MessageDigest.MD5(); 17 | System.gc(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "maven" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/settings.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | sonatype 8 | ${env.SONATYPE_USER} 9 | ${env.SONATYPE_PASSWORD} 10 | 11 | 12 | github 13 | ${env.GITHUB_USER} 14 | ${env.GITHUB_PASSWORD} 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /openssl4j/src/test/java/de/sfuhrm/openssl4j/BaseTest.java: -------------------------------------------------------------------------------- 1 | package de.sfuhrm.openssl4j; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | 5 | import java.io.IOException; 6 | import java.nio.charset.Charset; 7 | import java.nio.charset.StandardCharsets; 8 | 9 | /** 10 | * Test base. 11 | * @author Stephan Fuhrmann 12 | */ 13 | public class BaseTest { 14 | 15 | Formatter formatter; 16 | Charset ascii; 17 | 18 | @BeforeEach 19 | public void before() throws IOException { 20 | NativeLoader.loadAll(); 21 | 22 | formatter = Formatter.getInstance(); 23 | ascii = StandardCharsets.US_ASCII; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Dockerfile for building a per-platform build 3 | # in github actions. 4 | # 5 | # See .github/workflows/build.yml 6 | # 7 | FROM debian:11 8 | 9 | ENV JAVA_HOME=/opt/java/openjdk 10 | COPY --from=eclipse-temurin:11 $JAVA_HOME $JAVA_HOME 11 | ENV PATH="${JAVA_HOME}/bin:${PATH}" 12 | 13 | RUN apt-get update && apt-get install -y \ 14 | make gcc libssl1.1 libssl-dev 15 | COPY . openssl4j 16 | ENV JAVA_HOME=/opt/java/openjdk/ 17 | RUN echo "JAVA_HOME is ${JAVA_HOME}" 18 | RUN echo "OS_ARCH is $(cd openssl4j/build-helper && ${JAVA_HOME}/bin/java -Xint OsArch.java)" 19 | RUN cd openssl4j && \ 20 | make 21 | RUN cd openssl4j/target && ls -al 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This release process looks for tags and produces releases. 2 | name: Release 3 | 4 | on: 5 | push: 6 | tags: 7 | - 'openssl4j-parent-[0-9]+*' 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | env: 13 | DEBIAN_FRONTEND: noninteractive 14 | 15 | steps: 16 | - uses: actions/create-release@v1 17 | id: create_release 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | with: 21 | tag_name: ${{ github.ref }} 22 | release_name: ${{ github.ref }} 23 | draft: true 24 | body: | 25 | # Release ${{ steps.version.outputs.version }} 26 | ## Changes 27 | 28 | * 1 29 | * 2 30 | * 3 31 | 32 | -------------------------------------------------------------------------------- /openssl4j/src/main/java/de/sfuhrm/openssl4j/PropertyAccessor.java: -------------------------------------------------------------------------------- 1 | package de.sfuhrm.openssl4j; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.util.Properties; 6 | 7 | class PropertyAccessor { 8 | private static Properties properties; 9 | 10 | /** Gets a property. 11 | * @param name the property name. 12 | * @param defaultValue the default value if the property was not set. 13 | * */ 14 | static String get(String name, String defaultValue) { 15 | if (properties == null) { 16 | properties = loadOpenssl4jProperties(); 17 | } 18 | return (String)properties.getOrDefault(name, defaultValue); 19 | } 20 | 21 | private static Properties loadOpenssl4jProperties() { 22 | Properties result = new Properties(); 23 | try (InputStream inputStream = ObjectTransfer.class.getResourceAsStream("/META-INF/openssl4j.properties")) { 24 | if (inputStream != null) { 25 | result.load(inputStream); 26 | } 27 | } 28 | catch (IOException e) { 29 | } 30 | return result; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /openssl4j-objects/src/main/c/openssl4j_common.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** OpenSSL Common Java Binding Code 3 | ** @author Stephan Fuhrmann 4 | */ 5 | 6 | #include 7 | #include "openssl4j.h" 8 | 9 | void throw_error(JNIEnv *env, const char *exceptionClassName, const char *message) { 10 | jclass exceptionClass = (*env)->FindClass(env, exceptionClassName); 11 | if (exceptionClass != NULL) { 12 | jint success = (*env)->ThrowNew(env, exceptionClass, message); 13 | if (0 != success) { 14 | (*env)->FatalError(env, "Could not throw exception"); 15 | } 16 | } else { 17 | (*env)->FatalError(env, "Didn't find IllegalStateException class"); 18 | } 19 | } 20 | 21 | void* get_context_from(JNIEnv *env, jobject context) { 22 | if (context == NULL) { 23 | throw_error(env, NULL_POINTER_EXCEPTION, "context is NULL"); 24 | return NULL; 25 | } 26 | void* context_data = (void*) (*env)->GetDirectBufferAddress(env, context); 27 | if (context_data == NULL) { 28 | throw_error(env, ILLEGAL_STATE_EXCEPTION, "GetDirectBufferAddress() for Context failed"); 29 | } 30 | return context_data; 31 | } 32 | -------------------------------------------------------------------------------- /openssl4j/src/test/java/de/sfuhrm/openssl4j/OpenSSLMessageDigestNativeTest.java: -------------------------------------------------------------------------------- 1 | package de.sfuhrm.openssl4j; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Disabled; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.io.IOException; 9 | import java.util.Set; 10 | 11 | /** 12 | * Test for {@linkplain OpenSSLMessageDigestNative}. 13 | * @author Stephan Fuhrmann 14 | */ 15 | public class OpenSSLMessageDigestNativeTest { 16 | 17 | @BeforeEach 18 | public void init() throws IOException { 19 | NativeLoader.loadAll(); 20 | } 21 | 22 | @Test 23 | public void getMessageDigestList() { 24 | Set sslAlgos = OpenSSLMessageDigestNative.getMessageDigestList(); 25 | Assertions.assertNotNull(sslAlgos); 26 | Assertions.assertNotEquals(0, sslAlgos.size()); 27 | Assertions.assertTrue(sslAlgos.contains("MD5")); 28 | } 29 | 30 | @Test 31 | public void freeWithNull() { 32 | Assertions.assertThrows(NullPointerException.class, () -> 33 | OpenSSLMessageDigestNative.free(null) 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /openssl4j/src/test/java/de/sfuhrm/openssl4j/SunProviderListTest.java: -------------------------------------------------------------------------------- 1 | package de.sfuhrm.openssl4j; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.security.Provider; 6 | import java.security.Security; 7 | import java.util.Arrays; 8 | import java.util.Comparator; 9 | import java.util.TreeSet; 10 | 11 | /** 12 | * Lists the registered Security providers. 13 | * @author Stephan Fuhrmann 14 | */ 15 | public class SunProviderListTest { 16 | 17 | @Test 18 | public void list() { 19 | Provider[] providers = Security.getProviders(); 20 | Arrays.sort(providers, Comparator.comparing(Provider::getName)); 21 | for (Provider provider : providers) { 22 | System.out.println(provider.getName()); 23 | TreeSet sortedServices = new TreeSet<>(Comparator.comparing(o -> (o.getType() + o.getAlgorithm()))); 24 | sortedServices.addAll(provider.getServices()); 25 | for (Provider.Service service : sortedServices) { 26 | System.out.println(service.getType()+" - "+service.getClassName()+" - " + service.getAlgorithm()); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /openssl4j/src/main/java/de/sfuhrm/openssl4j/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Binding classes from the Java JCA providers. The 3 | * native OpenSSL library is being linked dynamically 4 | * into the JVMs address space. 5 | * 6 | *

Usage

7 | * There are multiple ways of using the library. 8 | * 9 | *

Runtime configuration

10 | * 11 | * You can create an instance of a message digest as follows if you are 12 | * explicitly specifying the crypto provider {@code OpenSSL4JProvider}: 13 | * 14 | * {@code MessageDigest md5 = new MessageDigest("MD5", new OpenSSL4JProvider());} 15 | * 16 | *

JDK-wide configuration

17 | * 18 | * You can specify the {@code OpenSSL4JProvider} to be used JDK-wide 19 | * implicitly by the by changing your 20 | *
    21 | *
  • Linux, or macOS: `<java-home>/conf/security/java.security
  • 22 | *
  • Windows: `<java-home>\conf\security\java.security
  • 23 | *
24 | * 25 | * to have the OpenSSL4J provider in the first place: 26 | * 27 | * 28 | security.provider.1=OpenSSL4J 29 | security.provider.2=SUN 30 | security.provider.3=SunRsaSign 31 | security.provider.4=SunEC 32 | security.provider.5=SunJSSE 33 | security.provider.6=SunJCE 34 | security.provider.7=SunJGSS 35 | security.provider.8=SunSASL 36 | security.provider.9=XMLDSig 37 | security.provider.10=SunPCSC 38 | 39 | */ 40 | package de.sfuhrm.openssl4j; -------------------------------------------------------------------------------- /openssl4j/src/test/java/de/sfuhrm/openssl4j/ObjectTransferTest.java: -------------------------------------------------------------------------------- 1 | package de.sfuhrm.openssl4j; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | 6 | /** 7 | * Manual test for {@linkplain ObjectTransfer}. 8 | * @author Stephan Fuhrmann 9 | */ 10 | public class ObjectTransferTest { 11 | 12 | @Test 13 | public void enforceAlnumWithAlnum() { 14 | System.setProperty("foo", "foobar123"); 15 | Assertions.assertEquals("foobar123", ObjectTransfer.getSystemPropertyAlnum("foo")); 16 | } 17 | 18 | @Test 19 | public void enforceAlnumWithSpace() { 20 | System.setProperty("foo", "foobar 123"); 21 | Assertions.assertEquals("foobar_123", ObjectTransfer.getSystemPropertyAlnum("foo")); 22 | } 23 | 24 | @Test 25 | public void enforceAlnumWithBackslash() { 26 | System.setProperty("foo", "foobar\\123"); 27 | Assertions.assertEquals("foobar_123", ObjectTransfer.getSystemPropertyAlnum("foo")); 28 | } 29 | 30 | @Test 31 | public void enforceAlnumWithNotExist() { 32 | Assertions.assertThrows(NullPointerException.class, () -> ObjectTransfer.getSystemPropertyAlnum("NOT_EXIST")); 33 | } 34 | 35 | @Test 36 | public void enforceAlnumWithNull() { 37 | Assertions.assertThrows(NullPointerException.class, () -> ObjectTransfer.getSystemPropertyAlnum(null)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/build-singleplatform.yml: -------------------------------------------------------------------------------- 1 | name: Single-Platform Build 2 | 3 | on: push 4 | 5 | env: 6 | BUILD_JDK: 11 7 | 8 | jobs: 9 | 10 | # 11 | # build the java application with single (local) platform 12 | # 13 | single_platform_jar: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - 17 | name: Checkout 18 | uses: actions/checkout@v3 19 | 20 | - 21 | name: Set up JDK 22 | uses: actions/setup-java@v3 23 | with: 24 | distribution: 'adopt' 25 | java-version: ${{ env.BUILD_JDK }} 26 | - 27 | name: Cache local Maven repository 28 | uses: actions/cache@v3 29 | with: 30 | path: ~/.m2/repository 31 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 32 | restore-keys: | 33 | ${{ runner.os }}-maven- 34 | - 35 | name: Build using the build script 36 | env: 37 | GITHUB_USER: ${{ secrets.GH_USER }} 38 | GITHUB_PASSWORD: ${{ secrets.GH_PASSWORD }} 39 | run: ./build.sh 40 | 41 | - 42 | name: Archive Test Results 43 | if: always() 44 | uses: actions/upload-artifact@v3 45 | with: 46 | name: target 47 | path: openssl4j/target 48 | 49 | - 50 | uses: actions/upload-artifact@v3 51 | with: 52 | name: build 53 | path: openssl4j/target/*.jar 54 | -------------------------------------------------------------------------------- /openssl4j-objects/src/main/c/openssl4j.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** OpenSSL to Java Header Code 3 | ** @author Stephan Fuhrmann 4 | */ 5 | 6 | #ifndef OPENSSL4J_H 7 | #define OPENSSL4J_H 8 | 9 | #include 10 | 11 | #define NULL_POINTER_EXCEPTION "java/lang/NullPointerException" 12 | #define ILLEGAL_STATE_EXCEPTION "java/lang/IllegalStateException" 13 | #define UNSUPPORTED_OPERATION_EXCEPTION "java/lang/UnsupportedOperationException" 14 | 15 | struct StringArrayPosition { 16 | /* The next write index in the array below. */ 17 | jint index; 18 | /* The length of the array below. */ 19 | jint length; 20 | /* The JNI env to use for doing JNI environment calls. */ 21 | JNIEnv *env; 22 | /* The String array to store the names in. */ 23 | jobjectArray array; 24 | }; 25 | 26 | /* 27 | * Throws an exception. Actually signals the JVM that an exception shall be thrown. 28 | * C methods need to terminate normally. 29 | * @param env the JNI environment. 30 | * @param exceptionClassName the Java Name of the exception to throw, for example java/lang/NullPointerException. 31 | * @param message the exception message to pass to the exception constructor. 32 | */ 33 | void throw_error(JNIEnv *env, const char *exceptionClassName, const char *message); 34 | 35 | /* 36 | * Returns the MD / Crypto context from a passed in ByteBuffer jobject. 37 | * @param env the JNI environment. 38 | * @param context a pointer to the context ByteBuffer object. 39 | */ 40 | void* get_context_from(JNIEnv *env, jobject context); 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /openssl4j/src/main/java/de/sfuhrm/openssl4j/NativeLoader.java: -------------------------------------------------------------------------------- 1 | package de.sfuhrm.openssl4j; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import java.util.HashSet; 7 | import java.util.Set; 8 | 9 | /** 10 | * Loads the object files. 11 | * @author Stephan Fuhrmann 12 | */ 13 | class NativeLoader { 14 | /** Which objects have already been loaded? */ 15 | private final Set loaded; 16 | 17 | private static boolean isLoaded = false; 18 | 19 | static final String[] OBJECTS = { 20 | "libopenssl4j" 21 | }; 22 | 23 | NativeLoader() { 24 | loaded = new HashSet<>(); 25 | } 26 | 27 | /** 28 | * Loads all object files. 29 | * @throws IOException if transferring the object files failed. 30 | */ 31 | static void loadAll() throws IOException { 32 | if (isLoaded) { 33 | return; 34 | } 35 | NativeLoader nativeLoader = new NativeLoader(); 36 | ObjectTransfer objectTransfer = new ObjectTransfer(); 37 | objectTransfer.transfer(OBJECTS); 38 | for (Path path : objectTransfer.getObjectFiles()) { 39 | nativeLoader.load(path); 40 | } 41 | isLoaded = true; 42 | } 43 | 44 | /** Loads an object file and remembers it was loaded. */ 45 | final void load(Path name) { 46 | if (!loaded.contains(name)) { 47 | if (Files.isRegularFile(name)) { 48 | System.load(name.toString()); 49 | loaded.add(name); 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #### 2 | # 3 | # Makefile for generating the native C library 4 | # 5 | #### 6 | JAVA_OS_ARCH:=$(shell cd build-helper && ${JAVA_HOME}/bin/java -Xint OsArch.java ) 7 | 8 | JNI_JAVA_SOURCES=openssl4j/src/main/java 9 | JNI_C_SOURCES=openssl4j-objects/src/main/c 10 | TARGET=target 11 | INSTALL_TARGET=openssl4j-objects/src/main/resources/objects 12 | JNI_JAVA_FILES=${JNI_JAVA_SOURCES}/de/sfuhrm/openssl4j/OpenSSLMessageDigestNative.java 13 | JNI_HEADER_FILES=${TARGET}/include/de_sfuhrm_openssl4j_OpenSSLMessageDigestNative.h 14 | 15 | .PHONY: all 16 | .PHONY: clean 17 | .PHONY: install 18 | 19 | install: ${TARGET}/libopenssl4j-${JAVA_OS_ARCH}.so 20 | mkdir -p ${INSTALL_TARGET} 21 | cp $< ${INSTALL_TARGET} 22 | 23 | clean: 24 | rm -fr ${TARGET} ${INSTALL_TARGET} 25 | 26 | ${TARGET}/include/%.h: ${JNI_JAVA_FILES} 27 | mkdir -p ${TARGET}/include 28 | ${JAVA_HOME}/bin/javac -J-Xint -classpath ${JNI_JAVA_SOURCES} -h ${TARGET}/include -d ${TARGET} -s ${TARGET} ${JNI_JAVA_FILES} 29 | 30 | ${TARGET}/%.o: ${JNI_C_SOURCES}/%.c ${JNI_HEADER_FILES} 31 | gcc -Wall -Werror -fPIC -c -o $@ \ 32 | -I${TARGET}/include/ \ 33 | -I${JAVA_HOME}/include \ 34 | -I${JAVA_HOME}/include/linux \ 35 | $< 36 | 37 | ${TARGET}/libopenssl4j-${JAVA_OS_ARCH}.so: ${TARGET}/openssl4j_common.o ${TARGET}/openssl4j_messagedigest.o 38 | # link libssl statically, libc dynamically 39 | # this avoids the need for specific libssl versions 40 | # in the system 41 | ld --verbose --pic-executable -fPIC -shared -o $@ \ 42 | ${TARGET}/openssl4j_common.o \ 43 | ${TARGET}/openssl4j_messagedigest.o \ 44 | --whole-archive -Bstatic -lssl \ 45 | --no-whole-archive -Bdynamic -lcrypto -lc 46 | -------------------------------------------------------------------------------- /openssl4j/src/test/java/de/sfuhrm/openssl4j/Formatter.java: -------------------------------------------------------------------------------- 1 | package de.sfuhrm.openssl4j; 2 | 3 | /** 4 | * Formats digests to hexadecimal Strings. 5 | * @author Stephan Fuhrmann 6 | */ 7 | public final class Formatter { 8 | 9 | /** Only singleton. */ 10 | private Formatter() { 11 | 12 | } 13 | 14 | /** Lazily initialized singleton. */ 15 | private static Formatter instance; 16 | 17 | /** Get a formatter instance. 18 | * @return the single shared instance. 19 | * */ 20 | public static Formatter getInstance() { 21 | if (instance == null) { 22 | instance = new Formatter(); 23 | } 24 | return instance; 25 | } 26 | 27 | /** Formats the given digest bytes to a hexadecimal 28 | * String. 29 | * @param digest the digest bytes to format. 30 | * @return the digest bytes formatted as hexadecimal 31 | * String. Every byte is formatted as two characters. 32 | * */ 33 | public String format(final byte[] digest) { 34 | StringBuilder stringBuilder = new StringBuilder(); 35 | for (byte b : digest) { 36 | stringBuilder 37 | .append( 38 | digitValue(0x0f & (b >> 4))) 39 | .append( 40 | digitValue(0x0f & b)); 41 | } 42 | return stringBuilder.toString(); 43 | } 44 | 45 | 46 | 47 | /** Formats a nibble to a char. 48 | * @param b a nibble value between 0 and 15 (inclusive). 49 | * @return the character value. 50 | * @throws IllegalArgumentException if the character 51 | * was not inside the hex chars. 52 | * */ 53 | private static char digitValue(final int b) { 54 | if (b >= 0 && b <= 9) { 55 | return (char)(b + '0'); 56 | } else if (b >= 0xa && b <= 0xf) { 57 | return (char)(b - 10 + 'a'); 58 | } else { 59 | throw new IllegalArgumentException( 60 | "Digit not formattable: " + b); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /.github/workflows/build-java.yml: -------------------------------------------------------------------------------- 1 | name: Cross-Platform Java Build 2 | 3 | on: push 4 | 5 | jobs: 6 | 7 | # 8 | # build the java application using 9 | # cross-platform libs in openssl4j-objects 10 | # from github repo 11 | # 12 | build_crossplatform_jar: 13 | strategy: 14 | matrix: 15 | include: 16 | - BUILD_JDK: 8 17 | - BUILD_JDK: 11 18 | - BUILD_JDK: 17 19 | 20 | runs-on: ubuntu-latest 21 | steps: 22 | - 23 | name: Checkout 24 | uses: actions/checkout@v3 25 | 26 | - 27 | name: Set up JDK 28 | uses: actions/setup-java@v3 29 | with: 30 | distribution: 'adopt' 31 | java-version: ${{ matrix.BUILD_JDK }} 32 | - 33 | name: Cache local Maven repository 34 | uses: actions/cache@v3 35 | with: 36 | path: ~/.m2/repository 37 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 38 | restore-keys: | 39 | ${{ runner.os }}-maven- 40 | - 41 | name: Build with Maven 42 | env: 43 | GITHUB_USER: ${{ secrets.GH_USER }} 44 | GITHUB_PASSWORD: ${{ secrets.GH_PASSWORD }} 45 | run: mvn -B package --file pom.xml -s .github/settings.xml 46 | 47 | - 48 | name: Archive Test Results 49 | if: always() 50 | uses: actions/upload-artifact@v3 51 | with: 52 | name: target-${{ matrix.BUILD_JDK }} 53 | path: openssl4j/target 54 | 55 | - 56 | uses: actions/upload-artifact@v3 57 | with: 58 | name: build-${{ matrix.BUILD_JDK }} 59 | path: openssl4j/target/*.jar 60 | - 61 | name: Deploy with Maven to SONATYPE OSS Snapshot if secrets are set 62 | env: 63 | SONATYPE_USER: ${{ secrets.SONATYPE_USER }} 64 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 65 | run: | 66 | if [ "${SONATYPE_USER}" != "" -a "${SONATYPE_PASSWORD}" != "" ]; then 67 | mvn -B deploy -P snapshot -s .github/settings.xml 68 | else 69 | echo "Not deploying, secrets SONATYPE_USER and SONATYPE_PASSWORD not provided" 70 | fi 71 | -------------------------------------------------------------------------------- /openssl4j-objects/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | de.sfuhrm 5 | openssl4j-objects 6 | 0-SNAPSHOT 7 | OpenSSL4J Shared Libraries 8 | OpenSSL4J Shared Library Objects 9 | https://github.com/sfuhrm/openssl4j 10 | 11 | git@github.com:sfuhrm/openssl4j 12 | scm:git:git@github.com:sfuhrm/openssl4j.git 13 | HEAD 14 | 15 | 16 | 17 | GNU Lesser General Public License, Version 3.0 18 | https://www.gnu.org/licenses/lgpl-3.0.en.html 19 | repo 20 | 21 | 22 | 23 | 24 | stephan 25 | Stephan Fuhrmann 26 | s@sfuhrm.de 27 | 28 | 29 | 30 | UTF-8 31 | 32 | 33 | 34 | github 35 | 36 | 37 | github 38 | GitHub Packages 39 | https://maven.pkg.github.com/sfuhrm/openssl4j 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | org.apache.maven.plugins 48 | maven-jar-plugin 49 | 3.2.0 50 | 51 | 52 | 53 | true 54 | true 55 | 56 | 57 | ${project.version} 58 | ${basedir} 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /openssl4j/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | de.sfuhrm 6 | openssl4j-parent 7 | 0.5.1-SNAPSHOT 8 | 9 | openssl4j 10 | OpenSSL4J JNI 11 | Java binding to OpenSSL library functions 12 | 13 | 2023-10-21-08-46-22 14 | 15 | 16 | 17 | 18 | ${basedir}/src/main/resources 19 | false 20 | 21 | 22 | ${basedir}/target/openssl4j-objects 23 | false 24 | 25 | 26 | ${basedir}/src/main/resources-filtered 27 | true 28 | 29 | 30 | 31 | 32 | org.apache.maven.plugins 33 | maven-dependency-plugin 34 | 35 | 36 | copy-shared-objects 37 | validate 38 | 39 | unpack 40 | 41 | 42 | 43 | 44 | de.sfuhrm 45 | openssl4j-objects 46 | ${openssl4j-objects.version} 47 | 48 | 49 | target/openssl4j-objects 50 | true 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | org.bouncycastle 61 | bcprov-jdk18on 62 | 1.77 63 | test 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /openssl4j/src/main/java/de/sfuhrm/openssl4j/PhantomReferenceCleanup.java: -------------------------------------------------------------------------------- 1 | package de.sfuhrm.openssl4j; 2 | 3 | import java.lang.ref.PhantomReference; 4 | import java.lang.ref.ReferenceQueue; 5 | import java.nio.ByteBuffer; 6 | import java.util.Collections; 7 | import java.util.HashSet; 8 | import java.util.Objects; 9 | import java.util.Set; 10 | import java.util.function.Consumer; 11 | 12 | /** 13 | * Frees native AbstractNative objects. 14 | * The ByteBuffer objects are allocated in {@linkplain OpenSSLMessageDigestNative#OpenSSLMessageDigestNative(String)} ()} 15 | * and are not used any longer. 16 | * @author Stephan Fuhrmann 17 | */ 18 | class PhantomReferenceCleanup { 19 | 20 | /** The reference queue of unused AbstractNative objects. */ 21 | private static final ReferenceQueue BYTE_BUFFER_REFERENCE_QUEUE = new ReferenceQueue<>(); 22 | 23 | /** Is the thread running? */ 24 | private static boolean running = false; 25 | 26 | private static final Set nativePhantomReferenceList = Collections.synchronizedSet(new HashSet<>()); 27 | 28 | private static class NativePhantomReference extends PhantomReference { 29 | private final Consumer freeFunction; 30 | private final ByteBuffer byteBuffer; 31 | NativePhantomReference(Object abstractNative, Consumer freeFunction, ByteBuffer context) { 32 | super(abstractNative, BYTE_BUFFER_REFERENCE_QUEUE); 33 | this.freeFunction = freeFunction; 34 | this.byteBuffer = context; 35 | } 36 | public void free() { 37 | freeFunction.accept(byteBuffer); 38 | } 39 | } 40 | 41 | /** Enqueues a AbstractNative for later cleanup. */ 42 | static void enqueueForCleanup(Object ref, Consumer freeFunction, ByteBuffer context) { 43 | NativePhantomReference phantomReference = new NativePhantomReference( 44 | Objects.requireNonNull(ref), 45 | Objects.requireNonNull(freeFunction), 46 | Objects.requireNonNull(context)); 47 | nativePhantomReferenceList.add(phantomReference); 48 | startIfNeeded(); 49 | } 50 | 51 | /** Checks whether the queue thread is already 52 | * running and starts it if not. 53 | * */ 54 | static synchronized void startIfNeeded() { 55 | if (!running) { 56 | running = true; 57 | Runnable r = () -> { 58 | try { 59 | while (true) { 60 | NativePhantomReference reference = (NativePhantomReference)BYTE_BUFFER_REFERENCE_QUEUE.remove(); 61 | reference.free(); 62 | nativePhantomReferenceList.remove(reference); 63 | } 64 | } catch (InterruptedException e) { 65 | e.printStackTrace(); 66 | } 67 | }; 68 | Thread t = new Thread(r, "OpenSSL-Cleanup"); 69 | t.setDaemon(true); 70 | t.start(); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '44 12 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'cpp', 'java' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Use only 'java' to analyze code written in Java, Kotlin or both 38 | # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both 39 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 40 | 41 | steps: 42 | - name: Checkout repository 43 | uses: actions/checkout@v3 44 | 45 | # Initializes the CodeQL tools for scanning. 46 | - name: Initialize CodeQL 47 | uses: github/codeql-action/init@v2 48 | with: 49 | languages: ${{ matrix.language }} 50 | # If you wish to specify custom queries, you can do so here or in a config file. 51 | # By default, queries listed here will override any specified in a config file. 52 | # Prefix the list here with "+" to use these queries and those in the config file. 53 | 54 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 55 | # queries: security-extended,security-and-quality 56 | 57 | 58 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 59 | # If this step fails, then you should remove it and run the build manually (see below) 60 | - name: Build it 61 | run: ./build.sh 62 | 63 | # ℹ️ Command-line programs to run using the OS shell. 64 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 65 | 66 | # If the Autobuild fails above, remove it and uncomment the following three lines. 67 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 68 | 69 | # - run: | 70 | # echo "Run, Build Application using script" 71 | # ./location_of_script_within_repo/buildscript.sh 72 | 73 | - name: Perform CodeQL Analysis 74 | uses: github/codeql-action/analyze@v2 75 | with: 76 | category: "/language:${{matrix.language}}" 77 | -------------------------------------------------------------------------------- /openssl4j/src/test/java/de/sfuhrm/openssl4j/MessageDigestWithKnownHashesTest.java: -------------------------------------------------------------------------------- 1 | package de.sfuhrm.openssl4j; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.params.ParameterizedTest; 5 | import org.junit.jupiter.params.provider.Arguments; 6 | import org.junit.jupiter.params.provider.MethodSource; 7 | 8 | import java.io.IOException; 9 | import java.nio.charset.Charset; 10 | import java.nio.charset.StandardCharsets; 11 | import java.security.MessageDigest; 12 | import java.security.NoSuchAlgorithmException; 13 | import java.security.Provider; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.stream.Stream; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | 20 | /** 21 | * Test cases that compare the message digest of the 22 | * implementations with well known expected outputs. 23 | * @author Stephan Fuhrmann 24 | */ 25 | public class MessageDigestWithKnownHashesTest extends BaseTest { 26 | 27 | private static final String[] REFERENCES = new String[] { 28 | "BLAKE2b512", "", "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce", 29 | "BLAKE2b512", "The quick brown fox jumps over the lazy dog", "a8add4bdddfd93e4877d2746e62817b116364a1fa7bc148d95090bc7333b3673f82401cf7aa2e4cb1ecd90296e3f14cb5413f8ed77be73045b13914cdcd6a918", 30 | "BLAKE2b512", "The quick brown fox jumps over the lazy dof", "ab6b007747d8068c02e25a6008db8a77c218d94f3b40d2291a7dc8a62090a744c082ea27af01521a102e42f480a31e9844053f456b4b41e8aa78bbe5c12957bb", 31 | "BLAKE2b512", "", "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce", 32 | "BLAKE2s256", "", "69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9", 33 | "RIPEMD160", "", "9c1185a5c5e9fc54612808977ee8f548b2258d31", 34 | "RIPEMD160", "The quick brown fox jumps over the lazy dog", "37f332f68db77bd9d7edd4969571ad671cf9dd3b", 35 | "RIPEMD160", "The quick brown fox jumps over the lazy cog", "132072df690933835eb8b6ad0b77e7b6f14acad7", 36 | "MD4", "", "31d6cfe0d16ae931b73c59d7e0c089c0", 37 | "MD4", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "043f8582f241db351ce627e153e7f0e4", 38 | "Whirlpool", "", "19FA61D75522A4669B44E39C1D2E1726C530232130D407F89AFEE0964997F7A73E83BE698B288FEBCF88E3E03C4F0757EA8964E59B63D93708B138CC42A66EB3", 39 | "Whirlpool", "The quick brown fox jumps over the lazy eog", "C27BA124205F72E6847F3E19834F925CC666D0974167AF915BB462420ED40CC50900D85A1F923219D832357750492D5C143011A76988344C2635E69D06F2D38C" 40 | }; 41 | 42 | private static Stream provideTestArguments() throws NoSuchAlgorithmException, IOException { 43 | List result = new ArrayList<>(); 44 | Provider openSsl = new OpenSSL4JProvider(); 45 | 46 | for (int i=0; i < REFERENCES.length; i+= 3) { 47 | String algorithm = REFERENCES[i]; 48 | String clearText = REFERENCES[i + 1]; 49 | String expected = REFERENCES[i + 2]; 50 | 51 | result.add(Arguments.of( 52 | algorithm, 53 | MessageDigest.getInstance(algorithm, openSsl), 54 | clearText.getBytes(StandardCharsets.US_ASCII), 55 | expected 56 | )); 57 | } 58 | 59 | return result.stream(); 60 | } 61 | 62 | @ParameterizedTest 63 | @MethodSource("provideTestArguments") 64 | public void compareWithWellKnownHash(String digestName, MessageDigest testMD, byte[] clearText, String expectedDigest) { 65 | testMD.update(clearText); 66 | byte[] actual = testMD.digest(); 67 | 68 | assertEquals(expectedDigest.toLowerCase(), formatter.format(actual)); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /.github/workflows/build-crossplatform.yml: -------------------------------------------------------------------------------- 1 | name: Cross-Platform C Build 2 | 3 | on: push 4 | 5 | env: 6 | QEMU_IMAGE: tonistiigi/binfmt:qemu-v5.0.1 7 | BUILD_JDK: 11 8 | VERSION_SUFFIX: '' 9 | 10 | jobs: 11 | # 12 | # show the available qemu platforms to have a list of all platforms 13 | # 14 | qemu_show_platform_info: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - 18 | name: Checkout 19 | uses: actions/checkout@v3 20 | - 21 | name: Set up QEMU for all platforms 22 | id: qemu 23 | uses: docker/setup-qemu-action@v2 24 | with: 25 | image: ${{ env.QEMU_IMAGE }} 26 | platforms: all 27 | - 28 | name: Available platforms 29 | run: echo ${{ steps.qemu.outputs.platforms }} 30 | 31 | # 32 | # build the different platforms using QEMU 33 | # 34 | qemu_platform_object_files: 35 | runs-on: ubuntu-latest 36 | strategy: 37 | matrix: 38 | include: 39 | - platform: linux/amd64 40 | - platform: linux/arm64 41 | - platform: linux/s390x 42 | - platform: linux/arm/v7 43 | #- platform: linux/arm/v6 44 | - platform: linux/ppc64le 45 | #- platform: linux/riscv64 46 | #- platform: linux/386 47 | #- platform: linux/mips64le 48 | steps: 49 | - 50 | name: Checkout 51 | uses: actions/checkout@v3 52 | - 53 | name: Set up QEMU for ${{ matrix.platform }} 54 | id: qemu 55 | uses: docker/setup-qemu-action@v2 56 | with: 57 | image: ${{ env.QEMU_IMAGE }} 58 | platforms: ${{ matrix.platform }} 59 | - 60 | name: Set up Docker Buildx 61 | uses: docker/setup-buildx-action@v2 62 | - 63 | name: Login to DockerHub 64 | uses: docker/login-action@v2 65 | with: 66 | username: ${{ secrets.DOCKERHUB_USERNAME }} 67 | password: ${{ secrets.DOCKERHUB_TOKEN }} 68 | 69 | - 70 | name: Build on ${{ matrix.platform }} 71 | uses: docker/build-push-action@v3 72 | with: 73 | context: . 74 | platforms: ${{ matrix.platform }} 75 | outputs: "type=tar,dest=image.tar" 76 | - 77 | name: Show it 78 | run: | 79 | ls -alR image.tar 80 | tar -tvf image.tar 81 | 82 | - 83 | name: Extract shared library 84 | run: | 85 | tar --wildcards -xvf image.tar openssl4j/target/libopenssl4j-*.so 86 | 87 | - uses: actions/upload-artifact@v3 88 | with: 89 | name: shared_objects 90 | path: openssl4j/target/*.so 91 | 92 | # 93 | # deploy the openssl4j-objects artifact to github packages 94 | # 95 | deploy_openssl4j_objects: 96 | needs: qemu_platform_object_files 97 | runs-on: ubuntu-latest 98 | steps: 99 | - 100 | name: Checkout 101 | uses: actions/checkout@v3 102 | - 103 | uses: actions/download-artifact@v3 104 | with: 105 | name: shared_objects 106 | path: openssl4j-objects/src/main/resources/objects 107 | - 108 | name: Set up JDK 109 | uses: actions/setup-java@v3 110 | with: 111 | distribution: 'adopt' 112 | java-version: ${{ env.BUILD_JDK }} 113 | - 114 | name: Cache local Maven repository 115 | uses: actions/cache@v3 116 | with: 117 | path: ~/.m2/repository 118 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 119 | restore-keys: | 120 | ${{ runner.os }}-maven- 121 | - 122 | name: Set by-date version with Maven 123 | run: mvn versions:set -DnewVersion=$(date +"%Y-%m-%d-%H-%M-%S")${{ env.VERSION_SUFFIX }} 124 | working-directory: openssl4j-objects 125 | - 126 | name: Build with Maven 127 | run: mvn -B package --file pom.xml 128 | working-directory: openssl4j-objects 129 | 130 | - 131 | uses: actions/upload-artifact@v3 132 | with: 133 | name: openssl4j-objects 134 | path: openssl4j-objects/target/*.jar 135 | - 136 | name: Publish package to Github 137 | run: mvn -Pgithub --batch-mode deploy 138 | working-directory: openssl4j-objects 139 | env: 140 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 141 | -------------------------------------------------------------------------------- /openssl4j/src/main/java/de/sfuhrm/openssl4j/ObjectTransfer.java: -------------------------------------------------------------------------------- 1 | package de.sfuhrm.openssl4j; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.nio.file.Paths; 8 | import java.nio.file.attribute.PosixFilePermission; 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.HashSet; 12 | import java.util.List; 13 | import java.util.Objects; 14 | import java.util.Set; 15 | 16 | /** 17 | * Transfers the object files from the JAR file 18 | * to a temporary directory. The temporary directory 19 | * will be deleted when the JVM shuts down. 20 | * @author Stephan Fuhrmann 21 | */ 22 | final class ObjectTransfer { 23 | 24 | /** The destination temporary directory. */ 25 | private final Path targetDirectory; 26 | 27 | /** The libraries copies. */ 28 | private final List libraries; 29 | 30 | ObjectTransfer() throws IOException { 31 | targetDirectory = Files.createTempDirectory("native"); 32 | libraries = new ArrayList<>(); 33 | Runnable removeTarget = () -> { 34 | for (Path p : libraries) { 35 | try { 36 | Files.delete(p); 37 | } catch (IOException e) { 38 | e.printStackTrace(); 39 | } 40 | } 41 | try { 42 | Files.delete(targetDirectory); 43 | } catch (IOException e) { 44 | e.printStackTrace(); 45 | } 46 | }; 47 | Runtime.getRuntime().addShutdownHook(new Thread(removeTarget)); 48 | } 49 | 50 | /** Gets a system property, enforcing that the value string is alphanumeric. 51 | * @param property the name of the property to get. 52 | * @return the value of the property consisting of its alphanumeric parts 53 | * and the non-alphanumeric parts being replaced with an 54 | * underscore character ('_'). 55 | * @throws NullPointerException if property is null or not set. 56 | * */ 57 | static String getSystemPropertyAlnum(String property) { 58 | Objects.requireNonNull(property); 59 | 60 | final String value = System.getProperty(property); 61 | Objects.requireNonNull(value, "System property " + property + " is null"); 62 | StringBuilder result = new StringBuilder(value.length()); 63 | for (int i = 0; i < value.length(); i++) { 64 | final char c = value.charAt(i); 65 | if (Character.isLetterOrDigit(c)) { 66 | result.append(c); 67 | } else { 68 | result.append('_'); 69 | } 70 | } 71 | return result.toString(); 72 | } 73 | 74 | private static String getOsName() { 75 | return getSystemPropertyAlnum("os.name"); 76 | } 77 | 78 | 79 | private static String getArchName() { 80 | return getSystemPropertyAlnum("os.arch"); 81 | } 82 | 83 | static String toLibraryName(String name) { 84 | return name + "-" + getOsName() + "-" + getArchName() + ".so"; 85 | } 86 | 87 | final List getObjectFiles() { 88 | return Collections.unmodifiableList(libraries); 89 | } 90 | 91 | final void transfer(String... names) throws IOException { 92 | for (String name : names) { 93 | String libName = toLibraryName(name); 94 | Path targetLibraryPath = targetDirectory.resolve(libName); 95 | 96 | try (InputStream inputStream = getClass().getResourceAsStream("/objects/" + libName)) { 97 | if (inputStream != null) { 98 | transferTo(inputStream, targetLibraryPath); 99 | break; 100 | } 101 | } 102 | 103 | try (InputStream inputStream = Files.newInputStream(Paths.get("src/main/resources/objects").resolve(libName))) { 104 | transferTo(inputStream, targetLibraryPath); 105 | break; 106 | } 107 | } 108 | } 109 | 110 | private void transferTo(InputStream inputStream, Path targetFile) throws IOException { 111 | Files.copy(inputStream, targetFile); 112 | Set set = new HashSet<>(); 113 | set.add(PosixFilePermission.OWNER_EXECUTE); 114 | set.add(PosixFilePermission.OWNER_READ); 115 | Files.setPosixFilePermissions(targetFile, set); 116 | libraries.add(targetFile); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /openssl4j/src/main/java/de/sfuhrm/openssl4j/MessageDigest.java: -------------------------------------------------------------------------------- 1 | package de.sfuhrm.openssl4j; 2 | 3 | /** Class definitions for the message digest spis. 4 | * @author Stephan Fuhrmann 5 | * */ 6 | public final class MessageDigest { 7 | 8 | private MessageDigest() { 9 | // no instances allowed 10 | } 11 | 12 | /** MD5 message digest implementation. 13 | * */ 14 | public final static class MD5 extends OpenSSLMessageDigestNative { 15 | 16 | /** Creates a new instance. */ 17 | public MD5() { super("MD5"); } 18 | } 19 | 20 | /** SHA1 message digest implementation. 21 | * */ 22 | public final static class SHA1 extends OpenSSLMessageDigestNative { 23 | /** Creates a new instance. */ 24 | public SHA1() { super("SHA1"); } 25 | } 26 | 27 | /** SHA-224 message digest implementation. 28 | * */ 29 | public final static class SHA_224 extends OpenSSLMessageDigestNative { 30 | /** Creates a new instance. */ 31 | public SHA_224() { super("SHA224"); } 32 | } 33 | 34 | /** SHA-256 message digest implementation. 35 | * */ 36 | public final static class SHA_256 extends OpenSSLMessageDigestNative { 37 | /** Creates a new instance. */ 38 | public SHA_256() { super("SHA256"); } 39 | } 40 | 41 | /** SHA-384 message digest implementation. 42 | * */ 43 | public final static class SHA_384 extends OpenSSLMessageDigestNative { 44 | /** Creates a new instance. */ 45 | public SHA_384() { super("SHA384"); } 46 | } 47 | 48 | /** SHA-512 message digest implementation. 49 | * */ 50 | public final static class SHA_512 extends OpenSSLMessageDigestNative { 51 | /** Creates a new instance. */ 52 | public SHA_512() { super("SHA512"); } 53 | } 54 | 55 | /** SHA-512/224 message digest implementation. 56 | * */ 57 | public final static class SHA_512_224 extends OpenSSLMessageDigestNative { 58 | /** Creates a new instance. */ 59 | public SHA_512_224() { super("SHA512-224"); } 60 | } 61 | 62 | /** SHA-512/256 message digest implementation. 63 | * */ 64 | public final static class SHA_512_256 extends OpenSSLMessageDigestNative { 65 | /** Creates a new instance. */ 66 | public SHA_512_256() { super("SHA512-256"); } 67 | } 68 | 69 | /** SHA3-224 message digest implementation. 70 | * */ 71 | public final static class SHA3_224 extends OpenSSLMessageDigestNative { 72 | /** Creates a new instance. */ 73 | public SHA3_224() { super("SHA3-224"); } 74 | } 75 | 76 | /** SHA3-256 message digest implementation. 77 | * */ 78 | public final static class SHA3_256 extends OpenSSLMessageDigestNative { 79 | /** Creates a new instance. */ 80 | public SHA3_256() { super("SHA3-256"); } 81 | } 82 | 83 | /** SHA3-384 message digest implementation. 84 | * */ 85 | public final static class SHA3_384 extends OpenSSLMessageDigestNative { 86 | /** Creates a new instance. */ 87 | public SHA3_384() { super("SHA3-384"); } 88 | } 89 | 90 | /** SHA3-512 message digest implementation. 91 | * */ 92 | public final static class SHA3_512 extends OpenSSLMessageDigestNative { 93 | /** Creates a new instance. */ 94 | public SHA3_512() { super("SHA3-512"); } 95 | } 96 | 97 | /** BLAKE2b512 message digest implementation. 98 | * */ 99 | public final static class BLAKE2b512 extends OpenSSLMessageDigestNative { 100 | /** Creates a new instance. */ 101 | public BLAKE2b512() { super("BLAKE2b512"); } 102 | } 103 | 104 | /** BLAKE2s256 message digest implementation. 105 | * */ 106 | public final static class BLAKE2s256 extends OpenSSLMessageDigestNative { 107 | /** Creates a new instance. */ 108 | public BLAKE2s256() { super("BLAKE2s256"); } 109 | } 110 | 111 | /** MD4 message digest implementation. 112 | * */ 113 | public final static class MD4 extends OpenSSLMessageDigestNative { 114 | /** Creates a new instance. */ 115 | public MD4() { super("MD4"); } 116 | } 117 | 118 | /** RIPEMD160 message digest implementation. 119 | * */ 120 | public final static class RIPEMD160 extends OpenSSLMessageDigestNative { 121 | /** Creates a new instance. */ 122 | public RIPEMD160() { super("RIPEMD160"); } 123 | } 124 | 125 | /** SM3 message digest implementation. 126 | * */ 127 | public final static class SM3 extends OpenSSLMessageDigestNative { 128 | /** Creates a new instance. */ 129 | public SM3() { super("SM3"); } 130 | } 131 | 132 | /** Whirlpool message digest implementation. 133 | * */ 134 | public final static class Whirlpool extends OpenSSLMessageDigestNative { 135 | /** Creates a new instance. */ 136 | public Whirlpool() { super("whirlpool"); } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /openssl4j/src/test/java/de/sfuhrm/openssl4j/SpeedTest.java: -------------------------------------------------------------------------------- 1 | package de.sfuhrm.openssl4j; 2 | 3 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 4 | import org.junit.jupiter.api.Disabled; 5 | import org.junit.jupiter.params.ParameterizedTest; 6 | import org.junit.jupiter.params.provider.Arguments; 7 | import org.junit.jupiter.params.provider.MethodSource; 8 | 9 | import java.io.IOException; 10 | import java.nio.ByteBuffer; 11 | import java.security.MessageDigest; 12 | import java.security.NoSuchAlgorithmException; 13 | import java.security.Provider; 14 | import java.security.Security; 15 | import java.util.ArrayList; 16 | import java.util.Arrays; 17 | import java.util.Formatter; 18 | import java.util.HashMap; 19 | import java.util.List; 20 | import java.util.Locale; 21 | import java.util.Map; 22 | import java.util.stream.Stream; 23 | 24 | /** 25 | * Benchmark testing the speed of multiple algorithm implementations. 26 | * @author Stephan Fuhrmann 27 | */ 28 | @Disabled 29 | public class SpeedTest { 30 | 31 | static final int TIMES = 100; 32 | 33 | private static Stream provideTestArguments() throws NoSuchAlgorithmException, IOException { 34 | List messageDigestNames = Arrays.asList("MD5", "SHA1", "SHA-224", "SHA-256", "SHA-384", "SHA-512", "SHA-512/224", "SHA-512/256", "SHA3-224", "SHA3-256", "SHA3-384", "SHA3-512"); 35 | List bufferSizes = Arrays.asList(1000, 100000, 1000000); 36 | List result = new ArrayList<>(); 37 | Map providerMap = new HashMap<>(); 38 | providerMap.put("OpenSSL", new OpenSSL4JProvider()); 39 | providerMap.put("Sun", Security.getProvider("SUN")); 40 | providerMap.put("BC", new BouncyCastleProvider()); 41 | 42 | for (Map.Entry providerEntry : providerMap.entrySet()) { 43 | for (String messageDigestName : messageDigestNames) { 44 | for (Integer bufferSize : bufferSizes) { 45 | String name = providerEntry.getKey(); 46 | result.add(Arguments.of( 47 | name, 48 | messageDigestName, 49 | MessageDigest.getInstance(messageDigestName, providerEntry.getValue()), 50 | messageDigestName, 51 | bufferSize)); 52 | } 53 | } 54 | } 55 | 56 | return result.stream(); 57 | } 58 | 59 | @ParameterizedTest 60 | @MethodSource("provideTestArguments") 61 | public void updateWithByte(String provider, String messageDigest, MessageDigest md, String messageDigestName, Integer bufferSize) { 62 | benchmark(provider, messageDigest, "SingleByte", TIMES, bufferSize, () -> { 63 | for (int i = 0; i < bufferSize; i++) { 64 | md.update((byte) 0); 65 | } 66 | }); 67 | } 68 | 69 | @ParameterizedTest 70 | @MethodSource("provideTestArguments") 71 | public void updateWithArray(String provider, String messageDigest, MessageDigest md, String messageDigestName, Integer bufferSize) { 72 | byte[] data = new byte[bufferSize]; 73 | benchmark(provider, messageDigest, "ByteArray", TIMES, bufferSize, () -> md.update(data)); 74 | } 75 | 76 | @ParameterizedTest 77 | @MethodSource("provideTestArguments") 78 | public void updateWithHeapBB(String provider, String messageDigest, MessageDigest md, String messageDigestName, Integer bufferSize) { 79 | ByteBuffer byteBuffer = ByteBuffer.allocate(bufferSize); 80 | byteBuffer.limit(byteBuffer.capacity()); 81 | benchmark(provider, messageDigest, "HeapBB", TIMES, bufferSize, () -> { 82 | md.update(byteBuffer); 83 | byteBuffer.flip(); 84 | }); 85 | } 86 | 87 | @ParameterizedTest 88 | @MethodSource("provideTestArguments") 89 | public void updateWithDirectBB(String provider, String messageDigest, MessageDigest md, String messageDigestName, Integer bufferSize) { 90 | ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bufferSize); 91 | byteBuffer.limit(byteBuffer.capacity()); 92 | benchmark(provider, messageDigest, "DirectBB", TIMES, bufferSize, () -> { 93 | md.update(byteBuffer); 94 | byteBuffer.flip(); 95 | }); 96 | } 97 | 98 | static boolean first = true; 99 | static void benchmark(String provider, String messageDigest, String testName, int times, int length, Runnable r) { 100 | long start = System.currentTimeMillis(); 101 | for (int i = 0; i < times; i++) { 102 | r.run(); 103 | } 104 | long end = System.currentTimeMillis(); 105 | long millis = end - start; 106 | 107 | double totalData = times * length; 108 | double seconds = millis / 1000.; 109 | 110 | Formatter formatter = new Formatter(System.out, Locale.ENGLISH); 111 | 112 | if (first) { 113 | formatter.format("Provider;MD;Test;Times;Length;Seconds;Data;SpeedMBPS%n"); 114 | first = false; 115 | } 116 | 117 | formatter.format("%s;%s;%s;%d;%d;%g;%g;%g%n", 118 | provider, 119 | messageDigest, 120 | testName, 121 | times, 122 | length, 123 | seconds, 124 | totalData, 125 | (totalData / (1024. * 1024.)) / seconds); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSL4JProvider.java: -------------------------------------------------------------------------------- 1 | package de.sfuhrm.openssl4j; 2 | 3 | import java.io.IOException; 4 | import java.security.Provider; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.Set; 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | /** 12 | * JCA provider directing all calls to the system native OpenSSL library. 13 | * @author Stephan Fuhrmann 14 | */ 15 | public final class OpenSSL4JProvider extends Provider { 16 | 17 | /** The provider name as passed to JCA. */ 18 | public final static String PROVIDER_NAME = "OpenSSL4J"; 19 | 20 | private static Set openSslMessageDigestAlgorithms; 21 | 22 | /** Constructor for the JCA Provider for OpenSSL JNI. 23 | * @throws IllegalStateException if the native object file can't be loaded and the 24 | * class can't be used. 25 | * */ 26 | public OpenSSL4JProvider() { 27 | super(PROVIDER_NAME, 28 | getLibraryVersion(), 29 | "OpenSSL4J provider v" 30 | + PropertyAccessor.get("version", "unknown") + ", implementing " 31 | + "multiple message digest algorithms."); 32 | 33 | try { 34 | NativeLoader.loadAll(); 35 | if (openSslMessageDigestAlgorithms == null) { 36 | openSslMessageDigestAlgorithms = OpenSSLMessageDigestNative.getMessageDigestList(); 37 | } 38 | 39 | Map names = getNames(openSslMessageDigestAlgorithms); 40 | putAll(names); 41 | } catch (IOException e) { 42 | throw new IllegalStateException("Could not initialize", e); 43 | } 44 | } 45 | 46 | private static double getLibraryVersion() { 47 | double result = 0.0; 48 | String stringVersion = PropertyAccessor.get("version", "0.0.0"); 49 | Pattern versionPattern = Pattern.compile("(\\d+\\.\\d+).*"); 50 | Matcher matcher = versionPattern.matcher(stringVersion); 51 | if (matcher.matches()) { 52 | result = Double.parseDouble(matcher.group(1)); 53 | } 54 | return result; 55 | } 56 | 57 | /** Gets the names and the aliases of all message digest 58 | * algorithms. 59 | * @return a map mapping from algorithm name / alias to algorithm class. 60 | * */ 61 | private static Map getNames(Set availableOpenSslAlgorithmNames) { 62 | Map result = getOpenSSLHashnames(availableOpenSslAlgorithmNames); 63 | result.putAll(createAliases(result)); 64 | return result; 65 | } 66 | 67 | /** Creates some aliases for an input map. 68 | * @param map a map with keys being algorithm names of the form "MessageDigest.MD5" 69 | * and the keys being java class names. 70 | * @return a map mapping from algorithm name / alias to algorithm class. 71 | * */ 72 | private static Map createAliases(Map map) { 73 | Map aliases = new HashMap<>(); 74 | Pattern pattern = Pattern.compile("([^0-9]*)-([0-9]+)"); 75 | 76 | for (Map.Entry entry : map.entrySet()) { 77 | Matcher matcher = pattern.matcher(entry.getKey()); 78 | if (matcher.matches()) { 79 | 80 | // adds for MessageDigest.SHA512 an alias like MessageDigest.SHA-512 81 | aliases.put( 82 | matcher.group(1) + matcher.group(2), 83 | entry.getValue()); 84 | } 85 | } 86 | aliases.put("MessageDigest.SHA", map.get("MessageDigest.SHA1")); 87 | return aliases; 88 | } 89 | 90 | /** Name pairs mapping from SSL to Java. 91 | * First one is SSL name, second one is Java name. 92 | * */ 93 | private static final String[] SSL_TO_JAVA_NAMES = { 94 | "MD5", "MD5", 95 | "SHA1", "SHA1", 96 | "SHA224", "SHA-224", 97 | "SHA256", "SHA-256", 98 | "SHA384", "SHA-384", 99 | "SHA512", "SHA-512", 100 | "SHA512-224", "SHA-512/224", 101 | "SHA512-256", "SHA-512/256", 102 | "SHA3-224", "SHA3-224", 103 | "SHA3-256", "SHA3-256", 104 | "SHA3-384", "SHA3-384", 105 | "SHA3-512", "SHA3-512", 106 | "BLAKE2b512", "BLAKE2b512", 107 | "BLAKE2s256", "BLAKE2s256", 108 | "MD4", "MD4", 109 | "RIPEMD160", "RIPEMD160", 110 | "SM3", "SM3", 111 | "whirlpool", "Whirlpool" 112 | }; 113 | 114 | /** Fills a map with the names of all algorithms in 115 | * OpenSSL-JNA. 116 | * @return mapping from algorithm name to class name. 117 | * */ 118 | private static Map getOpenSSLHashnames(Set availableOpenSslAlgos) { 119 | Map map = new HashMap<>(); 120 | 121 | for (int i = 0; i < SSL_TO_JAVA_NAMES.length; i+= 2) { 122 | String sslName = SSL_TO_JAVA_NAMES[i]; 123 | String javaName = SSL_TO_JAVA_NAMES[i + 1]; 124 | 125 | // only if OpenSSL has the algorithm available, add it 126 | if (availableOpenSslAlgos.contains(sslName)) { 127 | String javaClass = MessageDigest.class.getName() + "$" + 128 | (javaName.replaceAll("-", "_").replaceAll("/", "_")); 129 | map.put("MessageDigest." + javaName, javaClass); 130 | } 131 | } 132 | 133 | return map; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | de.sfuhrm 5 | openssl4j-parent 6 | 0.5.1-SNAPSHOT 7 | OpenSSL4J Parent 8 | OpenSSL4J Parent POM 9 | https://github.com/sfuhrm/openssl4j 10 | 11 | git@github.com:sfuhrm/openssl4j 12 | scm:git:git@github.com:sfuhrm/openssl4j.git 13 | HEAD 14 | 15 | 16 | 17 | Apache License, Version 2.0 18 | https://www.apache.org/licenses/LICENSE-2.0.txt 19 | repo 20 | 21 | 22 | 23 | 24 | github 25 | GitHub Packages 26 | https://maven.pkg.github.com/sfuhrm/openssl4j 27 | 28 | 29 | 30 | 31 | ossrh 32 | https://oss.sonatype.org/content/repositories/snapshots 33 | 34 | 35 | 36 | 37 | stephan 38 | Stephan Fuhrmann 39 | s@sfuhrm.de 40 | 41 | 42 | pom 43 | 44 | UTF-8 45 | 8 46 | 8 47 | 48 | 49 | 50 | snapshot 51 | 52 | 53 | sonatype 54 | https://oss.sonatype.org/content/repositories/snapshots 55 | 56 | 57 | 58 | 59 | release 60 | 61 | 62 | sonatype 63 | https://oss.sonatype.org/content/repositories/snapshots 64 | 65 | 66 | 67 | 68 | 69 | org.sonatype.plugins 70 | nexus-staging-maven-plugin 71 | 1.6.13 72 | true 73 | 74 | ossrh 75 | https://oss.sonatype.org/ 76 | true 77 | 78 | 79 | 80 | org.apache.maven.plugins 81 | maven-gpg-plugin 82 | 3.1.0 83 | 84 | 0AC5A45E91FA93DA25380017B0D87B063EAD41F1 85 | 86 | 87 | 88 | sign-artifacts 89 | verify 90 | 91 | sign 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | org.apache.maven.plugins 104 | maven-jar-plugin 105 | 3.3.0 106 | 107 | 108 | 109 | true 110 | true 111 | 112 | 113 | ${project.version} 114 | ${basedir} 115 | 116 | 117 | 118 | 119 | 120 | org.apache.maven.plugins 121 | maven-source-plugin 122 | 3.3.0 123 | 124 | 125 | attach-sources 126 | 127 | jar 128 | 129 | 130 | 131 | 132 | 133 | org.apache.maven.plugins 134 | maven-javadoc-plugin 135 | 3.6.3 136 | 137 | 138 | attach-javadocs 139 | 140 | jar 141 | 142 | 143 | 144 | 145 | 9 146 | 147 | 148 | 149 | maven-surefire-plugin 150 | 3.0.0-M4 151 | 152 | 153 | org.junit.jupiter 154 | junit-jupiter-engine 155 | 5.10.1 156 | 157 | 158 | 159 | 160 | org.apache.maven.plugins 161 | maven-release-plugin 162 | 3.0.1 163 | 164 | true 165 | release 166 | 167 | 168 | 169 | 170 | 171 | 172 | org.junit.jupiter 173 | junit-jupiter-api 174 | 5.10.1 175 | test 176 | 177 | 178 | org.junit.jupiter 179 | junit-jupiter-engine 180 | 5.10.1 181 | test 182 | 183 | 184 | org.junit.jupiter 185 | junit-jupiter-params 186 | 5.10.1 187 | test 188 | 189 | 190 | 191 | openssl4j 192 | 193 | 194 | -------------------------------------------------------------------------------- /openssl4j/src/main/java/de/sfuhrm/openssl4j/OpenSSLMessageDigestNative.java: -------------------------------------------------------------------------------- 1 | package de.sfuhrm.openssl4j; 2 | 3 | import java.io.IOException; 4 | import java.nio.ByteBuffer; 5 | import java.security.MessageDigestSpi; 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | import java.util.HashSet; 9 | import java.util.List; 10 | import java.util.Objects; 11 | import java.util.Set; 12 | 13 | /** 14 | * An interface to OpenSSL message digest functions. 15 | * @author Stephan Fuhrmann 16 | */ 17 | class OpenSSLMessageDigestNative extends MessageDigestSpi { 18 | 19 | /** Return the digest length in bytes. 20 | * @return the digest length in bytes. 21 | * */ 22 | private static native int digestLength(ByteBuffer context); 23 | 24 | /** Removes a context allocated with {@linkplain #nativeContext()}. 25 | * @param context the context to free. 26 | * */ 27 | private static native void removeContext(ByteBuffer context); 28 | 29 | /** Get the list of MessageDigest algorithms supported by OpenSSL. 30 | * @return an array of supported message digest algorithms from the OpenSSL library. 31 | * */ 32 | private native static String[] listMessageDigests(); 33 | 34 | /** Returns the context size in bytes. This is used to allocate the {@link #context direct ByteBuffer}. 35 | * @return a ByteBuffer containing the native message digest context. 36 | * */ 37 | private final native ByteBuffer nativeContext(); 38 | 39 | /** Initialize the context. 40 | * @param context the context as allocated in {@link #context}. 41 | * @param algorithmName the OpenSSL algorithm name as returned by {@linkplain #listMessageDigests()}. 42 | * */ 43 | private final native void nativeInit(ByteBuffer context, String algorithmName); 44 | 45 | /** Update the context with a single byte. 46 | * @param context the context as allocated in {@link #context}. 47 | * @param byteData the byte to update the context with. 48 | * */ 49 | private final native void nativeUpdateWithByte(ByteBuffer context, byte byteData); 50 | 51 | /** Update the context with an array. 52 | * @param context the context as allocated in {@link #context}. 53 | * @param byteArray the array to update the context with. 54 | * @param offset the start offset of the array data to update the context with. 55 | * @param length the number of bytes to update the context with. 56 | * */ 57 | private final native void nativeUpdateWithByteArray(ByteBuffer context, byte[] byteArray, int offset, int length); 58 | 59 | /** Update the context with a direct byte buffer. 60 | * @param context the context as allocated in {@link #context}. 61 | * @param data the byte buffer to update the context with. 62 | * @param offset the start offset of the buffer data to update the context with. 63 | * @param length the number of bytes to update the context with. 64 | * */ 65 | private final native void nativeUpdateWithByteBuffer(ByteBuffer context, ByteBuffer data, int offset, int length); 66 | 67 | /** Do the final digest calculation and return it. 68 | * @param context the context as allocated in {@link #context}. 69 | * @param digest the target array to write the digest data to. 70 | * */ 71 | private final native void nativeFinal(ByteBuffer context, byte[] digest); 72 | 73 | /** A native message digest context where the state of the current calculation is stored. 74 | * Allocated with {@linkplain #nativeContext()}, freed by the 75 | * {@linkplain PhantomReferenceCleanup} with {@linkplain #free(ByteBuffer)}. 76 | * */ 77 | private final ByteBuffer context; 78 | 79 | /** The OpenSSL algorithm name as returned by {@linkplain #listMessageDigests()}. */ 80 | private final String algorithmName; 81 | 82 | /** The digest length as calculated by the engine. */ 83 | private final int digestLength; 84 | 85 | OpenSSLMessageDigestNative(String openSslName) { 86 | try { 87 | NativeLoader.loadAll(); 88 | algorithmName = Objects.requireNonNull(openSslName); 89 | context = nativeContext(); 90 | PhantomReferenceCleanup.enqueueForCleanup(this, OpenSSLMessageDigestNative::free, context); 91 | engineReset(); 92 | digestLength = digestLength(context); 93 | } 94 | catch (IOException e) { 95 | throw new IllegalStateException(e); 96 | } 97 | } 98 | 99 | /** Free the native context that came from {@linkplain #nativeContext()}. 100 | * @param context the context allocated with {@linkplain #nativeContext()}. 101 | * */ 102 | protected static void free(ByteBuffer context) { 103 | Objects.requireNonNull(context); 104 | if (! context.isDirect()) { 105 | throw new IllegalStateException("Illegal buffer passed in"); 106 | } 107 | removeContext(context); 108 | } 109 | 110 | @Override 111 | protected final int engineGetDigestLength() { 112 | return digestLength; 113 | } 114 | 115 | /** Get the list of digest algorithms supported by the OpenSSL library. 116 | * @return a Set of supported message digest algorithms. 117 | * */ 118 | protected static Set getMessageDigestList() { 119 | String[] messageDigestAlgorithms = listMessageDigests(); 120 | Set result = new HashSet<>(Arrays.asList(messageDigestAlgorithms)); 121 | return result; 122 | } 123 | 124 | @Override 125 | protected final void engineUpdate(final ByteBuffer input) { 126 | if (!input.hasRemaining()) { 127 | return; 128 | } 129 | int remaining = input.remaining(); 130 | int offset = input.position(); 131 | if (input.isDirect()) { 132 | nativeUpdateWithByteBuffer(context, input, offset, remaining); 133 | input.position(input.position() + remaining); 134 | } else if (input.hasArray()){ 135 | // buffer is heap based and has an array 136 | byte[] array = input.array(); 137 | nativeUpdateWithByteArray(context, array, offset, remaining); 138 | input.position(offset + remaining); 139 | } else { 140 | // neither direct nor array (read-only?) 141 | byte[] array = new byte[remaining]; 142 | input.get(array); 143 | nativeUpdateWithByteArray(context, array, 0, array.length); 144 | } 145 | } 146 | 147 | @Override 148 | protected final void engineUpdate(final byte inputByte) { 149 | nativeUpdateWithByte(context, inputByte); 150 | } 151 | 152 | @Override 153 | protected final void engineUpdate(final byte[] input, final int offset, final int len) { 154 | nativeUpdateWithByteArray(context, input, offset, len); 155 | } 156 | 157 | @Override 158 | protected final byte[] engineDigest() { 159 | byte[] result = new byte[digestLength]; 160 | nativeFinal(context, result); 161 | engineReset(); 162 | return result; 163 | } 164 | 165 | @Override 166 | protected final void engineReset() { 167 | nativeInit(context, algorithmName); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | OpenSSL4J JNI Java Library 2 | =================== 3 | 4 | [![Single-Platform Build](https://github.com/sfuhrm/openssl4j/actions/workflows/build-singleplatform.yml/badge.svg)](https://github.com/sfuhrm/openssl4j/actions/workflows/build-singleplatform.yml) 5 | [![Java Build](https://github.com/sfuhrm/openssl4j/actions/workflows/build-java.yml/badge.svg)](https://github.com/sfuhrm/openssl4j/actions/workflows/build-java.yml) 6 | [![Crossplatform Build](https://github.com/sfuhrm/openssl4j/actions/workflows/build-crossplatform.yml/badge.svg)](https://github.com/sfuhrm/openssl4j/actions/workflows/build-crossplatform.yml) 7 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/de.sfuhrm/openssl4j/badge.svg)](https://maven-badges.herokuapp.com/maven-central/de.sfuhrm/openssl4j) 8 | [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 9 | 10 | OpenSSL4J is a Java bridge to the native OpenSSL library. 11 | On the Java side it's offering the 12 | conventional [MessageDigest](https://docs.oracle.com/javase/8/docs/api/java/security/MessageDigest.html) class. In the background the calls 13 | will be translated to the native OpenSSL library with all its 14 | [optimizations](https://www.openssl.org/docs/faq-4-build.txt): 15 | 16 | > On x86, the assembly code uses the CPUID instruction (see the 17 | > OPENSSL_ia32cap.pod manpage) to determine if various instructions (AES, 18 | > SSE, MMX, etc) are available and will use them if so. For other processors, 19 | > similar tests are performed if at all possible. 20 | 21 | ## Features 22 | 23 | * Performance: The main feature of OpenSSL4J is performance: The MD5-implementation of OpenSSL4J is 24 | typically 67% to 102% faster than the pure Java version from SUN. 25 | * Functionality: There are some algorithms available in OpenSSL4J that are not available in the normal SUN crypto provider. 26 | 27 | ## Performance 28 | 29 | The following picture shows a performance comparison of 30 | 31 | * BouncyCastle crypto provider (version 1.70) 32 | * Adoptium JDK SUN crypto provider (JDK 17.0.6) 33 | * OpenSSL4j (version 0.3.0) 34 | 35 | Each bar shows different throughputs in megabytes per second. 36 | The per-bar throughputs contain multiple different test scenarios 37 | regarding blocks sizes and data structures used for 38 | data passing (byte, array, direct ByteBuffer, heap ByteBuffer). 39 | The median of the tests is presented by a dark-blue horizontal line 40 | within the bar. The 25% and 75% quantile make up the 41 | area of the bars. 42 | 43 | ![bc-sun-ossl-performance.png](./images/bc-sun-ossl-performance.png) 44 | 45 | The benchmark was conducted on a i7-3840QM CPU. 46 | 47 | ## Building OpenSSL4J for your platform 48 | 49 | For building the application you need 50 | 51 | * JDK 8+, 52 | * Apache Maven, 53 | * GNU Make, 54 | * GNU GCC, 55 | * OpenSSL development headers 56 | 57 | To build the C library for your current platform, wrap it into a maven artifact (openssl4j-objects), build the java parts (openssl4j), execute: 58 | 59 | ```bash 60 | $ build.sh 61 | ... 62 | [INFO] Reactor Summary for OpenSSL4J Parent 0.2.1-SNAPSHOT: 63 | [INFO] 64 | [INFO] OpenSSL4J Parent ................................... SUCCESS [ 0.953 s] 65 | [INFO] OpenSSL4J JNI ...................................... SUCCESS [ 5.859 s] 66 | [INFO] ------------------------------------------------------------------------ 67 | [INFO] BUILD SUCCESS 68 | [INFO] ------------------------------------------------------------------------ 69 | [INFO] Total time: 6.912 s 70 | [INFO] Finished at: 2023-05-28T20:38:43+02:00 71 | [INFO] ------------------------------------------------------------------------ 72 | ``` 73 | 74 | ## Building OpenSSL4J for cross-platform 75 | 76 | The current cross-platform build is driven by github actions, using QEMU 77 | to build different platform shared object library. 78 | The github actions are visible to everyone. 79 | For the cross-platform build to work with your fork, there 80 | are some project secrets needed to be set in your 81 | Github fork settings: 82 | 83 | * DOCKERHUB_USERNAME: Dockerhub username for getting the parent of the build image. 84 | * DOCKERHUB_TOKEN: Dockerhub secret token. 85 | * GH_USER: Github username for storing artifacts. 86 | * GH_PASSWORD: Github password for storing artifacts. 87 | * SONATYPE_USER: (optional) sonatype username for pushing snapshots. 88 | * SONATYPE_PASSWORD: (optional) sonatype password for pushing snapshots. 89 | 90 | (Date of last update: 2023-05-28) 91 | 92 | ## Restrictions 93 | 94 | * MessageDigest restriction: The current milestone only contains MessageDigest algorithms. 95 | * Restricted platforms: The code uses dynamic linking to an object library on the machine. 96 | Native object code within the JAR file is used for binding the Java code to the native code. 97 | There is a restricted amount of platforms supported by the Github Actions 98 | builder (see below). 99 | 100 | ## Usage 101 | 102 | ### Dynamic security provider configuration 103 | 104 | The following example show how to create a MD5 message digest instance with the 105 | dynamically chosen security Provider: 106 | 107 | --------------------------------------- 108 | 109 | ```java 110 | import de.sfuhrm.openssl4j.OpenSSL4JProvider; 111 | 112 | ... 113 | 114 | MessageDigest messageDigest = MessageDigest.getInstance("MD5", new OpenSSL4JProvider()); 115 | messageDigest.update("hello world!".getBytes(Charset.forName("ASCII"))); 116 | byte[] digest = messageDigest.digest(); 117 | ``` 118 | 119 | --------------------------------------- 120 | 121 | ### Installing it in the JDK 122 | 123 | You can also install the provider in your JDK installation. Open the `java.security` file in an editor: 124 | 125 | * Linux, or macOS: `/conf/security/java.security` 126 | * Windows: `\conf\security\java.security` 127 | 128 | To be used effectively, insert it in front of the SUN provider. If this is how the original file looks: 129 | 130 | --------------------------------------- 131 | 132 | ``` 133 | security.provider.1=SUN 134 | security.provider.2=SunRsaSign 135 | security.provider.3=SunEC 136 | security.provider.4=SunJSSE 137 | security.provider.5=SunJCE 138 | security.provider.6=SunJGSS 139 | security.provider.7=SunSASL 140 | security.provider.8=XMLDSig 141 | security.provider.9=SunPCSC 142 | ... 143 | ``` 144 | 145 | --------------------------------------- 146 | 147 | then the new file could look like this after inserting and renumbering the entries: 148 | 149 | --------------------------------------- 150 | 151 | ``` 152 | security.provider.1=OpenSSL4J 153 | security.provider.2=SUN 154 | security.provider.3=SunRsaSign 155 | security.provider.4=SunEC 156 | security.provider.5=SunJSSE 157 | security.provider.6=SunJCE 158 | security.provider.7=SunJGSS 159 | security.provider.8=SunSASL 160 | security.provider.9=XMLDSig 161 | security.provider.10=SunPCSC 162 | ... 163 | ``` 164 | 165 | --------------------------------------- 166 | 167 | ## Including it with Maven 168 | 169 | The recommended way of including the library into your project is using maven: 170 | 171 | --------------------------------------- 172 | 173 | ```xml 174 | 175 | de.sfuhrm 176 | openssl4j 177 | 0.5.0 178 | 179 | ``` 180 | 181 | --------------------------------------- 182 | 183 | ## Native platforms supported 184 | 185 | There are the following native implementations available inside the JAR file: 186 | 187 | * Linux-aarch64 188 | * Linux-amd64 189 | * Linux-arm 190 | * Linux-ppc64le 191 | * Linux-s390x 192 | 193 | ## Version notice 194 | 195 | Please note that the current version is experimental. 196 | 197 | ## Versions 198 | 199 | The version numbers used by `openssl4j` itself comply to the 200 | [semantic versioning](https://semver.org/) schema. 201 | Especially major version changes come with breaking API 202 | changes. 203 | 204 | The temporary internal `openssl4j-objects` artifact is using 205 | date-derived versions, but it is invisible to maven users. 206 | 207 | ## Author 208 | 209 | Written 2020-2023 by Stephan Fuhrmann. You can reach me via email to s (at) sfuhrm.de 210 | 211 | ## License 212 | 213 | The project *is* licensed under [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) after excluding OpenSSL4j release v0.3.0. 214 | 215 | The project *was* licensed under [LGPL 3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html) until including OpenSSL4j release v0.3.0. 216 | -------------------------------------------------------------------------------- /openssl4j-objects/src/main/c/openssl4j_messagedigest.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** OpenSSL MessageDigest to Java Binding Code. 3 | ** 4 | ** See here for an example of the EVP API: 5 | ** https://wiki.openssl.org/index.php/EVP_Message_Digests 6 | ** @author Stephan Fuhrmann 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "openssl4j.h" 15 | 16 | #include "de_sfuhrm_openssl4j_OpenSSLMessageDigestNative.h" 17 | 18 | #if OPENSSL_VERSION_NUMBER >= 0x10101000L 19 | #define OPENSSL_MD_NEW_FUNC EVP_MD_CTX_new 20 | #define OPENSSL_MD_FREE_FUNC EVP_MD_CTX_free 21 | #else 22 | #define OPENSSL_MD_NEW_FUNC EVP_MD_CTX_create 23 | #define OPENSSL_MD_FREE_FUNC EVP_MD_CTX_destroy 24 | #endif 25 | 26 | JNIEXPORT jint JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMessageDigestNative_digestLength 27 | (JNIEnv *env, jclass clazz, jobject context) { 28 | EVP_MD_CTX *mdctx = get_context_from(env, context); 29 | if (mdctx != NULL) { 30 | return EVP_MD_CTX_size(mdctx); 31 | } 32 | return 0; 33 | } 34 | 35 | JNIEXPORT void JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMessageDigestNative_removeContext 36 | (JNIEnv *env, jclass clazz, jobject context) { 37 | EVP_MD_CTX *mdctx = get_context_from(env, context); 38 | if (mdctx != NULL) { 39 | OPENSSL_MD_FREE_FUNC(mdctx); 40 | } 41 | } 42 | 43 | 44 | /* Callback for EVP_MD_do_all that counts the number of MD algorithms. */ 45 | static void EVP_MD_do_all_count_func(const EVP_MD *ciph, const char *from, const char *to, void *x) { 46 | if (ciph != NULL) { 47 | jint *numOfAlgos = (jint*)x; 48 | (*numOfAlgos)++; 49 | } 50 | } 51 | 52 | /* Callback for EVP_MD_do_all that sets the string array elements. 53 | ** @param ciph cipher, can be NULL if this is an alias. 54 | ** @param from the name of the algorithm. 55 | ** @param to NULL if this is not an alias, or the target EVP_MD if this is a an alias. 56 | ** @param x the last param passed to the EVP_MD_do_all() call. 57 | */ 58 | static void EVP_MD_do_all_string_array_set(const EVP_MD *ciph, const char *from, const char *to, void *x) { 59 | struct StringArrayPosition *sap = (struct StringArrayPosition*)x; 60 | if (ciph == NULL) { 61 | // alias 62 | return; 63 | } 64 | const char *evp_name = EVP_MD_name(ciph); 65 | 66 | jstring algoNameString = (*sap->env)->NewStringUTF(sap->env, evp_name); 67 | if (algoNameString == NULL) { 68 | return; 69 | } 70 | 71 | (*sap->env)->SetObjectArrayElement(sap->env, sap->array, sap->index, algoNameString); 72 | sap->index++; 73 | } 74 | 75 | JNIEXPORT jobjectArray JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMessageDigestNative_listMessageDigests 76 | (JNIEnv *env, jclass clazz) { 77 | struct StringArrayPosition sap; 78 | jobjectArray result = NULL; 79 | 80 | sap.index = 0; 81 | sap.length = 0; 82 | sap.env = env; 83 | sap.array = NULL; 84 | 85 | EVP_MD_do_all(EVP_MD_do_all_count_func, &sap.length); 86 | jclass stringClass = (*env)->FindClass(env, "java/lang/String"); 87 | if (stringClass == NULL) { 88 | return NULL; 89 | } 90 | 91 | result = (*env)->NewObjectArray(env, sap.length, stringClass, NULL); 92 | sap.array = result; 93 | 94 | EVP_MD_do_all(EVP_MD_do_all_string_array_set, &sap); 95 | 96 | return result; 97 | } 98 | 99 | JNIEXPORT jobject JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMessageDigestNative_nativeContext 100 | (JNIEnv *env, jobject obj) { 101 | EVP_MD_CTX *mdctx; 102 | 103 | if ((mdctx = OPENSSL_MD_NEW_FUNC()) == NULL) { 104 | throw_error(env, ILLEGAL_STATE_EXCEPTION, "Could not allocate context"); 105 | return NULL; 106 | } 107 | 108 | size_t usableSize = malloc_usable_size(mdctx); 109 | jobject result = (*env)->NewDirectByteBuffer(env, mdctx, usableSize); 110 | if (result == NULL) { 111 | throw_error(env, ILLEGAL_STATE_EXCEPTION, "Could not NewDirectByteBuffer()"); 112 | } 113 | 114 | return result; 115 | } 116 | 117 | JNIEXPORT void JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMessageDigestNative_nativeUpdateWithByte 118 | (JNIEnv *env, jobject obj, jobject context, jbyte byteData) { 119 | EVP_MD_CTX* context_data = get_context_from(env, context); 120 | if (context_data != NULL) { 121 | if (1 != EVP_DigestUpdate(context_data, &byteData, 1)) { 122 | throw_error(env, ILLEGAL_STATE_EXCEPTION, "EVP_DigestUpdate failed"); 123 | } 124 | } 125 | } 126 | 127 | JNIEXPORT void JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMessageDigestNative_nativeUpdateWithByteArray 128 | (JNIEnv *env, jobject obj, jobject context, jbyteArray jarray, jint offset, jint length) { 129 | if (jarray == NULL) { 130 | throw_error(env, NULL_POINTER_EXCEPTION, "array is NULL"); 131 | return; 132 | } 133 | 134 | EVP_MD_CTX* context_data = get_context_from(env, context); 135 | if (context_data != NULL) { 136 | jboolean isCopy = JNI_FALSE; 137 | 138 | /* TODO this copies the whole array, even if length is 1 byte */ 139 | jbyte *carray = (*env)->GetByteArrayElements(env, jarray, &isCopy); 140 | if (carray != NULL) { 141 | if (1 != EVP_DigestUpdate(context_data, carray + offset, length)) { 142 | throw_error(env, ILLEGAL_STATE_EXCEPTION, "EVP_DigestUpdate failed"); 143 | } 144 | /* JNI_ABORT: Don't copy back the array, nothing has changed */ 145 | (*env)->ReleaseByteArrayElements(env, jarray, carray, JNI_ABORT); 146 | } else { 147 | throw_error(env, ILLEGAL_STATE_EXCEPTION, "GetByteArrayElements for array failed"); 148 | } 149 | } 150 | } 151 | 152 | JNIEXPORT void JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMessageDigestNative_nativeUpdateWithByteBuffer 153 | (JNIEnv *env, jobject obj, jobject context, jobject bb, jint offset, jint length) { 154 | if (bb == NULL) { 155 | throw_error(env, NULL_POINTER_EXCEPTION, "ByteBuffer is NULL"); 156 | return; 157 | } 158 | EVP_MD_CTX* context_data = get_context_from(env, context); 159 | if (context_data != NULL) { 160 | jbyte* buffer = (*env)->GetDirectBufferAddress(env, bb); 161 | if (buffer != NULL) { 162 | jbyte* offset_buffer = buffer + offset; 163 | 164 | if (1 != EVP_DigestUpdate(context_data, offset_buffer, length)) { 165 | throw_error(env, ILLEGAL_STATE_EXCEPTION, "EVP_DigestUpdate failed"); 166 | } 167 | } else { 168 | throw_error(env, ILLEGAL_STATE_EXCEPTION, "GetDirectBufferAddress for ByteBuffer failed"); 169 | } 170 | } 171 | } 172 | 173 | JNIEXPORT void JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMessageDigestNative_nativeFinal 174 | (JNIEnv *env, jobject obj, jobject context, jbyteArray jdigest) { 175 | EVP_MD_CTX* context_data = get_context_from(env, context); 176 | if (jdigest == NULL) { 177 | throw_error(env, NULL_POINTER_EXCEPTION, "Digest array is NULL"); 178 | return; 179 | } 180 | if (context_data != NULL) { 181 | jbyte cdigest[EVP_MAX_MD_SIZE]; 182 | unsigned int actualSize; 183 | if (1 != EVP_DigestFinal_ex(context_data, (unsigned char*)cdigest, &actualSize)) { 184 | throw_error(env, ILLEGAL_STATE_EXCEPTION, "EVP_DigestFinal_ex failed"); 185 | } 186 | (*env)->SetByteArrayRegion(env, jdigest, 0, actualSize, cdigest); 187 | } 188 | } 189 | 190 | JNIEXPORT void JNICALL Java_de_sfuhrm_openssl4j_OpenSSLMessageDigestNative_nativeInit 191 | (JNIEnv *env, jobject obj, jobject context, jstring jalgoName) { 192 | if (jalgoName == NULL) { 193 | throw_error(env, NULL_POINTER_EXCEPTION, "Algorithm name is NULL"); 194 | return; 195 | } 196 | EVP_MD_CTX* context_data = get_context_from(env, context); 197 | if (context_data == NULL) { 198 | throw_error(env, ILLEGAL_STATE_EXCEPTION, "EVP_DigestInit_ex failed"); 199 | return; 200 | } 201 | 202 | jsize nameLength = (*env)->GetStringUTFLength(env, jalgoName); 203 | 204 | char javaNameC[256]; 205 | if (nameLength > sizeof(javaNameC)) { 206 | throw_error(env, ILLEGAL_STATE_EXCEPTION, "Algorithm name exceeds the limit"); 207 | return; 208 | } 209 | 210 | jboolean isCopy; 211 | const char * cstr = (*env)->GetStringUTFChars(env, jalgoName, &isCopy); 212 | strncpy(javaNameC, cstr, sizeof(javaNameC)); 213 | (*env)->ReleaseStringUTFChars(env, jalgoName, cstr); 214 | 215 | const EVP_MD *evp_md = EVP_get_digestbyname(javaNameC); 216 | if (evp_md == NULL) { 217 | throw_error(env, ILLEGAL_STATE_EXCEPTION, "Named MessageDigest was not found"); 218 | return; 219 | } 220 | 221 | if (1 != EVP_DigestInit_ex(context_data, evp_md, NULL)) { 222 | throw_error(env, ILLEGAL_STATE_EXCEPTION, "EVP_DigestInit_ex failed"); 223 | return; 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /openssl4j/src/test/java/de/sfuhrm/openssl4j/MessageDigestWithReferenceMDTest.java: -------------------------------------------------------------------------------- 1 | package de.sfuhrm.openssl4j; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.params.ParameterizedTest; 5 | import org.junit.jupiter.params.provider.Arguments; 6 | import org.junit.jupiter.params.provider.MethodSource; 7 | 8 | import java.io.IOException; 9 | import java.nio.ByteBuffer; 10 | import java.nio.charset.Charset; 11 | import java.nio.charset.StandardCharsets; 12 | import java.security.MessageDigest; 13 | import java.security.NoSuchAlgorithmException; 14 | import java.security.Provider; 15 | import java.util.ArrayList; 16 | import java.util.Arrays; 17 | import java.util.List; 18 | import java.util.function.Consumer; 19 | import java.util.stream.Stream; 20 | 21 | import static org.junit.jupiter.api.Assertions.*; 22 | /** 23 | * Test cases that compare the message digest of the 24 | * Sun provider (aka 'reference') with the implementations 25 | * in this context (aka 'test'). 26 | * @author Stephan Fuhrmann 27 | */ 28 | public class MessageDigestWithReferenceMDTest extends BaseTest { 29 | 30 | private static Stream provideTestArguments() throws NoSuchAlgorithmException, IOException { 31 | List messageDigestNames = Arrays.asList("MD5", "SHA1", "SHA-224", "SHA-256", "SHA-384", "SHA-512", "SHA-512/224", "SHA-512/256", "SHA3-224", "SHA3-256", "SHA3-384", "SHA3-512"); 32 | List result = new ArrayList<>(); 33 | Provider openSsl = new OpenSSL4JProvider(); 34 | Provider sun = MessageDigest.getInstance("MD5").getProvider(); 35 | 36 | for (String messageDigestName : messageDigestNames) { 37 | result.add(Arguments.of( 38 | messageDigestName, 39 | MessageDigest.getInstance(messageDigestName, openSsl), 40 | MessageDigest.getInstance(messageDigestName, sun))); 41 | } 42 | 43 | return result.stream(); 44 | } 45 | 46 | @ParameterizedTest 47 | @MethodSource("provideTestArguments") 48 | public void compareGetters(String digestName, MessageDigest testMD, MessageDigest referenceMD) { 49 | assertEquals(referenceMD.getAlgorithm(), testMD.getAlgorithm()); 50 | assertEquals(referenceMD.getDigestLength(), testMD.getDigestLength()); 51 | } 52 | 53 | @ParameterizedTest 54 | @MethodSource("provideTestArguments") 55 | public void digestWithNoData(String digestName, MessageDigest testMD, MessageDigest referenceMD) { 56 | byte[] actualDigest = testMD.digest(); 57 | byte[] expectedDigest = referenceMD.digest(); 58 | 59 | assertEquals(expectedDigest.length, actualDigest.length); 60 | assertEquals(formatter.format(expectedDigest), formatter.format(actualDigest)); 61 | } 62 | 63 | private byte[] franzJagt() { 64 | byte[] data = "Franz jagt im komplett verwahrlosten Taxi quer durch Bayern".getBytes(ascii); 65 | return data; 66 | } 67 | 68 | private void applyTo(Consumer consumer, MessageDigest testMD, MessageDigest referenceMD) { 69 | consumer.accept(testMD); 70 | consumer.accept(referenceMD); 71 | byte[] actualDigest = testMD.digest(); 72 | byte[] expectedDigest = referenceMD.digest(); 73 | assertEquals(formatter.format(expectedDigest), formatter.format(actualDigest)); 74 | } 75 | 76 | @ParameterizedTest 77 | @MethodSource("provideTestArguments") 78 | public void updateWithFullArray(String digestName, MessageDigest testMD, MessageDigest referenceMD) { 79 | applyTo(md -> md.update(franzJagt()), testMD, referenceMD); 80 | } 81 | 82 | @ParameterizedTest 83 | @MethodSource("provideTestArguments") 84 | public void updateWithSingleBytes(String digestName, MessageDigest testMD, MessageDigest referenceMD) { 85 | applyTo(md -> { 86 | for (byte val : franzJagt()) { 87 | md.update(val); 88 | } 89 | }, testMD, referenceMD); 90 | } 91 | 92 | @ParameterizedTest 93 | @MethodSource("provideTestArguments") 94 | public void updateWithHeapByteBuffer(String digestName, MessageDigest testMD, MessageDigest referenceMD) { 95 | final List list = new ArrayList<>(); 96 | applyTo(md -> { 97 | ByteBuffer bb = ByteBuffer.wrap(franzJagt()); 98 | list.add(bb); 99 | md.update(bb); 100 | }, testMD, referenceMD); 101 | ByteBuffer first = list.get(0); 102 | ByteBuffer second = list.get(1); 103 | assertEquals(first.position(), second.position()); 104 | assertEquals(first.limit(), second.limit()); 105 | assertEquals(first.capacity(), second.capacity()); 106 | } 107 | 108 | @ParameterizedTest 109 | @MethodSource("provideTestArguments") 110 | public void updateWithReadOnlyHeapByteBuffer(String digestName, MessageDigest testMD, MessageDigest referenceMD) { 111 | final List list = new ArrayList<>(); 112 | applyTo(md -> { 113 | ByteBuffer bb = ByteBuffer.wrap(franzJagt()); 114 | bb = bb.asReadOnlyBuffer(); 115 | list.add(bb); 116 | md.update(bb); 117 | }, testMD, referenceMD); 118 | ByteBuffer first = list.get(0); 119 | ByteBuffer second = list.get(1); 120 | assertEquals(first.position(), second.position()); 121 | assertEquals(first.limit(), second.limit()); 122 | assertEquals(first.capacity(), second.capacity()); 123 | } 124 | 125 | @ParameterizedTest 126 | @MethodSource("provideTestArguments") 127 | public void updateWithReadOnlyMiddlePositionHeapByteBuffer(String digestName, MessageDigest testMD, MessageDigest referenceMD) { 128 | final List list = new ArrayList<>(); 129 | applyTo(md -> { 130 | ByteBuffer bb = ByteBuffer.wrap(franzJagt()); 131 | bb = bb.asReadOnlyBuffer(); 132 | bb.position(bb.remaining() / 2); 133 | list.add(bb); 134 | md.update(bb); 135 | }, testMD, referenceMD); 136 | ByteBuffer first = list.get(0); 137 | ByteBuffer second = list.get(1); 138 | assertEquals(first.position(), second.position()); 139 | assertEquals(first.limit(), second.limit()); 140 | assertEquals(first.capacity(), second.capacity()); 141 | } 142 | 143 | @ParameterizedTest 144 | @MethodSource("provideTestArguments") 145 | public void updateWithDirectByteBufferNoRemaining(String digestName, MessageDigest testMD, MessageDigest referenceMD) { 146 | final List list = new ArrayList<>(); 147 | applyTo(md -> { 148 | ByteBuffer bb = ByteBuffer.allocateDirect(franzJagt().length); 149 | bb.put(franzJagt()); 150 | list.add(bb); 151 | md.update(bb); 152 | }, testMD, referenceMD); 153 | ByteBuffer first = list.get(0); 154 | ByteBuffer second = list.get(1); 155 | assertEquals(first.position(), second.position()); 156 | assertEquals(first.limit(), second.limit()); 157 | assertEquals(first.capacity(), second.capacity()); 158 | } 159 | 160 | @ParameterizedTest 161 | @MethodSource("provideTestArguments") 162 | public void updateWithDirectByteBuffer(String digestName, MessageDigest testMD, MessageDigest referenceMD) { 163 | final List list = new ArrayList<>(); 164 | applyTo(md -> { 165 | ByteBuffer bb = ByteBuffer.allocateDirect(franzJagt().length); 166 | bb.put(franzJagt()); 167 | bb.flip(); 168 | list.add(bb); 169 | md.update(bb); 170 | }, testMD, referenceMD); 171 | ByteBuffer first = list.get(0); 172 | ByteBuffer second = list.get(1); 173 | assertEquals(first.position(), second.position()); 174 | assertEquals(first.limit(), second.limit()); 175 | assertEquals(first.capacity(), second.capacity()); 176 | } 177 | 178 | @ParameterizedTest 179 | @MethodSource("provideTestArguments") 180 | public void updateWithNonFullDirectByteBuffer(String digestName, MessageDigest testMD, MessageDigest referenceMD) { 181 | final List list = new ArrayList<>(); 182 | applyTo(md -> { 183 | ByteBuffer bb = ByteBuffer.allocateDirect(franzJagt().length * 2); 184 | bb.put(franzJagt()); 185 | bb.flip(); 186 | list.add(bb); 187 | md.update(bb); 188 | }, testMD, referenceMD); 189 | ByteBuffer first = list.get(0); 190 | ByteBuffer second = list.get(1); 191 | assertEquals(first.position(), second.position()); 192 | assertEquals(first.limit(), second.limit()); 193 | assertEquals(first.capacity(), second.capacity()); 194 | } 195 | 196 | @ParameterizedTest 197 | @MethodSource("provideTestArguments") 198 | public void updateWithMiddlePositionDirectByteBuffer(String digestName, MessageDigest testMD, MessageDigest referenceMD) { 199 | final List list = new ArrayList<>(); 200 | applyTo(md -> { 201 | ByteBuffer bb = ByteBuffer.allocateDirect(franzJagt().length); 202 | bb.put(franzJagt()); 203 | bb.flip(); 204 | bb.position(bb.remaining() / 2); 205 | list.add(bb); 206 | md.update(bb); 207 | }, testMD, referenceMD); 208 | ByteBuffer first = list.get(0); 209 | ByteBuffer second = list.get(1); 210 | assertEquals(first.position(), second.position()); 211 | assertEquals(first.limit(), second.limit()); 212 | assertEquals(first.capacity(), second.capacity()); 213 | } 214 | 215 | @ParameterizedTest 216 | @MethodSource("provideTestArguments") 217 | public void updateWithFragmentedArray(String digestName, MessageDigest testMD, MessageDigest referenceMD) { 218 | applyTo(md -> { 219 | byte[] dataInner = franzJagt(); 220 | byte[] data = new byte[dataInner.length * 2]; 221 | int insertOffset = data.length / 4; 222 | System.arraycopy(dataInner, 0, data, insertOffset, dataInner.length); 223 | md.update(data, insertOffset, dataInner.length); 224 | }, testMD, referenceMD); 225 | } 226 | 227 | static byte[] filledArray(int size) { 228 | byte[] data = new byte[size]; 229 | for (int i = 0; i < data.length; i++) { 230 | data[i] = (byte) i; 231 | } 232 | return data; 233 | } 234 | 235 | @ParameterizedTest 236 | @MethodSource("provideTestArguments") 237 | public void updateWithLongArray(String digestName, MessageDigest testMD, MessageDigest referenceMD) { 238 | applyTo(md -> { 239 | byte[] data = filledArray(1024 * 1024); 240 | int rounds = 16; 241 | for (int i=0; i < rounds; i++) { 242 | md.update(data, 0, data.length); 243 | } 244 | }, testMD, referenceMD); 245 | } 246 | 247 | @ParameterizedTest 248 | @MethodSource("provideTestArguments") 249 | public void updateWithLongDirectBB(String digestName, MessageDigest testMD, MessageDigest referenceMD) { 250 | applyTo(md -> { 251 | byte[] data = filledArray(1024 * 1024); 252 | int rounds = 16; 253 | ByteBuffer direct = ByteBuffer.allocateDirect(data.length); 254 | direct.put(data); 255 | direct.flip(); 256 | for (int i=0; i < rounds; i++) { 257 | md.update(direct); 258 | } 259 | }, testMD, referenceMD); 260 | } 261 | 262 | @ParameterizedTest 263 | @MethodSource("provideTestArguments") 264 | public void updateWithDirectBBWalkingPosition(String digestName, MessageDigest testMD, MessageDigest referenceMD) { 265 | int size = 10240; 266 | for (int i = 0; i < size; i++) { 267 | final int position = i; 268 | applyTo(md -> { 269 | byte[] array = filledArray(size); 270 | ByteBuffer direct = ByteBuffer.allocateDirect(array.length); 271 | direct.put(array); 272 | direct.flip(); 273 | direct.position(position); 274 | md.update(direct); 275 | }, testMD, referenceMD); 276 | } 277 | } 278 | 279 | @ParameterizedTest 280 | @MethodSource("provideTestArguments") 281 | public void updateWithHeapBBWalkingPosition(String digestName, MessageDigest testMD, MessageDigest referenceMD) { 282 | int size = 10240; 283 | for (int i = 0; i < size; i++) { 284 | final int position = i; 285 | applyTo(md -> { 286 | byte[] array = filledArray(size); 287 | ByteBuffer direct = ByteBuffer.allocate(array.length); 288 | direct.put(array); 289 | direct.flip(); 290 | direct.position(position); 291 | md.update(direct); 292 | }, testMD, referenceMD); 293 | } 294 | } 295 | 296 | @ParameterizedTest 297 | @MethodSource("provideTestArguments") 298 | public void updateWithReadOnlyBBWalkingPosition(String digestName, MessageDigest testMD, MessageDigest referenceMD) { 299 | int size = 10240; 300 | for (int i = 0; i < size; i++) { 301 | final int position = i; 302 | applyTo(md -> { 303 | byte[] array = filledArray(size); 304 | ByteBuffer direct = ByteBuffer.allocate(array.length); 305 | direct.put(array); 306 | direct.flip(); 307 | direct.position(position); 308 | direct = direct.asReadOnlyBuffer(); 309 | md.update(direct); 310 | }, testMD, referenceMD); 311 | } 312 | } 313 | 314 | @ParameterizedTest 315 | @MethodSource("provideTestArguments") 316 | public void updateWithArrayWalkingPosition(String digestName, MessageDigest testMD, MessageDigest referenceMD) { 317 | int size = 10240; 318 | for (int i = 0; i < size; i++) { 319 | final int position = i; 320 | applyTo(md -> { 321 | byte[] array = filledArray(size); 322 | md.update(array, position, size - position); 323 | }, testMD, referenceMD); 324 | } 325 | } 326 | } 327 | --------------------------------------------------------------------------------