PBKDF2 с упругим замком в Java - PullRequest
38 голосов
/ 30 декабря 2011

Я пытаюсь надежно сохранить пароль в базе данных, и для этого я решил сохранить его хэш, сгенерированный с использованием функции PBKDF2. Я хочу сделать это с помощью библиотеки надувных замков, но я не знаю, почему я не могу заставить ее работать, используя интерфейс JCE ... Проблема в том, что генерация хеша в 3 разных режимах:
1. используя фабрику секретных ключей PBKDF2WithHmacSHA1, предоставленную Sun
2. используя надувной замок API непосредственно
3. используя надувной замок через JCE
приводит к двум различным значениям: одно общее для первых двух и одно для третьего.

Вот мой код:

    //Mode 1

    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    KeySpec keyspec = new PBEKeySpec("password".toCharArray(), salt, 1000, 128);
    Key key = factory.generateSecret(keyspec);
    System.out.println(key.getClass().getName());
    System.out.println(Arrays.toString(key.getEncoded()));

    //Mode 2

    PBEParametersGenerator generator = new PKCS5S2ParametersGenerator();
    generator.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(("password").toCharArray()), salt, 1000);
    KeyParameter params = (KeyParameter)generator.generateDerivedParameters(128);
    System.out.println(Arrays.toString(params.getKey()));

    //Mode 3

    SecretKeyFactory factorybc = SecretKeyFactory.getInstance("PBEWITHHMACSHA1", "BC");
    KeySpec keyspecbc = new PBEKeySpec("password".toCharArray(), salt, 1000, 128);
    Key keybc = factorybc.generateSecret(keyspecbc);
    System.out.println(keybc.getClass().getName());
    System.out.println(Arrays.toString(keybc.getEncoded()));
    System.out.println(keybc.getAlgorithm());

Я знаю, что PBKDF2 реализован с использованием HMAC SHA1, поэтому я выбрал в качестве алгоритма в последнем методе «PBEWITHHMACSHA1», который я взял из java docs надувного замка.

Вывод следующий:

com.sun.crypto.provider.SunJCE_ae
[-53, 29, 113, -110, -25, 76, 115, -127, -64, 74, -63, 102, 75, 81, -21, 74]
[-53, 29, 113, -110, -25, 76, 115, -127, -64, 74, -63, 102, 75, 81, -21, 74]
org.bouncycastle.jce.provider.JCEPBEKey
[14, -47, -87, -16, -117, -31, 91, -121, 90, -68, -82, -31, -27, 5, -93, -67, 30, -34, -64, -40]
PBEwithHmacSHA

Есть идеи?

Ответы [ 3 ]

30 голосов
/ 13 мая 2012

Короче говоря, причина различия заключается в том, что алгоритм PBKDF2 в режимах # 1 и # 2 использует схему 2 PKCS # 5 v2 (PKCS5S2) для итеративной генерации ключа, но поставщик BouncyCastle для "PBEWITHHMACSHA1" в режиме # 3 используетвместо этого алгоритм PKCS # 12 v1 (PKCS12).Это совершенно разные алгоритмы генерации ключей, поэтому вы получите разные результаты.

Подробнее о том, почему это так и почему вы получаете результаты разных размеров, объясняется ниже.

Во-первых, когда вы 'При создании JCE KeySpec параметр keyLength выражает «предпочтение» провайдеру, какой размер ключа вы хотите.Начиная с API документы :

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

Поставщики Bouncy Castle не соблюдают этот параметр, судя по источнику JCEPBEKey , поэтому вы должны ожидатьполучить 160-битный ключ от любого провайдера BC, который использует SHA-1 при использовании JCE API.

Вы можете подтвердить это, программно обратившись к методу getKeySize() в возвращенной переменной keybc в вашем тестекод:

Key keybc = factorybc.generateSecret(keyspecbc);
// ...
Method getKeySize = JCEPBEKey.class.getDeclaredMethod("getKeySize");
getKeySize.setAccessible(true);
System.out.println(getKeySize.invoke(keybc)); // prints '160'

Теперь, чтобы понять, что соответствует провайдеру PBEWITHHMACSHA1, вы можете найти в исходном коде BouncyCastleProvider :

put("SecretKeyFactory.PBEWITHHMACSHA1", 
    "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHA");

И реализация JCESecretKeyFactory.PBEWithSHA выглядит следующим образом:

