A Java bytecode protection solution that provides JAR encryption and runtime dynamic decryption, making decompilation and code theft more difficult.
Supports Spring and the common startup and class-loading flow of default Spring Boot executable JARs. With application classes under
BOOT-INF/classesand intact dependencies underBOOT-INF/lib, classes are decrypted throughClassFileLoadHookbefore JVM definition without depending on the Spring Boot application ClassLoader implementation.Reduces the risk of exposing decryption logic inherent in conventional Java agent (
-javaagent) and native agent (-agentlib) approaches.
The main workflow and features are implemented. Documentation, JRE signature verification, class execution support, and other areas still need improvement.
- Bytecode Encryption: AES-GCM-256 encryption for class constants and method bytecode.
- Resource Protection: Block-level encryption and dynamic decryption for arbitrary resources inside JARs.
- Secure Launcher: A native Rust launcher keeps decryption logic outside Java code.
- Signature Verification: ED25519 signature validation ensures code integrity.
- Zero-Intrusion Integration: No business-code changes are required.
- Compilable Class Stubs: Preserves API metadata while replacing implementations with default bodies; unnamed
MethodParametersremain unnamed and are not copied into LocalVariableTable entries, whose names cannot be null. - JVM Integration: Launches the application directly through the JVM rather than a Java subprocess.
- Java 8 Bytecode Target: The project itself targets Java 8 bytecode. Compatibility automation exercises native launcher generation, runtime packaging, encrypted Spring Boot startup, and failure paths against JDK majors 8, 11, 17, 21, and 25.
- Java Runtime Packaging: Injects the generated launcher into a selected JDK/JRE and creates a platform archive.
Download the executable fat JAR, the Apache-2.0 LICENSE, its SHA-256 checksum, and two clearly scoped CycloneDX JSON SBOMs from GitHub Releases: java-guard-maven-sbom.json describes the Maven/Java component, while jg-launcher-cargo-sbom.json separately describes the Rust launcher from jg-launcher/Cargo.toml and jg-launcher/Cargo.lock. Neither SBOM alone covers both components. The release JAR already embeds the jg-launcher source, so Maven is not required to run Java Guard. Rust/Cargo and a native build toolchain are still required when generating a native launcher.
When using a release JAR, skip the Maven requirement and go directly to 3. Encrypt a JAR and generate the launcher.
- JDK 8 (source-build baseline); compatibility fixtures cover target/runtime JDK majors 8, 11, 17, 21, and 25
- Maven 3.1+ (when building Java Guard or the compatibility fixtures from source)
- Current stable Rust/Cargo (only when compiling the native launcher with
-l) - A native C build toolchain for the target platform
jg-launcheruses the Rust 2021 edition. The previous Rust 1.41+ requirement is no longer valid; use the current stable Rust toolchain.
git clone --depth 1 https://github.com/kyle-derrick/java-guard.git
cd java-guardFor offline usage, cache the
jg-launcherdependencies in advance. Dependencies are platform-specific.
Download the dependencies in the jg-launcher subproject:
cd jg-launcher
cargo generate-lockfile
cargo vendor ./vendorAdd the Cargo configuration:
mkdir .cargo
# Linux/macOS shell example (not an indication of macOS validation); Windows users can perform the equivalent steps
cat > .cargo/config.toml <<'EOF'
[source.crates-io]
replace-with = 'vendored-sources'
[source.vendored-sources]
directory = 'vendor'
EOF
cd ..mvn clean packageThe output is:
target/java-guard-0.4.3.jar
Generating a native launcher requires cargo and an available Java environment. None of oriJava, ORI_JAVA, or JAVA_HOME is mandatory. Java Guard selects the Java environment to package in this order:
oriJavain the configuration file- The
ORI_JAVAenvironment variable - The
JAVA_HOMEenvironment variable - The
java.homeproperty of the JVM currently running Java Guard
If ORI_JAVA is not set, Java Guard automatically tries JAVA_HOME; if neither is set, it uses the current JVM. oriJava/ORI_JAVA selects the JDK/JRE to package, while Cargo locates Java headers through JAVA_HOME or PATH when compiling the launcher. Setting the correct JAVA_HOME is therefore recommended for launcher builds. The launcher and packaged Java environment must use the same operating system and CPU architecture, but their JDK minor versions do not have to be identical.
# Generate an ED25519 key pair
mkdir key
ssh-keygen -t ed25519 -f key/id_ed25519
# Encrypt the JAR and explicitly enable launcher compilation and Java packaging
java -jar target/java-guard-*.jar \
-c ./config.yml \
-o ./out \
-l \
your-application.jar
# Launcher/runtime packaging only; no input JAR is required
java -jar target/java-guard-*.jar \
-c ./config.yml \
-o ./out \
-l
# Linux/macOS shell launch command (currently validated only on Linux)
./out/bin/jg-launcher -jar out/your-application.jar
# Windows
# .\out\bin\jg-launcher.exe -jar out\your-application.jarWith -l enabled, Java Guard creates out/bin/jg-launcher (.exe on Windows) and a Java runtime archive containing the launcher:
- Windows:
out/jg-<Java-runtime-name>.zip - Linux/macOS:
out/jg-<Java-runtime-name>.tar.gz
-l may be used by itself, without an input JAR, to generate only the launcher and packaged Java environment from the configured key and runtime. With one or more input JARs it processes those JARs and also generates the launcher/package. Without -l, at least one input JAR is required, and Java Guard only processes those JARs.
| Option | Description |
|---|---|
-c, --config <file> |
Configuration file; defaults to ./config.yml |
-m, --mode <mode> |
Processing mode: encrypt, decrypt, or signature; defaults to encrypt |
-o, --output <dir> |
Output directory; falls back to the configuration value, then ./out |
-l, --launcher |
Enable native launcher compilation and Java runtime packaging; may be used without an input JAR for launcher-only packaging |
--skip-deps |
Skip extracting bundled offline Cargo dependencies, if present; normally unnecessary for online builds |
-h, --help |
Print usage information |
# ./config.yml
matches:
- "com/yourcompany/*" # * crosses / and recursively matches every level below the prefix
- "BOOT-INF/classes/com/yourcompany/*"
- "META-INF/resources/*"
# - "*" # Match the full archive and recurse into matched nested JARs
key: your_encryption_key # AES key; may be omitted and generated during encryption
privateKey: key/id_ed25519 # ED25519 private key path
publicKey: key/id_ed25519.pub # ED25519 public key path
output: ./out # Default output directory
oriJava: /path/to/jdk-or-jre # Optional JDK/JRE directory or .zip/.tar.gz/.tgz archive
zipLevel: 6 # Optional output JAR compression level
bufferSize: 1048576 # Optional resource-processing buffer size
printEncryptEntry: true # Optional encrypted-entry loggingoriJava may point to a JDK/JRE directory or a .zip, .tar.gz, or .tgz archive. If omitted, Java Guard uses the environment-selection order described above. Wildcard * is translated to .*, so it crosses /: com/example/* recursively covers subpackages below that prefix. A standalone '*' covers every archive entry and recursively processes matched nested JARs.
Java Guard can distribute protected closed-source Java dependencies such as AI SDKs, model-orchestration libraries, agent/workflow engines, inference clients, and other core business components. An encrypted class retains API metadata such as class names, method signatures, fields, and annotations, so it can normally remain a Maven/Gradle compile-time dependency while the original implementation bytecode is stored in the encrypted payload.
Recommended workflow:
Closed-source dependency JAR
→ Encrypt with fixed AES/ED25519 configuration
→ Developer compiles and packages against the protected dependency
→ Supplier signs the final executable JAR and generates its launcher
→ Run the final JAR with the matching launcher
# 1. The supplier encrypts the closed-source dependency
java -jar java-guard-0.4.3.jar \
-m encrypt \
-c ./supplier-config.yml \
-o ./protected-deps \
proprietary-sdk.jar
# 2. The developer builds final-app.jar using the JAR from protected-deps
# 3. After all packaging is complete, the supplier signs the final JAR
# and generates its matching launcher
java -jar java-guard-0.4.3.jar \
-m signature \
-c ./supplier-config.yml \
-o ./release \
-l \
final-app.jar
# 4. Launch the signed final JAR
./release/bin/jg-launcher -jar release/final-app.jarImportant constraints:
- Multiple protected dependencies in one application should use the same AES key and run through the application-specific launcher generated with that key.
- Plain
java -jar, direct IDE execution, or build-time execution sees only the stub methods' default behavior. The matching launcher is required to load the real implementation. - The common default Spring Boot executable-JAR flow is supported: encrypt the dependency first, let the Boot plugin place it intact under
BOOT-INF/lib, and sign the final outer executable JAR afterward. When encrypting a completed Boot JAR directly, match rules must also include the relevantBOOT-INF/lib/*.jarentry before Java Guard can recurse into that nested dependency. - Shade relocation, minimization, instrumentation, AOT, or other bytecode rewriting may remove the encrypted payload or change class names and is not guaranteed to work. WAR, thin-JAR, exploded-deployment, and Native Image layouts are outside the current default support scope.
- Applying
signatureto the outer JAR must be the final packaging step. Modifying its manifest, nested dependencies, or any other content afterward invalidates the signature. - Encrypted classes are decrypted through a JVM-wide JVMTI hook and normally do not depend on the Boot nested-JAR ClassLoader implementation. Encrypted resources are transparently decrypted only when access passes through
URL.openConnection(), returns ajar:JarURLConnection, and consumesgetInputStream(). DirectJarFile/ZipFileaccess, custom protocols, and other URLConnection implementations require separate validation. - The compatibility fixtures automate default executable-JAR startup for Spring Boot 2.1.9.RELEASE, 2.7.18, 3.3.13, 3.4.13, and 4.1.0 on their matching JDK majors 8, 11, 17, 21, and 25. This is a deliberately selected fixture set, not a claim that every Spring Boot patch or packaging layout is compatible. Run the suite against the exact target Spring Boot/JDK combination before release.
- Do not distribute the AES key, ED25519 private key, supplier configuration, or the generated
jg-launcher-sourcedirectory to developers.
This design raises the cost of static analysis, decompilation, and routine code extraction while validating the final JAR's integrity. It does not provide absolute confidentiality on an untrusted host. A user who fully controls the launcher, JVM, native debugger, and execution machine may still recover runtime plaintext through reverse engineering, memory extraction, or a modified JVM. High-value AI models or algorithms that require a stronger trust boundary should use server-side execution, trusted execution environments, or other access-control mechanisms.
graph TD
A[Original JAR] --> B{Java Guard}
B --> C[Encrypted Bytecode]
B --> D[Encrypted Resources]
C --> E[Secure Launcher]
D --> E
E --> F[JVM ClassFileLoadHook]
E --> G[URL Class Extension]
F --> H[Runtime Decryption]
G --> H
| Feature | Description |
|---|---|
| Constant and method encryption | Encrypts critical data while preserving the class-file structure |
| JAR signature verification | Adds a private-key signature during encryption and verifies it with the public key at startup |
| Native launcher | Rust implementation increases analysis difficulty and supports capabilities such as agent-argument filtering and JAR signature validation |
| Transparent URL extension | Dynamically extends bytecode to support encrypted-resource access |
| JDK/JRE packaging | Injects the application-specific launcher into a Java environment and creates a platform archive |
CI exercises multiple JDK versions and platforms, covering native launcher and runtime packaging, encryption, signature verification, and protected Spring Boot executable-JAR startup. Tests use ephemeral AES and ED25519 keys, and generated secret material is not published.
Compatibility fixtures cover Spring Boot 2.1.9.RELEASE, 2.7.18, 3.3.13, 3.4.13, and 4.1.0 on JDK 8, 11, 17, 21, and 25 respectively. This selected set does not establish compatibility with every Spring Boot version or packaging layout. The launcher and packaged JDK/JRE must match the operating system and CPU architecture. macOS has not been verified.
See compat-tests/README.md for fixture details, supported combinations, and local runner commands. See GitHub Actions for current CI status.
- Expand platform and path coverage: Add macOS coverage and more representative Spring Boot executable-JAR layouts, nested dependencies, and encrypted-resource loading paths.
- JRE and classpath JAR signature verification: Improve runtime integrity validation
- Anti-disassembly detection and protection: Add detection and protection against disassembly attempts
Contributions are welcome through:
- Issues for bug reports and feature requests
- Forks and pull requests
- Documentation improvements and test cases
Distributed under the Apache License 2.0.