Getting started

Introduction

What HashGuard is, what it changes in a jar and how a run goes from input to output.

HashGuard is a Java bytecode obfuscator. It reads a compiled jar, rewrites its classes and writes a protected jar that runs the same way but is much harder to read, decompile or tamper with. It is built on ASM 9.9 and works on plain JVM bytecode, whatever language produced it.

It has no graphical interface. You drive it with a JSON configuration file, either from the command line or through the Gradle plugin, which makes it fit naturally into a build or a CI job.

hashguard -config obfuscator-config.json

What it changes

Each protection is a transformer, identified by a short token such as rename or flatten. Together they rewrite:

AreaWhat changes
NamesClasses, fields and methods get meaningless names, in the style you choose.
Control flowMethod bodies become switch-driven state machines, with opaque predicates and reordered blocks.
ConstantsNumbers move into encrypted tables or arithmetic chains.
StringsString pools are sharded and encrypted, and decrypted on demand at runtime.
ReferencesDirect calls and field accesses go through invokedynamic call sites, method handles and dispatchers.
StructurePhantom members, phantom annotations and fake exception edges break structural fingerprints and decompilers.
MetadataLine numbers, local variable names, parameter names, source file names and inner class tables are removed or scrambled.
RuntimeOptional integrity checks and anti-tamper guards stop a modified or instrumented application.

The full list is in the transformers reference.

How a run works

A run reads the input jar and the libraries it depends on, then passes every class through an ordered pipeline of seven phases:

PRE_RENAME → RENAME → PROTECT_EARLY → STRING → CONTROL_FLOW → STRUCTURE → DYNAMIC_LAST

PRE_RENAME only runs when you select methods to virtualise with sensitiveVm. Each transformer belongs to one phase and has a fixed position inside it, so the passes always run in the same order and each one builds on the previous ones. The result is written to the output jar. Optionally, a mapping file records every old and new name so that obfuscated stack traces stay readable.

You choose what runs in two ways: a profile sets a baseline from Ghost to Extreme, and explicit options override it.

What to keep in mind

  • Reflection and serialization. Renaming changes names. Anything your code or a library looks up by name must be kept, with annotations in the code or exclusion rules in the config.
  • Runtime cost. Some protections add work at runtime: flattened control flow, encrypted constants and indirect calls cost more than the original code. Exclude hot paths from the heaviest passes.
  • Output size. Heavy passes grow the bytecode. The maxOutputRatio budget stops them before the output outgrows a limit you set.

Next steps