2016-11-24 1 views
3

J'essayais de passer du fournisseur de sécurité de SunJCE à Bouncy Castle (BC) et j'ai trébuché sur ce comportement particulier dans l'objet Cipher. À ma connaissance le texte crypté retourné par cipher.update(bytes) de SunJCE inclut le vecteur d'initialisation suivant (IV) comme dernier bloc. Avec BC, je dois appeler cipher.doFinal() et prendre le premier bloc pour obtenir le IV. L'algorithme que j'utilise est AES/CBC/PKCS5PaddingBouncyCastle et SunJCE résultat différent dans Cipher :: update et Cipher :: doFInal

Pourquoi cela se produit-il et quelle est la meilleure façon de gérer cela?

Voici mon code

import org.bouncycastle.jce.provider.BouncyCastleProvider; 

import javax.crypto.Cipher; 
import javax.crypto.spec.IvParameterSpec; 
import javax.crypto.spec.SecretKeySpec; 
import java.nio.charset.StandardCharsets; 
import java.security.Security; 
import java.util.Arrays; 
import java.util.Base64; 

import static java.nio.charset.StandardCharsets.UTF_8; 
import static javax.xml.bind.DatatypeConverter.printHexBinary; 

public class CipherDebug { 

    private final String algorithm; 

    private final String provider; 

    private final String cryptographicAlgorithm; 

    public CipherDebug(String algorithm, 
         String cipherMode, 
         String paddingMode, 
         String provider) { 
     this.algorithm = algorithm; 
     this.provider = provider; 
     this.cryptographicAlgorithm = algorithm + "/" + cipherMode + "/" + paddingMode; 
    } 

    private Cipher createCipher(int encryptDecryptMode, 
           byte[] keyValue, 
           byte[] initializationVector) throws Exception { 
     SecretKeySpec keySpec = new SecretKeySpec(keyValue, algorithm); 
     IvParameterSpec ivSpec = new IvParameterSpec(initializationVector); 
     Cipher cipher = Cipher.getInstance(cryptographicAlgorithm, provider); 
     cipher.init(encryptDecryptMode, keySpec, ivSpec); 
     return cipher; 
    } 

    @Override 
    public String toString() { 
     return "CipherDebug{" + 
       "provider=\"" + provider + '\"' + 
       ", cryptographicAlgorithm=\"" + cryptographicAlgorithm + '\"' + 
       '}'; 
    } 

    private static String generateData(int length) { 
     char[] chars = new char[length]; 
     Arrays.fill(chars, '0'); 
     return new String(chars); 
    } 

    public static void main(String[] args) throws Exception { 
     Security.insertProviderAt(new BouncyCastleProvider(), 1); 

     int numberOfChunks = 3; 
     byte[] keyValue = Base64.getDecoder() 
       .decode("yY7flqEdx95dojF/yY7flqEdx95dojF/".getBytes(StandardCharsets.UTF_8)); 
     byte[] initializationVector = "pjts4PzQIr9Pd2yb".getBytes(StandardCharsets.UTF_8); 

     CipherDebug bouncyCastle = new CipherDebug("AES", "CBC", "PKCS5Padding", "BC"); 

     CipherDebug sunJCE = new CipherDebug("AES", "CBC", "PKCS5Padding", "SunJCE"); 

     Cipher bouncyCastleCipher = bouncyCastle.createCipher(Cipher.ENCRYPT_MODE, 
       keyValue, initializationVector); 

     Cipher sunJCECipher = sunJCE.createCipher(Cipher.ENCRYPT_MODE, 
       keyValue, initializationVector); 

     assert bouncyCastleCipher.getBlockSize() == sunJCECipher.getBlockSize(); 

     // blockSize = 16 
     int blockSize = bouncyCastleCipher.getBlockSize(); 

     byte[] data = generateData(blockSize * numberOfChunks).getBytes(UTF_8); 
     byte[] bouncyCastleUpdate = bouncyCastleCipher.update(data); 
     byte[] sunJCEUpdate = sunJCECipher.update(data); 

     //303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030 
     System.out.println(printHexBinary(data)); 

     // CipherDebug{provider="BC", cryptographicAlgorithm="AES/CBC/PKCS5Padding"} 
     // 1D4DE40480F0528D4F77E788817DA62902D98C9AE6DF9299F4F2D1836CC10924 
     // 0320B10C8646D17E0755F8BBA1214ABF24D2E6E7F06184A78559793B23A9A341 
     System.out.println(bouncyCastle.toString()); 
     System.out.println(printHexBinary(bouncyCastleUpdate)); 
     System.out.println(printHexBinary(bouncyCastleCipher.doFinal())); 

     System.out.println(); 

     // CipherDebug{provider="SunJCE", cryptographicAlgorithm="AES/CBC/PKCS5Padding"} 
     // 1D4DE40480F0528D4F77E788817DA62902D98C9AE6DF9299F4F2D1836CC109240320B10C8646D17E0755F8BBA1214ABF 
     // 24D2E6E7F06184A78559793B23A9A341 
     System.out.println(sunJCE.toString()); 
     System.out.println(printHexBinary(sunJCEUpdate)); 
     System.out.println(printHexBinary(sunJCECipher.doFinal())); 

     // assertion fails 
     assert Arrays.equals(bouncyCastleUpdate, sunJCEUpdate); 
    } 
} 

La sortie:

// data 
03030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030 

// Bouncy Castle 
CipherDebug{provider="BC", cryptographicAlgorithm="AES/CBC/PKCS5Padding"} 
1D4DE40480F0528D4F77E788817DA62902D98C9AE6DF9299F4F2D1836CC10924 
0320B10C8646D17E0755F8BBA1214ABF24D2E6E7F06184A78559793B23A9A341 


// SunJCE 
CipherDebug{provider="SunJCE", cryptographicAlgorithm="AES/CBC/PKCS5Padding"} 
1D4DE40480F0528D4F77E788817DA62902D98C9AE6DF9299F4F2D1836CC109240320B10C8646D17E0755F8BBA1214ABF 
24D2E6E7F06184A78559793B23A9A341 

Répondre

4

Les données supplémentaires dans le texte chiffré à la fin est le rembourrage, mais vous devez appeler Cipher.doFinal() à la fois cas - le chiffrement a besoin de savoir qu'il a toutes les données d'entrée avant qu'il puisse ajouter ou enlever le remplissage.

Cipher.getIV() renverra l'IV. Alors que la IV peut être générée lors du cryptage, elle ne fait jamais partie du flux réel et est normalement transmise en tant que paramètre ou générée.

Dans le cas où la sortie est fragmentée et provoque une confusion, il n'y a pas de "standard" pour cela - dans le cas de BC, elle est toujours bloquée jusqu'à l'arrivée de doFinal(), pour certains cryptages. Le fournisseur SunJCE ne le fait pas, pour les HSM, l'entrée peut être tamponnée en premier pour faire un meilleur usage du HSM, donc une succession de mises à jour peut ne rien produire, alors soudainement un gros morceau de données traitées peut apparaître. Vous devez compter sur les valeurs de retour des mises à jour et doFinals pour dire gérer correctement les données traitées.

+0

Merci @ david-crochet pour le rendre très clair et pour l'explication complète. Je vois, oui ça a du sens maintenant. La confusion provenait principalement de la divergence des deux tableaux de sortie qui ont échoué l'un de nos tessons qui dépend apparemment de l'hypothèse du format de sortie de SunJCE. Merci encore! – Mitch