¿Cómo evitar la instalación de archivos de políticas JCE de "fuerza ilimitada" al implementar una aplicación?
Tengo una aplicación que utiliza cifrado AES de 256 bits que Java no admite de fábrica. Sé que para que esto funcione correctamente instalo los frascos de potencia ilimitada de JCE en la carpeta de seguridad. Esto está bien para mí como desarrollador, puedo instalarlos.
Mi pregunta es que, dado que esta aplicación se distribuirá, lo más probable es que los usuarios finales no tengan instalados estos archivos de políticas. Hacer que el usuario final los descargue solo para que la aplicación funcione no es una solución atractiva.
¿Existe alguna manera de hacer que mi aplicación se ejecute sin sobrescribir archivos en la máquina del usuario final? ¿Un software de terceros que pueda manejarlo sin los archivos de políticas instalados? ¿O una forma de simplemente hacer referencia a estos archivos de políticas desde un JAR?
Hay un par de soluciones comúnmente citadas para este problema. Lamentablemente, ninguno de estos es del todo satisfactorio:
- Instale los archivos de políticas de fuerza ilimitada . Si bien esta es probablemente la solución adecuada para su estación de trabajo de desarrollo, rápidamente se convierte en una gran molestia (si no en un obstáculo) que usuarios sin conocimientos técnicos instalen los archivos en cada computadora. No hay forma de distribuir los archivos con su programa; deben instalarse en el directorio JRE (que incluso puede ser de solo lectura debido a los permisos).
- Omita la API JCE y utilice otra biblioteca de criptografía como Bouncy Castle . Este enfoque requiere una biblioteca adicional de 1 MB, lo que puede suponer una carga importante según la aplicación. También parece una tontería duplicar la funcionalidad incluida en las bibliotecas estándar. Obviamente, la API también es completamente diferente de la interfaz JCE habitual. (BC implementa un proveedor JCE, pero eso no ayuda porque las restricciones de seguridad de las claves se aplican antes de pasar a la implementación). Esta solución tampoco le permitirá usar conjuntos de cifrado TLS (SSL) de 256 bits, porque el Las bibliotecas TLS estándar llaman a la JCE internamente para determinar cualquier restricción.
Pero luego está la reflexión. ¿Hay algo que no puedas hacer usando la reflexión?
private static void removeCryptographyRestrictions() {
if (!isRestrictedCryptography()) {
logger.fine("Cryptography restrictions removal not needed");
return;
}
try {
/*
* Do the following, but with reflection to bypass access checks:
*
* JceSecurity.isRestricted = false;
* JceSecurity.defaultPolicy.perms.clear();
* JceSecurity.defaultPolicy.add(CryptoAllPermission.INSTANCE);
*/
final Class<?> jceSecurity = Class.forName("javax.crypto.JceSecurity");
final Class<?> cryptoPermissions = Class.forName("javax.crypto.CryptoPermissions");
final Class<?> cryptoAllPermission = Class.forName("javax.crypto.CryptoAllPermission");
final Field isRestrictedField = jceSecurity.getDeclaredField("isRestricted");
isRestrictedField.setAccessible(true);
final Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(isRestrictedField, isRestrictedField.getModifiers() & ~Modifier.FINAL);
isRestrictedField.set(null, false);
final Field defaultPolicyField = jceSecurity.getDeclaredField("defaultPolicy");
defaultPolicyField.setAccessible(true);
final PermissionCollection defaultPolicy = (PermissionCollection) defaultPolicyField.get(null);
final Field perms = cryptoPermissions.getDeclaredField("perms");
perms.setAccessible(true);
((Map<?, ?>) perms.get(defaultPolicy)).clear();
final Field instance = cryptoAllPermission.getDeclaredField("INSTANCE");
instance.setAccessible(true);
defaultPolicy.add((Permission) instance.get(null));
logger.fine("Successfully removed cryptography restrictions");
} catch (final Exception e) {
logger.log(Level.WARNING, "Failed to remove cryptography restrictions", e);
}
}
private static boolean isRestrictedCryptography() {
// This matches Oracle Java 7 and 8, but not Java 9 or OpenJDK.
final String name = System.getProperty("java.runtime.name");
final String ver = System.getProperty("java.version");
return name != null && name.equals("Java(TM) SE Runtime Environment")
&& ver != null && (ver.startsWith("1.7") || ver.startsWith("1.8"));
}
Simplemente llame removeCryptographyRestrictions()
desde un inicializador estático o similar antes de realizar cualquier operación criptográfica.
La JceSecurity.isRestricted = false
pieza es todo lo que se necesita para utilizar cifrados de 256 bits directamente; sin embargo, sin las otras dos operaciones, Cipher.getMaxAllowedKeyLength()
seguirá informando 128 y los conjuntos de cifrado TLS de 256 bits no funcionarán.
Este código funciona en Oracle Java 7 y 8 y omite automáticamente el proceso en Java 9 y OpenJDK donde no es necesario. Después de todo, al ser un truco feo, probablemente no funcione en las máquinas virtuales de otros proveedores.
Tampoco funciona en Oracle Java 6, porque las clases privadas JCE están ofuscadas allí. Sin embargo, la ofuscación no cambia de una versión a otra, por lo que aún es técnicamente posible admitir Java 6.
Esto ya no es necesario para Java 9 , ni para ninguna versión reciente de Java 6, 7 u 8. ¡Finalmente! :)
Según JDK-8170157 , la política criptográfica ilimitada ahora está habilitada de forma predeterminada.
Versiones específicas de la edición de JIRA:
- Java 9 (10, 11, etc.): ¡Cualquier versión oficial!
- Java 8u161 o posterior (disponible ahora )
- Java 7u171 o posterior (solo disponible a través de 'My Oracle Support')
- Java 6u181 o posterior (solo disponible a través de 'My Oracle Support')
Tenga en cuenta que si por alguna extraña razón se necesita el comportamiento anterior en Java 9, se puede configurar usando:
Security.setProperty("crypto.policy", "limited");
Aquí está la solución: http://middlesphere-1.blogspot.ru/2014/06/this-code-allows-to-break-limit-if.html
//this code allows to break limit if client jdk/jre has no unlimited policy files for JCE.
//it should be run once. So this static section is always execute during the class loading process.
//this code is useful when working with Bouncycastle library.
static {
try {
Field field = Class.forName("javax.crypto.JceSecurity").getDeclaredField("isRestricted");
field.setAccessible(true);
field.set(null, java.lang.Boolean.FALSE);
} catch (Exception ex) {
}
}
A partir de JDK 8u102, las soluciones publicadas que se basan en la reflexión ya no funcionarán: el campo que establecen estas soluciones ahora es final
( https://bugs.openjdk.java.net/browse/JDK-8149417 ).
Parece que hemos vuelto a (a) usar Bouncy Castle o (b) instalar los archivos de política JCE.