使用openssl解密使用aes-cbc-256加密的文件。

huangapple go评论105阅读模式
英文:

Decrypt file encrypted using openssl with aes-cbc-256

问题

package test;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.io.IOUtils;

public class OpenSSlDecryptor {
    private static final Charset ASCII = Charset.forName("ASCII");
    private static final int INDEX_KEY = 0;
    private static final int INDEX_IV = 1;
    private static final int ITERATIONS = 10000;

    private static final int SALT_OFFSET = 8;
    private static final int SALT_SIZE = 8;
    private static final int CIPHERTEXT_OFFSET = SALT_OFFSET + SALT_SIZE;

    private static final int KEY_SIZE_BITS = 256;

    public static byte[][] EVP_BytesToKey(final int key_len, final int iv_len, final MessageDigest md,
            final byte[] salt, final byte[] data, final int count) {
        // ... (EVP_BytesToKey implementation remains the same)
    }

    public static void main(final String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
        final String fileName = "test.txt.enc";

        final File f = new File(fileName);
        try {
            // ... (code for reading the encrypted file remains the same)

            final String random_bin_key = "test.key";
            final byte[] passwordKey = IOUtils.toByteArray(new FileInputStream(random_bin_key));

            // ... (code for extracting salt and encrypted data remains the same)

            // ... (code for specifying cipher and digest remains the same)

            // ... (code for creating key and IV remains the same)

            // --- initialize cipher instance and decrypt ---

            aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
            final byte[] decrypted = aesCBC.doFinal(encrypted);
            final String answer = new String(decrypted);
            System.out.println(answer);
        } catch (final BadPaddingException e) {
            System.out.println(e.getMessage());
        } catch (final IllegalBlockSizeException e) {
            System.out.println(e.getMessage());
        } catch (final GeneralSecurityException e) {
            System.out.println(e.getMessage());
        } catch (final IOException e) {
            System.out.println(e.getMessage());
        }
    }
}

Please note that the translation provided here includes only the code portions and removes any additional text or explanations.

英文:

I have encrypted a file using below commands

openssl rand 32 > test.key

openssl enc -aes-256-cbc -iter 10000 -pbkdf2 -salt -in test.txt -out test.txt.enc -pass file:test.key

Now i am trying to decrypt it using java. tring since last few days but no success.

Can anyone help ?

my code

