Как избежать установки файлов политики JCE «Unlimited Strength» при развертывании приложения?

169

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

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

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

Дункан Джонс
источник
1
Взгляните сюда: docs.oracle.com/javase/1.5.0/docs/guide/security/jce/…
Пити Б.
11
Я подозреваю, что намерение Sun / Oracle заключалось в том, что клиент будет использовать менее безопасный шифр, чтобы АНБ могло отслеживать соединение. Я не шучу и не параноик, но криптография рассматривается как оружие, и существуют запреты на экспорт для совместного использования шифрования .
Сани

Ответы:

175

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

  • Установите файлы политики неограниченной силы . Хотя это, вероятно, правильное решение для вашей рабочей станции разработки, быстро становится главной проблемой (если не препятствием), чтобы пользователи, не являющиеся техническими специалистами, устанавливали файлы на каждом компьютере. Нет возможности распространять файлы с вашей программой; они должны быть установлены в каталоге JRE (который может быть доступен только для чтения из-за разрешений).
  • Пропустите API JCE и используйте другую библиотеку криптографии, такую ​​как 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 там, где он не нужен. В конце концов, будучи уродливым хакером, он, вероятно, не работает на виртуальных машинах других производителей.

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

Ntoskrnl
источник
23
Решение для отражения может нарушать лицензионное соглашение Java : "F. ОГРАНИЧЕНИЯ ТЕХНОЛОГИИ JAVA. Вы не можете ... изменять поведение ... классов, интерфейсов или подпакетов, которые в любом случае определены как" java "," javax " , "солнце", "оракул" или подобное соглашение ... "
М. Дадли
14
@ М.Дадли Может быть. Проконсультируйтесь с юристом перед отправкой продукта, который содержит этот фрагмент кода, если он касается вас.
ntoskrnl
3
@peabody Включение 100-мегабайтного JRE в вашу программу, безусловно, вариант в некоторых случаях. Но если нет, пользователям все равно придется устанавливать файлы политики вручную, даже если вы включаете их в свою программу (по различным причинам, таким как права доступа к файлам). По моему опыту, многие пользователи не способны на это.
ntoskrnl
8
Похоже, что отражение решение просто перестало работать в 1.8.0_112. Это работает в 1.8.0_111, но не в 112.
Джон Л
3
@JohnL Я использую это в приложении. После проблем с finalполем в 8u111 я изменил его, чтобы он мог изменить последнее поле, следуя этому ответу . Результат примерно такой же, как и в новой версии ntoskrnl, за исключением того, что я не объявил modifiersFieldкак final. Один из моих пользователей сообщает, что он работает и в 8u112.
Арьян
87

Теперь это больше не нужно ни для 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");
cranphin
источник
4
Фактически, эта политика используется по умолчанию, поэтому никаких действий в Java 9 не требуется!
ntoskrnl
По состоянию на 2018/01/14 (последняя версия Oracle JDK - 8u151 / 152), она все еще не включена по умолчанию в Java 8, через год после того, как этот ответ был изначально написан ... Однако согласно java.com/en/jre -jdk-cryptoroadmap.html это предназначено для GA 2018/01/16
Alex
В моем случае, и для меня, чтобы получить знак A на этом сайте: ssllabs.com/ssltest ... Я должен установить это следующим образом: Security.setProperty ("crypto.policy", "unlimited"); то ... набор server.ssl.ciphers в моем applications.properties с 256 на основе алгоритмов указан в этой статье -> weakdh.org/sysadmin.html
Artanis Зератул
Также актуально для OpenJDK 8-Установки. Смотрите: stackoverlow-Article: Связана ли политика JCE с openjdk 8?
Leole
22

Вот решение: 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) {
    }
}
Майк
источник
Это то же решение, что и у меня, но без части "defaultPolicy". Сообщение в блоге датировано после моего ответа.
ntoskrnl
1
Но правильно ли это делать? В реальном времени этот код может бросить вызов безопасности приложения? Я не уверен, пожалуйста, помогите мне понять это влияние.
Блюдо
1
Я получаю эту ошибку после запуска этого:java.security.InvalidKeyException: Wrong algorithm: AES or Rijndael required
Энди
3
Начиная с Java 8 build 111, этого решения будет недостаточно, так как isRestrictedполе станет окончательным ( bugs.openjdk.java.net/browse/JDK-8149417 ). Ответ @ ntoskrnl заботится о любом возможном включении модификатора «final». Комментарий @ M.Dudley к Лицензионному соглашению Java также остается в силе.
MPelletier
13

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

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

Сэм Робертон
источник
7
Вы всегда можете использовать больше отражений stackoverflow.com/questions/3301635/…
Universal Electricity
Да, решение @ M.Dudley все еще будет работать для этой isRestrictedобласти, поскольку оно заботится о возможном добавлении модификатора «final».
MPelletier 20.10.16
1
Новый выпуск JDK 8u151 имеет «Новое свойство безопасности для управления политикой шифрования». Итог: удалите "#" из строки "# crypto.policy = unlimited" в "lib \ security \ java.security": oracle.com/technetwork/java/javase/8u151-relnotes-3850493.html
hemisphire
8

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

Мартен Бодевес
источник
19
Они являются отличным провайдером криптографии, но для работы с большими ключами все еще требуется файл JCE неограниченной прочности.
Джон Мигер
16
Если вы используете API Bouncy Castle напрямую, вам не нужны файлы неограниченной силы.
лаз
4

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

javax.crypto.Cipher.getMaxAllowedKeyLength(String transformation)

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

Кристиан Шульте
источник
3

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

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

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

Мохамед Мансур
источник
2

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

Эта версия работает с 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"));
}
xoned
источник
Это работает хорошо, но строка ((Map<?, ?>) perms.get(defaultPolicy)).clear();выдает ошибку компилятора. Комментирование не влияет на его функциональность. Нужна ли эта линия?
Андреас Унтервегер
2

Вот модифицированная версия @ Ntoskrnl код с изображением isRestrictedCryptographyпроверки по фактическимCipher.getMaxAllowedKeyLength , регистрациям SLF4J и поддержке одноплодной инициализации из приложения начальной загрузки , как это:

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;

// /programming/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
    }
}
Vadzim
источник
-1

Во время установки вашей программы просто предложите пользователю загрузить пакетный сценарий DOS или сценарий оболочки Bash и скопируйте JCE в нужное место в системе.

Раньше я делал это для веб-службы сервера, и вместо формального установщика я просто предоставлял сценарии для настройки приложения до того, как пользователь сможет его запустить. Вы можете отключить запуск приложения, пока он не запустит скрипт установки. Вы могли бы также заставить приложение жаловаться на отсутствие JCE, а затем попросить загрузить и перезапустить приложение?

djangofan
источник
7
«заставить мое приложение работать без перезаписи файлов на компьютере конечного пользователя»
erickson
Я полностью отредактировал свой ответ, так как мой первоначальный ответ был неправильным.
Джангофан