How to avoid installing "Unlimited Strength" JCE policy files when deploying an application?

JavaAesJcePolicyfiles

Java Problem Overview


I have an app that uses 256-bit AES encryption which is not supported by Java out of the box. I know to get this to function correctly I install the JCE unlimited strength jars in the security folder. This is fine for me being the developer, I can install them.

My question is since this app will be distributed, end users most likely will not have these policy files installed. Having the end user download these just to make the app function is not an attractive solution.

Is there a way to make my app run without overwriting files on the end user machine? A third party software that can handle it without the policy files installed? Or a way to just reference these policy files from within a JAR?

Java Solutions


Solution 1 - Java

There are a couple of commonly quoted solutions to this problem. Unfortunately neither of these are entirely satisfactory:

  • Install the unlimited strength policy files. While this is probably the right solution for your development workstation, it quickly becomes a major hassle (if not a roadblock) to have non-technical users install the files on every computer. There is no way to distribute the files with your program; they must be installed in the JRE directory (which may even be read-only due to permissions).
  • Skip the JCE API and use another cryptography library such as Bouncy Castle. This approach requires an extra 1MB library, which may be a significant burden depending on the application. It also feels silly to duplicate functionality included in the standard libraries. Obviously, the API is also completely different from the usual JCE interface. (BC does implement a JCE provider, but that doesn't help because the key strength restrictions are applied before handing over to the implementation.) This solution also won't let you use 256-bit TLS (SSL) cipher suites, because the standard TLS libraries call the JCE internally to determine any restrictions.

But then there's reflection. Is there anything you can't do using reflection?

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"));
}

Simply call removeCryptographyRestrictions() from a static initializer or such before performing any cryptographic operations.

The JceSecurity.isRestricted = false part is all that is needed to use 256-bit ciphers directly; however, without the two other operations, Cipher.getMaxAllowedKeyLength() will still keep reporting 128, and 256-bit TLS cipher suites won't work.

This code works on Oracle Java 7 and 8, and automatically skips the process on Java 9 and OpenJDK where it's not needed. Being an ugly hack after all, it likely doesn't work on other vendors' VMs.

It also doesn't work on Oracle Java 6, because the private JCE classes are obfuscated there. The obfuscation does not change from version to version though, so it is still technically possible to support Java 6.

Solution 2 - Java

This is now no longer needed for Java 9, nor for any recent release of Java 6, 7, or 8. Finally! :)

Per JDK-8170157, the unlimited cryptographic policy is now enabled by default.

Specific versions from the JIRA issue:

  • Java 9 (10, 11, etc..): Any official release!
  • Java 8u161 or later (Available now)
  • Java 7u171 or later (Only available through 'My Oracle Support')
  • Java 6u181 or later (Only available through 'My Oracle Support')

Note that if for some odd reason the old behavior is needed in Java 9, it can be set using:

Security.setProperty("crypto.policy", "limited");

Solution 3 - Java

Here is solution: 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) {
    }
}

Solution 4 - Java

As of JDK 8u102, the posted solutions relying on reflection will no longer work: the field that these solutions set is now final (https://bugs.openjdk.java.net/browse/JDK-8149417).

Looks like it's back to either (a) using Bouncy Castle, or (b) installing the JCE policy files.

Solution 5 - Java

Bouncy Castle still requires jars installed as far as I can tell.

I did a little test and it seemed to confirm this:

http://www.bouncycastle.org/wiki/display/JA1/Frequently+Asked+Questions

Solution 6 - Java

For an alternative cryptography library, have a look at Bouncy Castle. It has AES and a lot of added functionality. It's a liberal open source library. You will have to use the lightweight, proprietary Bouncy Castle API for this to work though.

Solution 7 - Java

You could use method

javax.crypto.Cipher.getMaxAllowedKeyLength(String transformation)

to test the available key length, use that and inform the user about what is going on. Something stating that your application is falling back to 128 bit keys due to the policy files not being installed, for example. Security conscious users will install the policy files, others will continue using weaker keys.

Solution 8 - Java

For our application, we had a client server architecture and we only allowed decrypting/encrypting data in the server level. Hence the JCE files are only needed there.

We had another problem where we needed to update a security jar on the client machines, through JNLP, it overwrites the libraries in${java.home}/lib/security/ and the JVM on first run.

That made it work.

Solution 9 - Java

Here's a updated version of ntoskrnl answer. It additionally contains a function to remove the final modifier like Arjan mentioned in the comments.

This version works with JRE 8u111 or newer.

private static void removeCryptographyRestrictions() {
	if (!isRestrictedCryptography()) {
		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");

		Field isRestrictedField = jceSecurity.getDeclaredField("isRestricted");
		isRestrictedField.setAccessible(true);
		setFinalStatic(isRestrictedField, true);
		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));
	}
	catch (final Exception e) {
		e.printStackTrace();
	}
}

