Diff cryptage entre Java et C #

Bonjour, j’essaie de comprendre comment reproduire le chiffrement de texte effectué en C # mais en Java. La partie du code qui me laisse perplexe et qui ne semble pas trouver de réponse est la suivante: C #:

PasswordDeriveBytes myPass = new PasswordDeriveBytes(Ssortingng Password, byte[] Salt); Trp.Key = myPass.GetBytes(24); Trp.IV = myPass.GetBytes(8); 

Fondamentalement, quel serait l’équivalent de ce code en Java? MISE À JOUR: En utilisant le code fourni pour PasswordDeriveBytes (le deuxième extrait), j’ai été en mesure de reproduire parfaitement le code C #. Merci Maarten Bodewes.

 BASE64Encoder base64 = new BASE64Encoder(); PasswordDeriveBytes i_Pass = new PasswordDeriveBytes(passWord, saltWordAsBytes); byte[] keyBytes = i_Pass.getBytes(24); byte[] ivBytes = i_Pass.getBytes(8); Cipher c3des = Cipher.getInstance("DESede/CBC/PKCS5Padding"); SecretKeySpec myKey = new SecretKeySpec(keyBytes, "DESede"); IvParameterSpec ivspec = new IvParameterSpec(ivBytes); c3des.init (Cipher.ENCRYPT_MODE, myKey, ivspec); encrytpedTextAsByte = c3des.doFinal(plainTextAsBytes); encryptedText = base64.encode(encrytpedTextAsByte); 