package test;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.io.IOUtils;
public class OpenSSlDecryptor {
private static final Charset ASCII = Charset.forName("ASCII");
private static final int INDEX_KEY = 0;
private static final int INDEX_IV = 1;
private static final int ITERATIONS = 10000;
private static final int ARG_INDEX_FILENAME = 0;
private static final int ARG_INDEX_PASSWORD = 1;
private static final int SALT_OFFSET = 8;
private static final int SALT_SIZE = 8;
private static final int CIPHERTEXT_OFFSET = SALT_OFFSET + SALT_SIZE;
private static final int KEY_SIZE_BITS = 256;
/**
* Thanks go to Ola Bini for releasing this source on his blog.
* The source was obtained from <a href="http://olabini.com/blog/tag/evp_bytestokey/">here</a> .
*/
public static byte[][] EVP_BytesToKey(final int key_len, final int iv_len, final MessageDigest md,
final byte[] salt, final byte[] data, final int count) {
final byte[][] both = new byte[2][];
final byte[] key = new byte[key_len];
int key_ix = 0;
final byte[] iv = new byte[iv_len];
int iv_ix = 0;
both[0] = key;
both[1] = iv;
byte[] md_buf = null;
int nkey = key_len;
int niv = iv_len;
int i = 0;
if (data == null) {
return both;
}
int addmd = 0;
for (;;) {
md.reset();
if (addmd++ > 0) {
md.update(md_buf);
}
md.update(data);
if (null != salt) {
md.update(salt, 0, 8);
}
md_buf = md.digest();
for (i = 1; i < count; i++) {
md.reset();
md.update(md_buf);
md_buf = md.digest();
}
i = 0;
if (nkey > 0) {
for (;;) {
if (nkey == 0) {
break;
}
if (i == md_buf.length) {
break;
}
key[key_ix++] = md_buf[i];
nkey--;
i++;
}
}
if (niv > 0 && i != md_buf.length) {
for (;;) {
if (niv == 0) {
break;
}
if (i == md_buf.length) {
break;
}
iv[iv_ix++] = md_buf[i];
niv--;
i++;
}
}
if (nkey == 0 && niv == 0) {
break;
}
}
for (i = 0; i < md_buf.length; i++) {
md_buf[i] = 0;
}
return both;
}
public static void main(final String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
final String fileName = "test.txt.enc";
final File f = new File(fileName );
try {
// --- read base 64 encoded file ---
List<String> lines = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new FileReader(f))) {
//br returns as stream and convert it into a List
lines = br.lines().collect(Collectors.toList());
} catch (final IOException e) {
e.printStackTrace();
}
final StringBuilder sb = new StringBuilder();
for (final String line : lines) {
sb.append(line.trim());
}
final String random_bin_key = "test.key";
final byte[] passwordKey = IOUtils.toByteArray(new FileInputStream(random_bin_key));
// --- extract salt & encrypted ---
final byte[] headerSaltAndCipherText = sb.toString().getBytes();
// header is "Salted__", ASCII encoded, if salt is being used (the default)
final byte[] salt = Arrays.copyOfRange(
headerSaltAndCipherText, SALT_OFFSET, SALT_OFFSET + SALT_SIZE);
final byte[] encrypted = Arrays.copyOfRange(
headerSaltAndCipherText, CIPHERTEXT_OFFSET, headerSaltAndCipherText.length);
// --- specify cipher and digest for EVP_BytesToKey method ---
final Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
final MessageDigest md5 = MessageDigest.getInstance("MD5");
// --- create key and IV  ---
// the IV is useless, OpenSSL might as well have use zero's
final byte[][] keyAndIV = EVP_BytesToKey(
KEY_SIZE_BITS / Byte.SIZE,
aesCBC.getBlockSize(),
md5,
salt,
passwordKey,
ITERATIONS);
final SecretKeySpec key = new SecretKeySpec(keyAndIV[INDEX_KEY], "AES");
final IvParameterSpec iv = new IvParameterSpec(keyAndIV[INDEX_IV]);
// --- initialize cipher instance and decrypt ---
aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
final byte[] decrypted = aesCBC.doFinal(encrypted);
final String answer = new String(decrypted);
System.out.println(answer);
} catch (final BadPaddingException e) {
System.out.println(e.getMessage());
} catch (final IllegalBlockSizeException e) {
System.out.println(e.getMessage());
} catch (final GeneralSecurityException e) {
System.out.println(e.getMessage());
} catch (final IOException e) {
System.out.println(e.getMessage());
}
}

Error i am getting

Given final block not properly padded. Such issues can arise if a bad key is used during decryption.

I refereed following link

https://raymii.org/s/tutorials/Encrypt_and_decrypt_files_to_public_keys_via_the_OpenSSL_Command_Line.html

https://community.cloudera.com/t5/Support-Questions/How-do-I-decrypt-AES-256-CBC-data-in-HDF-if-it-was-encrypted/td-p/97961#

tried with

