Configuración
Un único archivo JSON indica a Lock Master qué debe proteger y con qué intensidad. Todas las técnicas son opcionales: no se aplica ninguna si no la activas. Esta página sirve de guía; cada capa tiene su propia página con código real del antes y el después para cada transformación.
com/you/Main, no com.you.Main.
** abarca cualquier profundidad; * un solo segmento.exclude vs.
keep. Usa primero la exclusión por técnica (rename.exclude, flow.exclude,
techniques.stringEncryptionExclude, y classGuard.encrypt como lista de inclusión). rename.exclude conserva en una clase el nombre de la clase y los nombres de sus métodos y campos
; así, todo lo que se localice por nombre —un punto de entrada, una API pública o mediante reflexión— seguirá resolviéndose,
mientras que la ofuscación del flujo y el cifrado de cadenas siguen protegiendo los cuerpos. Esta es la opción habitual.
keep es el
último recurso absoluto : una clase conservada no se modifica en absoluto —no se renombran elementos, no se altera el flujo, no se cifran cadenas ni se sella— y se distribuye totalmente legible. Úsalo solo cuando el bytecode deba permanecer intacto, por ejemplo, para formatos serializados, protocolos que exigen estabilidad binaria o una biblioteca de terceros que prefieras no ofuscar.Estructura
El nivel superior consta de bloques opcionales. Incluye solo los que necesites:
{
"configVersion": 2,
"techniques": { },
"keep": { },
"rename": { },
"flow": { },
"classGuard": { },
"typeDecoupling":{ },
"antiDebug": { },
"libraries": { },
"seed": 123456
}
configVersion— opcional: identifica la versión actual del contrato de configuración.techniques— protecciones de los archivos de clase: cadenas, renombrado y eliminación de metadatos.keep— EXCLUSIÓN ABSOLUTA: clases que no se modifican en absoluto; úsalo como último recurso.rename— controla cómo se renombran los elementos, incluidarename.exclude(+ ámbitos).flow— ofuscación del flujo de control, incluidoflow.exclude.classGuard— cifrado de clases completas; usa la listaencrypty permite configurar el bloque anidadoantiDump).typeDecoupling— habilita el cifrado de clases desacoplando los tipos concretos.antiDebug— protección ligera que impide la ejecución bajo un depurador.libraries— JAR de dependencias necesarios para resolver la jerarquía de clases.seed— opcional: permite obtener compilaciones reproducibles.
jar y mapping existen en el motor, pero
los gestiona la plataforma en la nube —la CLI recibe la entrada y entrega la salida mediante
--in/--out). mapping también lo gestiona la plataforma: cada compilación con renombrado genera un mapa de nombres que la CLI descarga para
reconstruir los nombres; no tienes que configurar sus rutas.
libraries sí Sí debes configurar ; consulta
libraries más abajo.
Las capas
Las protecciones se apilan por capas, desde la más ligera hasta la más potente. El renombrado, el cifrado de cadenas y la ofuscación del flujo refuerzan los archivos de clase; el cifrado de clases elimina esas clases del disco, el desacoplamiento de tipos permite alcanzar la mayor parte del código y anti-dump las protege durante la ejecución. Cada capa tiene una página específica que explica todas sus opciones con resultados reales del motor: se transforma un método pequeño o un JAR y después se descompila para mostrar exactamente qué se distribuye:
- Renombrado y estructura — elimina el significado de los nombres de clases, métodos, campos y paquetes. Explica
rename,keeplas reglas de keep y las cinco estrategias de paquetes. - Cifrado de cadenas — elimina todos los literales legibles del archivo de clase. Explica los modos
PER_METHOD,INLINEyPOOLmodos. - Ofuscación del flujo de control — reescribe las bifurcaciones, los bucles y los cálculos para impedir que se reproduzcan como Java limpio. Explica los ajustes predefinidos y las diecinueve
flowtécnicas. - Cifrado de clases — distribuye las clases como bloques cifrados sin bytecode que descompilar. Explica
classGuard,antiDebugywatermark. - Desacoplamiento de tipos (CHF) — habilita la capa anterior reescribiendo el código mediante interfaces en claro generadas automáticamente, de modo que puedan sellarse las clases usadas por tipo concreto: la mayor parte del código. Explica
typeDecoupling. - Anti-dump — protege las clases selladas durante la ejecución: detección por capas de depuradores y agentes que deniega ante fallos y forma parte de la clave de descifrado, de modo que nada se desella bajo observación. Explica
classGuard.antiDump.
techniques
El bloque techniques contiene las opciones aplicadas a los archivos de clase. Las dos principales,
stringEncryption y rename, tienen páginas propias (cadenas, renombrado). Las demás eliminan información o configuran metadatos:
| Campo | Qué hace |
|---|---|
| stringEncryption | Cifra los literales de cadena: NONE / PER_METHOD / INLINE / POOL. Consulta Cifrado de cadenas. |
| stringEncryptionExclude | Clases o paquetes cuyas cadenas deben seguir siendo legibles; es la exclusión específica del cifrado de cadenas, además de keep). |
| rename | Renombra clases, métodos y campos; utiliza rename + keep). Consulta Renombrado y estructura. |
| stripDebug | Elimina la información de depuración, como los números de línea y los nombres de variables locales. Recomendado. |
| stripInnerClasses / stripNestInfo | Elimina los metadatos de clases internas y nestmates. |
| synthetic | Marca los miembros como sintéticos para ocultarlos a algunas herramientas e IDE. |
| cacheMode | Caché de cadenas: FAST —guarda el resultado tras el primer descifrado— o SAFE —vuelve a descifrarlo y lo borra en cada acceso—. |
| callerBinding | Vincula las cadenas con su llamador durante la ejecución: STACKWALKER —valor predeterminado— o STACKTRACE —consume menos recursos, pero se puede falsificar—. |
| assetPool | Ubicación de los datos de cadenas cifrados: INLINE —clase auxiliar— o EXTERNAL (META-INF/strings.dat). |
| stripKotlinMetadata | Elimina la anotación @Metadata del código Kotlin. |
| stripAnnotations | Elimina las anotaciones invisibles —con retención CLASS—; las visibles durante la ejecución siempre se conservan. Usa keepAnnotations para definir excepciones. |
libraries
Los JAR de bibliotecas son dependencias que el proyecto importa pero no incluye —las declaradas en build.gradle or pom.xml —la API de Paper o Spigot, un controlador de base de datos o cualquier biblioteca compileOnly or provided —. Están en el classpath durante la compilación, pero no dentro del JAR que nos envías.
El ofuscador necesita verlas para proteger el código de forma segura: para renombrar, reestructurar y cifrar, recorre toda la jerarquía de clases —superclases, interfaces y sobrescrituras de métodos—. Si el código extiende o implementa un tipo de una biblioteca que no podemos ver, no podemos distinguir una sobrescritura de un método nuevo y el JAR protegido podría fallar durante la ejecución. Al declarar las bibliotecas podemos resolver todos los tipos.
Haz que folder apunte al directorio que las contiene; la CLI empaquetará y subirá
todos los archivos .jar que haya dentro —la búsqueda es recursiva— junto con tu JAR; no es necesario enumerarlos uno por uno. Las rutas son relativas al directorio desde el que ejecutas la CLI:
{
"libraries": {
"folder": "./libs"
}
}
Si la carpeta contiene JAR que no quieres subir, enumera sus nombres concretos en
jars para seleccionar solo esos archivos; folder pasará a ser la ruta base y no se recogerá nada más. Solo necesitas lo imprescindible para resolver la jerarquía, no todo el classpath y nunca el propio JDK:
{
"libraries": {
"folder": "./libs",
"jars": ["paper-api.jar", "PlaceholderAPI.jar"]
}
}
Para carpetas grandes, configura mode: "DEMAND_SAFE". La CLI sube primero el manifiesto determinista; mientras el proceso de trabajo resuelve la jerarquía, solicita solo los JAR necesarios. Si no puede resolver una dependencia de forma estática, recurre expresamente a ALL .
Obtenerlas automáticamente
No las recopiles a mano. Añade una tarea a la compilación para copiar todas las dependencias en
./libs/ y después apunta libraries.folder a esa carpeta.
Gradle — añade esto a build.gradle, y después ejecuta
./gradlew exportLibs:
// build.gradle — export your dependencies into ./libs
tasks.register('exportLibs', Copy) {
// runtimeClasspath covers normal deps; add compileClasspath
// if your API is compileOnly / provided (Paper, Spigot, …)
from configurations.runtimeClasspath
from configurations.compileClasspath
into 'libs'
}
// run it: ./gradlew exportLibs
Maven — añade el plugin a pom.xml; se ejecutará al usar
mvn package, o bajo demanda con el comando indicado en el comentario:
<!-- pom.xml -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>export-libs</id>
<phase>package</phase>
<goals><goal>copy-dependencies</goal></goals>
<configuration>
<outputDirectory>${project.basedir}/libs</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!-- run it: mvn package
or: mvn dependency:copy-dependencies -DoutputDirectory=libs -->
seed y reproducibilidad
Configura "seed": <number> para obtener compilaciones reproducibles: la misma entrada con la misma configuración producirá una salida idéntica byte por byte, algo útil para depurar y verificar una compilación. Omítelo para generar una compilación aleatoria nueva cada vez.
--mapping rymga.map y reutilizará los nombres anteriores para los símbolos sin cambios; consulta el archivo de correspondencias. Para una API cuyos nombres nunca deban cambiar, conserva sus nombres originales.classGuard utiliza deliberadamente una clave aleatoria segura, por lo que activarlo hace que la compilación no sea reproducible, con independencia de la seed.Referencia completa
Todos los campos y sus valores válidos en un único archivo. Cópialo y elimina lo que no necesites. Tal como está es JSON válido —sin comentarios— y puede ejecutarse, aunque reflectionObfuscation y
classGuard están desactivados de forma predeterminada porque introducen más cambios:
{
"techniques": {
"stringEncryption": "PER_METHOD",
"stringEncryptionExclude": [],
"rename": true,
"overloadMethods": false,
"synthetic": false,
"stripDebug": true,
"stripInnerClasses": false,
"stripNestInfo": false,
"cacheMode": "FAST",
"callerBinding": "STACKWALKER",
"assetPool": "INLINE",
"stripKotlinMetadata": false,
"stripAnnotations": false,
"keepAnnotations": []
},
"keep": {
"packages": ["com/you/model"],
"classes": ["com/you/Protocol"],
"members": [
{ "classPattern": "com/you/**", "methodPattern": "on*", "fieldPattern": null, "descriptor": null }
]
},
"rename": {
"deep": 3,
"chars": ["a", "b", "c", "d"],
"packageStrategy": "WRAPPER",
"packageDepth": 2,
"packagePool": 8,
"generatedPackage": { "enabled": false, "name": "com/you/internal" },
"exclude": ["com/you/Main"],
"preservePackages": [],
"rewritePluginDescriptors": true
},
"flow": {
"enabled": true,
"preset": "custom",
"intensity": 6,
"exclude": [],
"timingSafeIndy": true,
"seedThreading": false,
"cfgFlattening": false,
"bogusControlFlow": { "enabled": true, "weight": 5 },
"realisticBogus": { "enabled": true, "weight": 5 },
"nullInvariantPredicate":{ "enabled": true, "weight": 5 },
"deadCode": { "enabled": true, "weight": 5 },
"tryCatchAbuse": { "enabled": true, "weight": 5 },
"exceptionFlow": { "enabled": true, "weight": 5 },
"controlFlowFlattening": { "enabled": true, "weight": 5 },
"loopTransforms": { "enabled": true, "weight": 5 },
"gotoSpaghetti": { "enabled": true, "weight": 5 },
"methodOutlining": { "enabled": true, "weight": 5 },
"irreducibleFlow": { "enabled": true, "weight": 5 },
"switchBogusCases": { "enabled": true, "weight": 5 },
"invokeDynamic": { "enabled": true, "weight": 5 },
"seedMutation": { "enabled": true, "weight": 5 },
"integerSplitting": { "enabled": true, "weight": 5 },
"fakeLoops": { "enabled": true, "weight": 5 },
"branchAugmentation": { "enabled": true, "weight": 5 },
"arithmeticMBA": { "enabled": true, "weight": 5 },
"exceptionOverlap": { "enabled": true, "weight": 5 },
"constantExpression": { "enabled": true, "weight": 5 },
"reflectionObfuscation": { "enabled": false, "weight": 5 }
},
"classGuard": {
"enabled": false,
"encrypt": ["com/you/internal/"],
"antiDump": {
"enabled": false,
"sites": 4,
"argsDetection": true,
"jdwpProperty": true,
"nativeDetection": true,
"nativeIntegrity": true,
"nativePrevention": true,
"periodicChecks": true,
"noRetainPlaintext": true,
"scatterChecks": false,
"scatterDensity": 20,
"attachListenerCheck": false
}
},
"typeDecoupling": {
"enabled": false,
"exclude": [],
"protectHotPaths": true
},
"antiDebug": {
"enabled": false,
"action": "throw",
"checkAgents": false
},
"libraries": {
"folder": "./libs",
"jars": []
},
"seed": null
}
Ejemplos completos
Un plugin de Minecraft, ligero y seguro
Cadenas eliminadas, elementos renombrados y ofuscación de flujo ligera. El servidor busca por nombre la clase principal, así que debe incluirse en rename.exclude —conserva su nombre y la
plugin.yml referencia sigue siendo válida—, pero seguirá recibiendo ofuscación del flujo y cifrado de cadenas. Esa es la diferencia respecto a conserva, que la dejaría totalmente legible. Es un buen punto de partida para un primer plugin de Bukkit, Spigot o Paper:
{
"techniques": {
"stringEncryption": "POOL",
"rename": true,
"stripDebug": true
},
"flow": { "enabled": true, "preset": "light" },
"rename": { "exclude": ["com/you/MyPlugin"] }
}
Un plugin real: bibliotecas sombreadas y tareas en segundo plano
El caso habitual en proyectos reales. La mayoría de los plugins sombrean bibliotecas —bStats, una API de licencias…— dentro del JAR y ejecutan tareas asíncronas programadas. Hay dos herramientas para dos necesidades: tu clase principal va en rename.exclude —sigue siendo cargable, pero se protegen el código circundante, el flujo y las cadenas—, mientras que una biblioteca sombreada de terceros que crea su propio hilo o se localiza a sí misma por nombre va en conserva : como no es tu código, es preferible dejarla totalmente intacta a correr el riesgo de ofuscarla:
{
"techniques": { "stringEncryption": "PER_METHOD", "rename": true, "stripDebug": true },
"flow": { "enabled": true, "preset": "balanced" },
"rename": { "exclude": ["com/you/MyPlugin"] },
"keep": {
"packages": ["com/you/libs/bstats", "com/vendor/licenseapi"]
}
}
classGuard carga sus clases cifradas con una estrategia child-first. Así, las tareas asíncronas programadas y los hilos iniciados por tu código las resuelven correctamente, y dos plugins con nombres ofuscados coincidentes ya no colisionan en un servidor compartido. Queda un caso que puede fallar: una biblioteca sombreada que carga una clase por nombre
desde un hilo propio mediante su cargador de clases de contexto —en algunas bStats compilaciones—. Excluye el paquete de esa biblioteca de encrypt —o añádelo a
conserva)—, o usa la configuración anterior —sin
classGuard— para ese JAR. En cualquier caso, no selles hasta comprobar que carga en un servidor real.Un plugin sellado —máxima protección; pruébalo antes—
Añade cifrado de clases, desacoplamiento de tipos y
anti-dump —la configuración recomendada para código real—. encrypt: ["*"] sella todo lo posible, y el desacoplamiento de tipos permite sellar casi todo mediante interfaces generadas automáticamente; no tienes que escribirlas a mano. antiDump protege después las clases selladas durante la ejecución y deniega ante fallos si detecta depuradores o agentes. La clase principal conserva únicamente su
nombre de clase (#name) para que el servidor pueda cargarla; sus campos y métodos se ofuscan, se conserva onEnable automáticamente y el motor la mantiene en claro al detectar el descriptor main). Es mejor usarlo después de comprobar que carga en un servidor real:
{
"techniques": {
"stringEncryption": "PER_METHOD",
"rename": true,
"stripDebug": true
},
"flow": { "enabled": true, "preset": "balanced", "timingSafeIndy": true },
"rename": { "exclude": ["com/you/MyPlugin#name"] },
"classGuard": {
"enabled": true,
"encrypt": ["*"],
"antiDump": { "enabled": true }
},
"typeDecoupling": { "enabled": true }
}
-javaagent, un perfilador o un depurador remoto, el conjunto sellado no se descifrará. Activa antiDump solo en compilaciones sin agentes legítimos conectados y pruébalas en ese entorno exacto. Consulta
Anti-dump.Una biblioteca o un JAR de API
Un JAR contra el que se compila otro código. Su api paquete público se incluye en
rename.exclude: los nombres de clases, métodos y campos se conservan exactamente como los enlazan los llamadores, pero los cuerpos de los métodos siguen protegidos mediante ofuscación del flujo y cifrado de cadenas. En este caso, el antiguo consejo de aplicar «keep» a la API era incorrecto: conserva
inmovilizaría los nombres y dejaría los cuerpos legibles. Todo lo que quede fuera api se ofusca por completo; MIRROR conserva la estructura de paquetes para que la reflexión siga funcionando:
{
"techniques": { "stringEncryption": "POOL", "rename": true, "stripDebug": true },
"flow": { "enabled": true, "preset": "balanced" },
"rename": {
"packageStrategy": "MIRROR",
"exclude": ["com/you/api/"]
}
}
Un plugin de Kotlin
Igual que un plugin normal, pero con stripKotlinMetadata para eliminar las @Metadata
anotaciones que permiten reconstruir las clases, los nombres y las firmas de Kotlin:
{
"techniques": {
"stringEncryption": "PER_METHOD",
"rename": true,
"stripDebug": true,
"stripKotlinMetadata": true
},
"flow": { "enabled": true, "preset": "balanced" },
"rename": { "exclude": ["com/you/MyKotlinPlugin"] }
}
Una aplicación independiente con todo activado
Un JAR ejecutable —no un plugin— con el ajuste de flujo más intenso, clases internas selladas y anti-debug. El Main-Class es lo que java -jar
se resuelve por nombre, por lo que permanece en claro y sin renombrar —fuera de encrypt, in
rename.exclude—, aunque sí recibe ofuscación del flujo y cifrado de cadenas:
{
"techniques": { "stringEncryption": "PER_METHOD", "rename": true, "stripDebug": true },
"flow": { "enabled": true, "preset": "max" },
"rename": { "exclude": ["com/you/Main#name"] },
"classGuard": { "enabled": true, "encrypt": ["*"] },
"typeDecoupling": { "enabled": true },
"antiDebug": { "enabled": true, "action": "throw", "checkAgents": false }
}
max
ajuste de flujo y reflectionObfuscation— introducen muchos cambios. Ejecuta la compilación protegida antes de distribuirla y añade
conserva para todo lo que utilice la reflexión.