rymga ← Volver a Lock Master

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.

Los nombres son nombres «internos». Siempre que indiques una clase, usa el formato interno de la JVM, con barras en vez de puntos: com/you/Main, no com.you.Main. ** abarca cualquier profundidad; * un solo segmento.
Dos formas de excluir una clase: por técnica 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, incluida rename.exclude (+ ámbitos).
  • flow — ofuscación del flujo de control, incluido flow.exclude.
  • classGuard — cifrado de clases completas; usa la lista encrypt y permite configurar el bloque anidado antiDump).
  • 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, keep las 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, INLINE y POOL modos.
  • 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 flow técnicas.
  • Cifrado de clases — distribuye las clases como bloques cifrados sin bytecode que descompilar. Explica classGuard, antiDebug y watermark.
  • 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:

CampoQué hace
stringEncryptionCifra los literales de cadena: NONE / PER_METHOD / INLINE / POOL. Consulta Cifrado de cadenas.
stringEncryptionExcludeClases o paquetes cuyas cadenas deben seguir siendo legibles; es la exclusión específica del cifrado de cadenas, además de keep).
renameRenombra clases, métodos y campos; utiliza rename + keep). Consulta Renombrado y estructura.
stripDebugElimina la información de depuración, como los números de línea y los nombres de variables locales. Recomendado.
stripInnerClasses / stripNestInfoElimina los metadatos de clases internas y nestmates.
syntheticMarca los miembros como sintéticos para ocultarlos a algunas herramientas e IDE.
cacheModeCaché de cadenas: FAST —guarda el resultado tras el primer descifrado— o SAFE —vuelve a descifrarlo y lo borra en cada acceso—.
callerBindingVincula las cadenas con su llamador durante la ejecución: STACKWALKER —valor predeterminado— o STACKTRACE —consume menos recursos, pero se puede falsificar—.
assetPoolUbicación de los datos de cadenas cifrados: INLINE —clase auxiliar— o EXTERNAL (META-INF/strings.dat).
stripKotlinMetadataElimina la anotación @Metadata del código Kotlin.
stripAnnotationsElimina 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 -->
Las bibliotecas no se cobran. El precio es fijo: una compilación cuesta un token y las bibliotecas de referencia no cuestan nada. Sí cuentan para el límite de 350 MB por trabajo y aumentan la carga del servidor, así que exporta solo lo que necesite la jerarquía y elimina los JAR exclusivos de pruebas o ejecución que tu código no extienda.

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.

Para mantener los nombres estables entre versiones, usa la ofuscación incremental. Los nombres ofuscados se asignan por orden, por lo que una semilla no basta para conservarlos cuando cambia el código. Pasa a la CLI --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"]
  }
}
Las tareas asíncronas funcionan correctamente; ten en cuenta un caso límite con las bibliotecas sombreadas que se cargan a sí mismas. 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 }
}
Anti-dump deniega ante cualquier tipo de agente, incluidos los perfiladores. Si el entorno conecta algún sistema de monitorización o APM -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 }
}
Prueba siempre el resultado. Las opciones más intensas —classGuard, el 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.