` final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
// strip off the salt and iv
final ByteBuffer buffer = ByteBuffer.wrap(encryptedText);
byte[] saltBytes = new byte[16];
buffer.get(saltBytes, 0, saltBytes.length);
saltBytes =  Arrays.copyOfRange(saltBytes, 8, 16);
final byte[] ivBytes1 = new byte[cipher.getBlockSize()];
buffer.get(ivBytes1, 0, ivBytes1.length);
final int length = buffer.capacity() - 16 - ivBytes1.length;
// length = length+ 16 -(length%16);
final byte[] encryptedTextBytes = new byte[length];
buffer.get(encryptedTextBytes);
// Deriving the key
final SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
final PBEKeySpec spec = new PBEKeySpec(new String(password).toCharArray(), saltBytes, 10000,
256);
final SecretKey secretKey = factory.generateSecret(spec);
final SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(ivBytes1));
byte[] decryptedTextBytes = null;
try {
decryptedTextBytes = cipher.doFinal(encryptedTextBytes);
} catch (final IllegalBlockSizeException e) {
e.printStackTrace();
} catch (final BadPaddingException e) {
e.printStackTrace();
}

Getting badpadding exception

tried with PBKDF2WithHmacSHA256 still getting the error

答案1

得分: 1

你有几个问题。其中最明显的问题是你试图从文件中读取IV,但是openssl enc在其默认的基于密码的模式下会从密码和盐派生出密钥和IV——即使使用PBKDF2。然而,无论是标准的(Sun/Oracle/OpenJDK)还是Java中的BouncyCastle提供者,在Java中实现的PBKDF2只派生密钥——就像在_PBES2_中使用的方式一样。

即使没有这个问题,你生成“密码”的方法,即随机字节,也不会奏效。PKCS5标准实际上定义了PBKDF2接受密码作为

> 一个长度任意的八位字节字符串,其作为文本字符串的解释是未指定的。然而,为了实现互操作性,建议应用遵循一些通用的文本编码规则。ASCII和UTF-8 [RFC3629]是两种可能性。(ASCII是UTF-8的子集。)

许多系统更加严谨地采用可互操作的编码,特别是Java(从一开始就设计成全球性的)。Java中的PBEKeySpec定义为包含_字符_——在Java中是UTF-16编码——在进行PBKDF2时会转换为UTF-8编码。相比之下,openssl是一个C程序,起源可以追溯到上世纪,当时C开始承认除了美国之外还存在其他国家,因此它只知道字节——这些字节可能是ASCII,也可能是其他单字节编码,比如EBCDIC,但可能根本不是字符,肯定不包括那些不适合一个字节的奇怪的外国字符。32个随机字节序列成为有效的UTF-8的概率非常低;我无法通过分析来计算,但我进行了1亿个随机值的测试,只有一个值能够与你的方案配合使用。(我本来想测试10亿次,但等得太累了。)

此外,由于密码应该是文本,openssl读取-pass file:作为一个文本文件,并将其视为字符串。这意味着如果任何随机字节是空字节或对应于换行符字符的字节,文件中其余的数据将被丢弃,并且用于密钥和IV派生的数据将被忽略。对于随机的32字节值,平均约有4次中就会发生一次,而且大约每20次中就会在文件的早期发生,从而使结果在密码学上变得脆弱且容易被破解。

这引出了一个问题:为什么要使用基于密码的加密?如果你的“密钥”是来自良好安全RNG的32字节——openssl rand就是这样——你不需要加强它,它已经作为密钥有效。你可以使用openssl enc来进行基于密钥的加密,而不是基于密码的加密,它更高效、更安全,而且在Java中更容易实现——这是一个巨大的优势。_如果_你为每次加密使用一个新的随机密钥,你甚至不需要使用真正的IV,你可以只使用一个零IV,就像我在下面所做的那样。但是如果你要_重用_密钥,你需要为每次加密使用唯一且不可预测的——通常是随机的——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,

<details>
<summary>英文:</summary>

You have several problems. The most obvious is that you are trying to read the IV from the file, but `openssl enc` in its default password-based mode derives both key and IV from password and salt -- even when using PBKDF2. However, both the standard (Sun/Oracle/OpenJDK) and BouncyCastle providers in Java implement PBKDF2 to derive only a key -- the way it is used in _PBES2_. 

Even without that, your method of generating the &#39;password&#39; as random bytes wouldn&#39;t work either. The [PKCS5 standard](https://www.rfc-editor.org/rfc/rfc8018#section-3) actually defines PBKDF2 to take the password as 

&gt; an octet
   string of arbitrary length whose interpretation as a text string is
   unspecified.  In the interest of interoperability, however, it is
   recommended that applications follow some common text encoding rules.
   ASCII and UTF-8 [RFC3629] are two possibilities.  (ASCII is a subset
   of UTF-8.)

Many systems take interoperable encoding more seriously, and Java in particular (which was designed from its inception to be worldwide) defines `PBEKeySpec` to contain _characters_ -- `char[]` in Java is UTF-16 -- which are encoded as UTF-8 when doing PBKDF2. In contrast `openssl` is a C program dating from before the turn of the century when C started admitting the existence of countries other than the USA, so it only knows about bytes -- bytes which _might_ be ASCII, or some other single-byte code like EBCDIC, but maybe not characters at all and certainly not any of those weird foreign characters that don&#39;t fit in a byte. The probability of a sequence of 32 random bytes being valid UTF-8 is very low; it&#39;s too much work for me to figure analytically, but I ran a test of 100 million random values and got only one that would work with your scheme. (I was going to test a billion but got tired of waiting.) 

Plus, since a password is supposed to be text, `openssl` reads `-pass file:` as a _text_ file and treats it as a string. That means if any of the random bytes is a null byte or a byte corresponding to the newline character, the remainder of the data in the file is discarded and ignored for the key-and-IV derivation. This will occur on average about 1 in 4 times for random 32-byte values, and about 1 in 20 times it will occur early enough in the file to make the result cryptographically weak and breakable. 

Which raises the point: why are you using password-based encryption at all? If your &#39;key&#39; is 32 bytes from a decent secure RNG -- which `openssl rand` is -- you don&#39;t need to strengthen it, it&#39;s already valid as a key. You can use `openssl enc` to do **key-based** encryption, not password-based, and it&#39;s more efficient, more secure, AND much easier in Java -- a massive win. _IF_ you use a new, random key for each encryption you don&#39;t even have to use a real IV, you can just use a zero IV as I did below. But if you are going to _reuse_ the/any key, you need to use a unique and unpredictable -- normally random -- IV for each encryption, and convey it with the data, perhaps by just putting it at the front.

So anyway, here&#39;s a fairly simple Java program which can handle either case: the openssl form of pdbkf2 with a &#39;password&#39; that isn&#39;t actually a password and isn&#39;t UTF-8, or the more sensible key-based form (but for this demo with zero 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(&quot;P&quot;) ){
// possibly truncate &#39;password&#39; in fil2
int n = 0; for( ; n &lt; fil2.length; n++ ) if( fil2[n]==0 || fil2[n]==&#39;\n&#39; ) break;
if( n &lt; fil2.length ) fil2 = Arrays.copyOf(fil2, n);
// extract salt and derive ...
byte[] salt = Arrays.copyOfRange(file, 8, 16);
byte[] derive = PBKDF2 (&quot;HmacSHA256&quot;, fil2, salt, 10000, 48);
// ... key is first 32, IV is last 16
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(derive,0,32,&quot;AES&quot;), 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,&quot;AES&quot;), 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&gt;0 ){
for( int o = saltcnt.length; --o&gt;=saltcnt.length-4; ) if( ++saltcnt[o] != 0 ) break; 
byte[] u = saltcnt, x = new byte[mac.getMacLength()];
for( int i = 1; i &lt;= iter; i++ ){
u = mac.doFinal (u); 
for( int o = 0; o &lt; 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 &lt; 1000000000; i++ ){
r.nextBytes(t); 
if( Arrays.equals(new String (t, StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8), t) ) 
System.out.println(i+&quot; &quot;+Arrays.toString(t));
if( i % 1000000 == 999999 ) System.out.println (i);
}
}

}

and a demo:

$ 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
$


</details>

huangapple
  • 本文由 发表于 2020年5月5日 20:35:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/61613286.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定