J'essaie de stocker de manière sécurisée un mot de passe dans une base de données et pour cela j'ai choisi de stocker son hachage généré à l'aide de la fonction PBKDF2. Je souhaite effectuer cette opération à l'aide de la bibliothèque du château gonflable, mais je ne sais pas pourquoi je ne parviens pas à la faire fonctionner à l'aide de l'interface JCE .... Le problème est que la génération du hachage dans 3 modes différents:
1. en utilisant la fabrique de clés secrètes PBKDF2WithHmacSHA1 fournie par Sun
2. utiliser directement l'api du château gonflable
3. utiliser le château gonflable par JCE
résulte en 2 valeurs distinctes: une commune aux deux premières et une pour la troisième.
Voici mon code:
//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());
Je sais que PBKDF2 est implémenté à l’aide de HMAC SHA1, c’est pourquoi j’ai choisi comme algorithme dans la dernière méthode le "PBEWITHHMACSHA1" que j’ai emprunté à la documentation Java du château gonflable.
La sortie est la suivante:
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
Des idées?
En bref, la différence tient au fait que l'algorithme PBKDF2 des modes n ° 1 et n ° 2 utilise le schéma PKCS n ° 5 v2 (PKCS5S2) pour la génération de clé itérative, mais que le fournisseur BouncyCastle pour "PBEWITHHMACSHA1" en mode n ° 3 utilise le système PKCS # 12 v1 (PKCS12) algorithme à la place. Ce sont des algorithmes de génération de clé complètement différents, vous obtenez donc des résultats différents.
Plus de détails sur les raisons pour lesquelles il en est ainsi et pourquoi vous obtenez des résultats de tailles différentes sont expliqués ci-dessous.
Tout d'abord, lorsque vous construisez une spécification de clé JCE, le paramètre keyLength exprime uniquement "une préférence" pour le fournisseur, quelle taille de clé vous souhaitez. De les documents de l'API :
Remarque: Ceci est utilisé pour indiquer la préférence sur la longueur de la clé pour les chiffrements de taille de clé variable. La taille réelle de la clé dépend de la mise en œuvre de chaque fournisseur.
Les fournisseurs de Bouncy Castle ne semblent pas respecter ce paramètre, à en juger par la source de JCEPBEKey , vous devez donc vous attendre à obtenir une clé de 160 bits de tout fournisseur de BC qui utilise SHA-1 avec l'API JCE. .
Vous pouvez le confirmer en accédant par programme à la méthode getKeySize()
sur la variable keybc
renvoyée dans votre code de test:
Key keybc = factorybc.generateSecret(keyspecbc);
// ...
Method getKeySize = JCEPBEKey.class.getDeclaredMethod("getKeySize");
getKeySize.setAccessible(true);
System.out.println(getKeySize.invoke(keybc)); // prints '160'
Maintenant, pour comprendre à quoi correspond le fournisseur "PBEWITHHMACSHA1", vous trouverez les informations suivantes dans la source de BouncyCastleProvider :
put("SecretKeyFactory.PBEWITHHMACSHA1",
"org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHA");
Et la mise en œuvre de JCESecretKeyFactory.PBEWithSHA se présente comme suit:
public static class PBEWithSHA
extends PBEKeyFactory
{
public PBEWithSHA()
{
super("PBEwithHmacSHA", null, false, PKCS12, SHA1, 160, 0);
}
}
Vous pouvez voir ci-dessus que cette fabrique de clés utilise l'algorithme PKCS # 12 v1 ( PKCS12 ) pour la génération de clés itérative. Mais l'algorithme PBKDF2 que vous souhaitez utiliser pour le hachage de mot de passe utilise à la place le schéma 2 PKCS # 5 v2 ( PKCS5S2 ). C'est pourquoi vous obtenez des résultats différents.
J'ai jeté un coup d'œil rapide sur les fournisseurs JCE enregistrés dans BouncyCastleProvider
, mais je ne pouvais pas voir les algorithmes de génération de clé [ qui utilisaient PKCS5S2 du tout, sans parler d'un algorithme qui l'utilise également avec HMAC-SHA-1.
Je suppose donc que vous utilisez la mise en œuvre Sun (mode 1 ci-dessus) et que vous perdez la portabilité sur d'autres machines virtuelles, ou que vous utilisez directement les classes de Bouncy Castle (mode 2 ci-dessus) et nécessitant la bibliothèque BC au moment de l'exécution.
Quoi qu'il en soit, vous devriez probablement passer aux clés 160 bits afin de ne pas tronquer inutilement le hachage SHA-1 généré.
J'ai trouvé une méthode BC Crypto-Only (issue du paquetage cms de BC) qui permet de produire un codage de mot de passe basé sur UTF-8. De cette façon, je peux générer une sortie KDF compatible avec
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();
}
}
PBKDF2WithHmacSHA1 est déjà pris en charge dans BouncyCastle 1.60
https://www.bouncycastle.org/specifications.html Mot de passe haché et PBE
Test réussi avec OpenJDK Runtime Environment 18.9 (build 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());
La sortie est:
com.Sun.crypto.provider.PBKDF2KeyImpl
e9b01389fa91a6172ed6e95e1e1a2611
org.bouncycastle.jcajce.provider.symmetric.util.BCPBEKey
e9b01389fa91a6172ed6e95e1e1a2611