2016-11-24 2 views
3

Я пытался переключить поставщика безопасности с SunJCE на Bouncy Castle (BC) и наткнулся на это своеобразное поведение в объекте Cipher. Насколько мне известно, зашифрованный текст, возвращаемый SunJCE cipher.update(bytes), включает в себя последующий вектор инициализации (IV) в качестве последнего блока. С BC мне нужно позвонить cipher.doFinal() и взять первый блок, чтобы получить IV. Используемый алгоритм AES/CBC/PKCS5PaddingBouncyCastle и SunJCE другой результат в Cipher :: update и Cipher :: doFInal

Почему это происходит и как лучше всего справиться с этим?

Вот мой код

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); 
    } 
} 

Выход:

// data 
03030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030 

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


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

ответ

4

Лишние данные в шифрованный текст в конце концов, это обивка, но вам нужно вызвать Cipher.doFinal() в обоих случаи - шифр должен знать, что он имеет все входные данные, прежде чем он сможет добавлять или удалять дополнения.

Cipher.getIV() вернет IV. Хотя IV может генерироваться при шифровании, он никогда не является частью фактического потока и обычно передается как параметр или генерируется.

В том случае, если выход становится чересстрочным, что вызывает путаницу, для этого нет «стандарта» - в случае BC он всегда держится на блоке до тех пор, пока не поступит сообщение toFinal(), для некоторых шифров Поставщик SunJCE этого не делает, для HSM вход может быть буферизирован, чтобы вначале лучше использовать HSM, поэтому последовательность обновлений может ничего не вытворять, а затем может появиться большой фрагмент обработанных данных. Вы должны полагаться на возвращаемые значения из обновлений и doFinals, чтобы правильно обрабатывать обработанные данные.

+0

Спасибо @ david-hook за то, что он очень понятен и для всестороннего объяснения. Понимаю, да, теперь это имеет смысл. Путаница объясняется главным образом несоответствием двух выходных массивов, которые не привели к одной из наших проблем, которые, по-видимому, зависят от предположения о выходном формате SunJCE. Еще раз спасибо! – Mitch