У вас несколько проблем. Наиболее очевидным является то, что вы пытаетесь прочитать IV из файла, но openssl enc
в своем режиме на основе пароля по умолчанию извлекает ключ и IV из пароля и соли - даже при использовании PBKDF2. Однако как стандартные (Sun / Oracle / OpenJDK), так и поставщики BouncyCastle в Java реализуют PBKDF2 для получения только ключа - так, как он используется в PBES2 .
Даже без этого ваш метод генерации «пароля» в виде случайных байтов тоже не будет работать. Стандарт PKCS5 фактически определяет, что PBKDF2 принимает пароль как
строку октетов произвольной длины, интерпретация которой как текстовая строка не указана. Однако в интересах взаимодействия рекомендуется, чтобы приложения следовали некоторым общим правилам кодирования текста. ASCII и UTF-8 [RFC3629] - две возможности. (ASCII является подмножеством UTF-8.)
Многие системы более серьезно относятся к кодированию с возможностью взаимодействия, и Java, в частности (который изначально был разработан для использования во всем мире), определяет PBEKeySpec
как содержат символов - char[]
в Java это UTF-16 - которые кодируются как UTF-8 при выполнении PBKDF2. Напротив, openssl
- это программа C, созданная до начала века, когда C начал признавать существование других стран, кроме США, поэтому она знает только о байтах - байтах, которые могут быть ASCII или каким-либо другим однобайтовым кодом, например EBCDI C, но, возможно, вообще не символами и уж точно не какими-либо из тех странных иностранных символов, которые не помещаются в байт. Вероятность того, что последовательность из 32 случайных байтов будет действительной UTF-8, очень мала; мне слишком сложно рассчитывать аналитически, но я провел тест на 100 миллионов случайных значений и получил только одно, которое будет работать с вашей схемой. (Я собирался протестировать миллиард, но устал ждать.)
Кроме того, поскольку пароль должен быть текстовым, openssl
читает -pass file:
как текстовый файл и рассматривает его как строку. Это означает, что если какой-либо из случайных байтов является нулевым байтом или байтом, соответствующим символу новой строки, оставшаяся часть данных в файле отбрасывается и игнорируется для получения ключа и IV. Это будет происходить в среднем примерно в 1 из 4 раз для случайных 32-байтовых значений и примерно в 1 из 20 раз в файле достаточно рано, чтобы сделать результат криптографически слабым и поддающимся взлому.
Возникает вопрос: почему вы вообще используете шифрование на основе пароля? Если ваш «ключ» составляет 32 байта от приличного безопасного ГСЧ - а это openssl rand
- вам не нужно его усиливать, он уже действителен в качестве ключа. Вы можете использовать openssl enc
для шифрования на основе ключей , а не на основе пароля, и это более эффективно, безопаснее и намного проще в Java - огромная победа. ЕСЛИ вы используете новый случайный ключ для каждого шифрования, вам даже не нужно использовать настоящий IV, вы можете просто использовать нулевой IV, как я сделал ниже. Но если вы собираетесь повторно использовать ключ / any, вам необходимо использовать уникальный и непредсказуемый - обычно случайный - IV для каждого шифрования и передавать его вместе с данными, возможно, просто поместив его в
Так или иначе, вот довольно простая Java программа, которая может обрабатывать любой случай: форма openssl pdbkf2 с «паролем», который на самом деле не является паролем и не UTF-8 , или более разумная форма на основе клавиш (но для этой демонстрации с нулевым IV):
//nopackage
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.security.*;
import java.util.*;
import javax.crypto.*;
import javax.crypto.spec.*;
public class SO61613286 {
static public void main (String[] args) throws Exception /* let JVM give error */{
// arguments: P/K, filename output from openssl enc, filename of text pw or binary key
byte[] file = Files.readAllBytes(Paths.get(args[1]));
byte[] fil2 = Files.readAllBytes(Paths.get(args[2]));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
if( args[0].startsWith("P") ){
// possibly truncate 'password' in fil2
int n = 0; for( ; n < fil2.length; n++ ) if( fil2[n]==0 || fil2[n]=='\n' ) break;
if( n < fil2.length ) fil2 = Arrays.copyOf(fil2, n);
// extract salt and derive ...
byte[] salt = Arrays.copyOfRange(file, 8, 16);
byte[] derive = PBKDF2 ("HmacSHA256", fil2, salt, 10000, 48);
// ... key is first 32, IV is last 16
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(derive,0,32,"AES"), new IvParameterSpec(derive,32,16));
// remainder of file is ciphertext
System.out.write( cipher.doFinal(file,16,file.length-16) );
}else{
// just use fil2 as key and zeros for IV ...
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(fil2,"AES"), new IvParameterSpec(new byte[16]));
// ... all of file is ciphertext
System.out.write( cipher.doFinal(file,0,file.length) );
// !!!if key will be reused, must use better IVs and transmit with the data!!!
}
}
public static byte[] PBKDF2 (String prf, byte[] pass, byte[] salt, int iter, int len)
throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException {
byte[] result = new byte[len];
Mac mac = Mac.getInstance(prf);
mac.init (new SecretKeySpec (pass,prf));
byte[] saltcnt = Arrays.copyOf (salt, salt.length+4);
while( /*remaining*/len>0 ){
for( int o = saltcnt.length; --o>=saltcnt.length-4; ) if( ++saltcnt[o] != 0 ) break;
byte[] u = saltcnt, x = new byte[mac.getMacLength()];
for( int i = 1; i <= iter; i++ ){
u = mac.doFinal (u);
for( int o = 0; o < x.length; o++ ) x[o] ^= u[o];
}
int len2 = Math.min (len, x.length);
System.arraycopy (x,0, result,result.length-len, len2);
len -= len2;
}
return result;
}
public static void testutf8 (){
Random r = new Random();
byte[] t = new byte[32];
for( int i = 0; i < 1000000000; i++ ){
r.nextBytes(t);
if( Arrays.equals(new String (t, StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8), t) )
System.out.println(i+" "+Arrays.toString(t));
if( i % 1000000 == 999999 ) System.out.println (i);
}
}
}
и демо:
$ openssl rand 32 >SO61613286.rnd # repeated several times until I got this:
$ xxd SO61613286.rnd # notice the null byte
0000000: ab1a 1384 9238 0900 c947 6b9a c23d 5ee0 .....8...Gk..=^.
0000010: 32f0 6b2f 91ec 2dd9 a69d eb7d e00e 37ff 2.k/..-....}..7.
$
$ echo the name of the cat >SO61613286.in
$
$ openssl aes-256-cbc -in SO61613286.in -out SO61613286.enc1 -pass file:SO61613286.rnd -pbkdf2 -iter 10000
$ java8 -cp . SO61613286 P SO61613286.enc1 SO61613286.rnd
the name of the cat
$
$ openssl aes-256-cbc -in SO61613286.in -out SO61613286.enc2 -K $(xxd -p -c32 SO61613286.rnd) -iv 00
hex string is too short, padding with zero bytes to length
$ # that's a warning and can be ignored, as long as we don't need real IVs (see above)
$ java8 -cp . SO61613286 K SO61613286.enc2 SO61613286.rnd
the name of the cat
$