static void setFinalStatic(Field field, Object newValue) throws Exception {
      field.setAccessible(true);

      Field modifiersField = Field.class.getDeclaredField("modifiers");
      modifiersField.setAccessible(true);
      modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

      field.set(null, newValue);
   }

private static boolean isRestrictedCryptography() {
	// This simply matches the Oracle JRE, but not OpenJDK.
	return "Java(TM) SE Runtime Environment".equals(System.getProperty("java.runtime.name"));
}

Solution 10 - Java

Here is a modified version of @ntoskrnl's code featuring isRestrictedCryptography check by actual Cipher.getMaxAllowedKeyLength, slf4j logging and support of singleton initialization from application bootstrap like this:

static {
    UnlimitedKeyStrengthJurisdictionPolicy.ensure();
}

This code would correctly stop mangling with reflection when unlimited policy becomes available by default in Java 8u162 as @cranphin's answer predicts.


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.NoSuchAlgorithmException;
import java.security.Permission;
import java.security.PermissionCollection;
import java.util.Map;

// https://stackoverflow.com/questions/1179672/how-to-avoid-installing-unlimited-strength-jce-policy-files-when-deploying-an
public class UnlimitedKeyStrengthJurisdictionPolicy {

    private static final Logger log = LoggerFactory.getLogger(UnlimitedKeyStrengthJurisdictionPolicy.class);

    private static boolean isRestrictedCryptography() throws NoSuchAlgorithmException {
        return Cipher.getMaxAllowedKeyLength("AES/ECB/NoPadding") <= 128;
    }

    private static void removeCryptographyRestrictions() {
        try {
            if (!isRestrictedCryptography()) {
                log.debug("Cryptography restrictions removal not needed");
                return;
            }
            /*
             * Do the following, but with reflection to bypass access checks:
             *
             * JceSecurity.isRestricted = false;
             * JceSecurity.defaultPolicy.perms.clear();
             * JceSecurity.defaultPolicy.add(CryptoAllPermission.INSTANCE);
             */
            Class<?> jceSecurity = Class.forName("javax.crypto.JceSecurity");
            Class<?> cryptoPermissions = Class.forName("javax.crypto.CryptoPermissions");
            Class<?> cryptoAllPermission = Class.forName("javax.crypto.CryptoAllPermission");

            Field isRestrictedField = jceSecurity.getDeclaredField("isRestricted");
            isRestrictedField.setAccessible(true);
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(isRestrictedField, isRestrictedField.getModifiers() & ~Modifier.FINAL);
            isRestrictedField.set(null, false);

            Field defaultPolicyField = jceSecurity.getDeclaredField("defaultPolicy");
            defaultPolicyField.setAccessible(true);
            PermissionCollection defaultPolicy = (PermissionCollection) defaultPolicyField.get(null);

            Field perms = cryptoPermissions.getDeclaredField("perms");
            perms.setAccessible(true);
            ((Map<?, ?>) perms.get(defaultPolicy)).clear();

            Field instance = cryptoAllPermission.getDeclaredField("INSTANCE");
            instance.setAccessible(true);
            defaultPolicy.add((Permission) instance.get(null));

            log.info("Successfully removed cryptography restrictions");
        } catch (Exception e) {
            log.warn("Failed to remove cryptography restrictions", e);
        }
    }

    static {
        removeCryptographyRestrictions();
    }

    public static void ensure() {
        // just force loading of this class
    }
}

Solution 11 - Java

During installation of your program, just prompt the user and have a DOS Batch script or a Bash shell script download and copy the JCE into the proper system location.

I used to have to do this for a server webservice and instead of a formal installer, I just provided scripts to setup the app before the user could run it. You can make the app un-runnable until they run the setup script. You could also make the app complain that the JCE is missing and then ask to download and restart the app?

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKenView Question on Stackoverflow
Solution 1 - JavantoskrnlView Answer on Stackoverflow
Solution 2 - JavabluemindView Answer on Stackoverflow
Solution 3 - JavamikeView Answer on Stackoverflow
Solution 4 - JavaSam RobertonView Answer on Stackoverflow
Solution 5 - JavatimothyjcView Answer on Stackoverflow
Solution 6 - Javauser150924View Answer on Stackoverflow
Solution 7 - JavaChristian SchulteView Answer on Stackoverflow
Solution 8 - JavaMohamed MansourView Answer on Stackoverflow
Solution 9 - JavaxonedView Answer on Stackoverflow
Solution 10 - JavaVadzimView Answer on Stackoverflow
Solution 11 - JavadjangofanView Answer on Stackoverflow