mais n’arrive pas à le faire fonctionner sur toutes les plateformes. fondamentalement, le code de décodage est défini (je ne peux pas changer en C # 3.5) et j’essaie de coder en java pour que le code C # puisse le décoder.

Toute aide serait grandement appréciée.

Le problème est que PasswordDeriveBytes n’est défini que pour les 20 premiers octets. Dans ce cas, il s’agit de PBKDF1 (et non de 2 , comme vous l’utilisez actuellement dans votre code Java). L’appel de getBytes plusieurs fois peut également modifier le résultat. L’algorithme pour un ou plusieurs appels à getBytes ou pour plus de 20 octets est propriétaire de Microsoft et ne semble pas être décrit nulle part. Dans Mono, il est même décrit comme un non-correctif car il peut ne pas être sécurisé.

Je suggère fortement d’utiliser RFC2898DeriveBytes qui implémente PBKDF2. Veillez à ne l’utiliser que pour les entrées ASCII, sinon il pourrait ne pas être compatible avec l’implémentation Java.

La seule autre option consiste à déterminer l’extension propriétaire de Microsoft PasswordDeriveBytes vers PBKDF1 (qui définit uniquement la sortie jusqu’à une taille de hachage de 20 octets). J’ai réimplémenté la version de Mono ci-dessous.

Les demandes répétées à Microsoft de mettre à jour la description de l’API de cette fonction n’ont donné aucun résultat. Vous voudrez peut-être lire ce rapport de bogue si vos résultats diffèrent.


C’est l’extension propriétaire de Microsoft. Fondamentalement, il calcule d’abord PBKDF-1 jusqu’à ce qu’il n’inclue pas la dernière itération de hachage, appelez ce HX. Pour les 20 premiers octets, il effectue simplement un autre hachage, il est donc compatible avec PBKDF1. Les hachages suivants survolent la représentation ASCII d’un compteur commençant à 1 (il est donc d’abord converti en "1" , puis en 0x31 ), suivi des octets de HX.

Ce qui suit est une conversion minimaliste, plutôt directe du code Mono:

 public class PasswordDeriveBytes { private final MessageDigest hash; private final byte[] initial; private final int iterations; private byte[] output; private int hashnumber = 0; private int position = 0; public PasswordDeriveBytes(Ssortingng password, byte[] salt) { try { this.hash = MessageDigest.getInstance("SHA-1"); this.initial = new byte[hash.getDigestLength()]; this.hash.update(password.getBytes(UTF_8)); this.hash.update(salt); this.hash.digest(this.initial, 0, this.initial.length); this.iterations = 100; } catch (NoSuchAlgorithmException | DigestException e) { throw new IllegalStateException(e); } } public byte[] getBytes(int cb) { if (cb < 1) throw new IndexOutOfBoundsException("cb"); byte[] result = new byte[cb]; int cpos = 0; // the initial hash (in reset) + at least one iteration int iter = Math.max(1, iterations - 1); // start with the PKCS5 key if (output == null) { // calculate the PKCS5 key output = initial; // generate new key material for (int i = 0; i < iter - 1; i++) output = hash.digest(output); } while (cpos < cb) { byte[] output2 = null; if (hashnumber == 0) { // last iteration on output output2 = hash.digest(output); } else if (hashnumber < 1000) { String n = String.valueOf(hashnumber); output2 = new byte[output.length + n.length()]; for (int j = 0; j < n.length(); j++) output2[j] = (byte) (n.charAt(j)); System.arraycopy(output, 0, output2, n.length(), output.length); // don't update output output2 = hash.digest(output2); } else { throw new SecurityException(); } int rem = output2.length - position; int l = Math.min(cb - cpos, rem); System.arraycopy(output2, position, result, cpos, l); cpos += l; position += l; while (position >= output2.length) { position -= output2.length; hashnumber++; } } return result; } } 

Ou, un peu plus optimisé et lisible, ne laissant que le tampon de sortie et la position à modifier entre les appels:

 public class PasswordDeriveBytes { private final MessageDigest hash; private final byte[] firstToLastDigest; private final byte[] outputBuffer; private int position = 0; public PasswordDeriveBytes(Ssortingng password, byte[] salt) { try { this.hash = MessageDigest.getInstance("SHA-1"); this.hash.update(password.getBytes(UTF_8)); this.hash.update(salt); this.firstToLastDigest = this.hash.digest(); final int iterations = 100; for (int i = 1; i < iterations - 1; i++) { hash.update(firstToLastDigest); hash.digest(firstToLastDigest, 0, firstToLastDigest.length); } this.outputBuffer = hash.digest(firstToLastDigest); } catch (NoSuchAlgorithmException | DigestException e) { throw new IllegalStateException("SHA-1 digest should always be available", e); } } public byte[] getBytes(int requested) { if (requested < 1) { throw new IllegalArgumentException( "You should at least request 1 byte"); } byte[] result = new byte[requested]; int generated = 0; try { while (generated < requested) { final int outputOffset = position % outputBuffer.length; if (outputOffset == 0 && position != 0) { final String counter = String.valueOf(position / outputBuffer.length); hash.update(counter.getBytes(US_ASCII)); hash.update(firstToLastDigest); hash.digest(outputBuffer, 0, outputBuffer.length); } final int left = outputBuffer.length - outputOffset; final int required = requested - generated; final int copy = Math.min(left, required); System.arraycopy(outputBuffer, outputOffset, result, generated, copy); generated += copy; position += copy; } } catch (final DigestException e) { throw new IllegalStateException(e); } return result; } } 

En réalité, la sécurité n’est pas si mauvaise dans la mesure où les octets sont séparés les uns des autres par le résumé. Donc, la clé est relativement OK. Notez qu'il existe des implémentations Microsoft PasswordDeriveBytes contenant un bogue et des octets répétés (voir le rapport de bogue ci-dessus). Ceci n'est pas reproduit ici.

Usage:

 private static final Ssortingng PASSWORD = "46dkaKLKKJLjdkdk;akdjafj"; private static final byte[] SALT = { 0x26, 0x19, (byte) 0x81, 0x4E, (byte) 0xA0, 0x6D, (byte) 0x95, 0x34 }; public static void main(Ssortingng[] args) throws Exception { final Cipher desEDE = Cipher.getInstance("DESede/CBC/PKCS5Padding"); final PasswordDeriveBytes myPass = new PasswordDeriveBytes(PASSWORD, SALT); final SecretKeyFactory kf = SecretKeyFactory.getInstance("DESede"); final byte[] key = myPass.getBytes(192 / Byte.SIZE); final SecretKey desEDEKey = kf.generateSecret(new DESedeKeySpec(key)); final byte[] iv = myPass.getBytes(desEDE.getBlockSize()); desEDE.init(Cipher.ENCRYPT_MODE, desEDEKey, new IvParameterSpec(iv)); final byte[] ct = desEDE.doFinal("owlstead".getBytes(US_ASCII)); } 

Notes sur l'implémentation Java:

  • le nombre d'itérations est trop faible, vous devriez au moins utiliser un nombre d'itérations de 1K, bien que je propose au moins 4K
  • la taille de la clé est incorrecte, vous devez créer des clés de 3 * 64 = 192 bits au lieu de 196 bits
  • 3DES vieillit, utilisez plutôt AES