Как избежать установки файлов политики JCE "Неограниченная сила" при развертывании приложения?

У меня есть приложение, которое использует 256-битное шифрование AES, которое не поддерживается Java из коробки. Я знаю, чтобы это правильно функционировало. Я устанавливаю JCE неограниченное количество банок в папке безопасности. Это нормально для меня, будучи разработчиком, я могу установить их.

Мой вопрос в том, что это приложение будет распространено, у конечных пользователей, скорее всего, эти файлы политик не будут установлены. После того, как конечный пользователь загрузит их, чтобы сделать функцию приложения не привлекательным решением.

Есть ли способ заставить мое приложение работать без перезаписи файлов на компьютере конечного пользователя? Программное обеспечение сторонних разработчиков, которое может обрабатывать его без установленных файлов политики? Или способ просто ссылаться на эти файлы политики из JAR?

Ответ 1

Есть несколько часто цитируемых решений этой проблемы. К сожалению, ни одно из них не является полностью удовлетворительным:

  • Установите файлы неограниченной силы.. Хотя это, вероятно, правильное решение для вашей рабочей станции разработки, оно быстро становится серьезной проблемой (если не дорожным блоком), чтобы нетехнические пользователи устанавливали файлы на каждом компьютере. нет способа распространять файлы с вашей программой; они должны быть установлены в каталог JRE (который может даже быть доступен только для чтения из-за разрешений).
  • Пропустить JCE API и использовать другую библиотеку криптографии, такую ​​как Bouncy Castle. Для этого подхода требуется дополнительная библиотека 1 МБ, которая может быть значительной нагрузкой в ​​зависимости от приложения. Также глупо дублировать функциональность, включенную в стандартные библиотеки. Очевидно, что API также полностью отличается от обычного интерфейса JCE. (BC реализует JCE-провайдер, но это не помогает, потому что ограничения силы ключа применяются до передачи реализации.) Это решение также не позволит вам использовать 256-битные шифровые пакеты TLS (SSL), поскольку стандартные библиотеки TLS вызывают JCE внутри, чтобы определить любые ограничения.

Но тогда есть отражение. Есть ли что-то, что вы не можете сделать, используя отражение?

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

Просто вызовите removeCryptographyRestrictions() из статического инициализатора или так, прежде чем выполнять криптографические операции.

Часть JceSecurity.isRestricted = false - это все, что необходимо для непосредственного использования 256-битных шифров; однако, без двух других операций, Cipher.getMaxAllowedKeyLength() все равно будет хранить отчетность 128, а 256-битные набор шифрования TLS не будут работать.

Этот код работает на Oracle Java 7 и 8 и автоматически пропускает процесс на Java 9 и OpenJDK, где он не нужен. Будучи уродливым взломом в конце концов, он, вероятно, не работает на VM других вендоров.

Он также не работает на Oracle Java 6, потому что частные классы JCE запутаны там. Обфускация не изменяется от версии к версии, но по-прежнему технически возможно поддерживать Java 6.

Ответ 2

Теперь это больше не нужно ни для Java 9, ни для любого недавнего выпуска Java 6, 7 или 8. Наконец! :)

Согласно JDK-8170157, неограниченная криптографическая политика теперь включена по умолчанию.

Конкретные версии из выпуска JIRA:

  • Java 9 (10, 11 и т.д.): Любой официальный релиз!
  • Java 8u161 или новее (доступно сейчас)
  • Java 7u171 или более поздняя версия (доступно только через "Служба поддержки Oracle")
  • Java 6u181 или более поздняя версия (доступно только через "Служба поддержки Oracle")

Обратите внимание, что если по какой-то странной причине в Java 9 требуется старое поведение, его можно установить с помощью:

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

Ответ 3

Вот решение: 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) {
    }
}

Ответ 5

Как и в случае JDK 8u102, размещенные решения, основанные на отражении, больше не будут работать: поле, которое эти решения установлены, теперь final (https://bugs.openjdk.java.net/browse/JDK-8149417).

Похоже, что он вернулся к (a) с помощью Bouncy Castle или (b) установил файлы политики JCE.

Ответ 6

Для альтернативной библиотеки криптографии посмотрите Bouncy Castle. Он имеет AES и много дополнительных функций. Это либеральная библиотека с открытым исходным кодом. Вам придется использовать легкий, запатентованный API Bouncy Castle, чтобы это работало.

Ответ 7

Вы можете использовать метод

javax.crypto.Cipher.getMaxAllowedKeyLength(String transformation)

чтобы проверить доступную длину ключа, используйте это и сообщите пользователю о том, что происходит. Что-то говорит о том, что ваше приложение возвращается к 128-битным ключам из-за того, что файлы политики не установлены, например. Пользователи, сознательные в безопасности, установят файлы политик, другие будут продолжать использовать более слабые ключи.

Ответ 8

Для нашего приложения у нас была архитектура клиентского сервера, и мы разрешили дешифрование/шифрование данных на уровне сервера. Следовательно, файлы JCE нужны только там.

У нас возникла еще одна проблема, когда нам нужно было обновить банку безопасности на клиентских компьютерах через JNLP, она перезаписывает библиотеки в ${java.home}/lib/security/ и JVM при первом запуске.

Это заставило его работать.

Ответ 9

Здесь приведена обновленная версия ntoskrnl. Он дополнительно содержит функцию удаления финального модификатора, например Arjan, упомянутого в комментариях.

Эта версия работает с JRE 8u111 или новее.

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

Ответ 10

Ниже приведена модифицированная версия кода @ntoskrnl с isRestrictedCryptography проверкой фактическим Cipher.getMaxAllowedKeyLength, протоколом slf4j и поддержкой инициализации singleton из бутстрапа приложения следующим образом:

static {
    UnlimitedKeyStrengthJurisdictionPolicy.ensure();
}

Этот код будет правильно останавливать манипуляцию с отражением, когда по умолчанию в Java 8u162 становится доступной неограниченная политика, как прогнозирует ответ @cranphin.


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
    }
}

Ответ 11

Во время установки вашей программы просто пригласите пользователя и загрузите DOS Batch script или Bash shell script и скопируйте JCE в правильное расположение системы.

Я имел обыкновение делать это для сервера webservice, и вместо официального установщика я просто предоставил скрипты для настройки приложения, прежде чем пользователь сможет его запустить. Вы можете сделать приложение un-runnable до тех пор, пока они не запустит установку script. Вы также можете заставить приложение жаловаться на отсутствие JCE, а затем попросить загрузить и перезапустить приложение?