public static class PBEWithSHA
    extends PBEKeyFactory
{
    public PBEWithSHA()
    {
        super("PBEwithHmacSHA", null, false, PKCS12, SHA1, 160, 0);
    }
}

Вы могли видеть выше, что эта фабрика ключей использует алгоритм PKCS # 12 v1 ( PKCS12 )для итеративной генерации ключей.Но алгоритм PBKDF2, который вы хотите использовать для хеширования паролей, использует PKCS # 5 v2, схема 2 ( PKCS5S2 ).Вот почему вы получаете разные результаты.

Я быстро просмотрел поставщиков JCE, зарегистрированных в BouncyCastleProvider, но не смог увидеть каких-либо алгоритмов генерации ключей, которые использовали PKCS5S2 ввсе, не говоря уже о том, который также использует его с HMAC-SHA-1.

Так что я думаю, что вы застряли либо с использованием реализации Sun (режим № 1 выше) и потерей переносимости на других JVM, либо с помощьюклассы Bouncy Castle (режим № 2 выше) и требуют библиотеки BC во время выполнения.

В любом случае, вам, вероятно, следует переключиться на 160-битные ключи, чтобы не урезать сгенерированный хэш SHA-1.т.к.

3 голосов
/ 09 января 2013

Я нашел метод BC Crypto-Only (фактически из пакета cms BC), который работает для создания кодировки паролей на основе UTF-8. Таким образом, я могу генерировать вывод KDF, который совместим с

http://packages.python.org/passlib/lib/passlib.hash.cta_pbkdf2_sha1.html#passlib.hash.cta_pbkdf2_sha1

private byte[] calculatePasswordDigest(char[] pass, byte[] salt, int iterations)
    throws PasswordProtectionException
{
    try
    {
        /* JCE Version (does not work as BC uses PKCS12 encoding)
        SecretKeyFactory kf = SecretKeyFactory.getInstance("PBEWITHHMACSHA1","BC");
        PBEKeySpec ks = new PBEKeySpec(pass, salt, iterations,160);
        SecretKey digest = kf.generateSecret(ks);
        return digest.getEncoded();
        */
        PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator();
        gen.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(pass), salt, iterations);
        byte[] derivedKey = ((KeyParameter)gen.generateDerivedParameters(160)).getKey();
        return derivedKey;
    }
    catch(Exception e)
    {
        LOG.error("Failed to strengthen the password with PBKDF2.",e);
        throw new PasswordProtectionException();
    }
}
0 голосов
/ 12 января 2019

PBKDF2WithHmacSHA1 уже поддерживается в BouncyCastle 1.60

https://www.bouncycastle.org/specifications.html Хеширование паролей и PBE

Тест пройден в среде выполнения OpenJDK 18.9 (сборка 11.0.1 + 13):

    Security.addProvider(new BouncyCastleProvider());

    String password = "xrS7AJk+V6L8J?B%";
    SecureRandom rnd = new SecureRandom();
    int saltLength = 16;
    int keyLength = 128;
    int iterationCount = 10000;

    byte[] salt = new byte[saltLength];
    rnd.nextBytes(salt);

//SunJCE
    SecretKeyFactory factorySun = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1", "SunJCE");
    KeySpec keyspecSun = new PBEKeySpec(password.toCharArray(), salt, iterationCount, keyLength);
    SecretKey keySun = factorySun.generateSecret(keyspecSun);
    System.out.println(keySun.getClass().getName());
    System.out.println(Hex.toHexString(keySun.getEncoded()));

//BouncyCastle  
    SecretKeyFactory factoryBC = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1", "BC");
    KeySpec keyspecBC = new PBEKeySpec(password.toCharArray(), salt, iterationCount, keyLength);
    SecretKey keyBC = factoryBC.generateSecret(keyspecBC);
    System.out.println(keyBC.getClass().getName());
    System.out.println(Hex.toHexString(keyBC.getEncoded()));

    Assert.assertArrayEquals(keySun.getEncoded(), keyBC.getEncoded());

Вывод:

com.sun.crypto.provider.PBKDF2KeyImpl
e9b01389fa91a6172ed6e95e1e1a2611
org.bouncycastle.jcajce.provider.symmetric.util.BCPBEKey
e9b01389fa91a6172ed6e95e1e1a2611
...