使用带有 aes-cbc-256 的 openssl 解密文件 [英] Decrypt file encrypted using openssl with aes-cbc-256

查看:41
本文介绍了使用带有 aes-cbc-256 的 openssl 解密文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用以下命令加密了一个文件

I have encrypted a file using below commands

openssl rand 32 > test.key

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

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

现在我正在尝试使用 java 解密它.前几天一直在尝试,但没有成功.

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

有人可以帮忙吗?

我的代码

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

我遇到的错误

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

我参考了以下链接

https://raymii.org/s/tutorials/Encrypt_and_decrypt_files_to_public_keys_via_the>Line

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#

尝试过

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

获取 badpadding 异常

Getting badpadding exception

尝试使用 PBKDF2WithHmacSHA256 仍然得到错误

tried with PBKDF2WithHmacSHA256 still getting the error

推荐答案

您有几个问题.最明显的是,您正在尝试从文件中读取 IV,但是 openssl enc 在其默认的基于密码的模式下从密码和 salt 派生密钥和 IV——即使在使用 PBKDF2 时也是如此.然而,Java 中的标准 (Sun/Oracle/OpenJDK) 和 BouncyCastle 提供程序都实现了 PBKDF2 以仅派生一个密钥——它在 PBES2 中的使用方式.

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.

即使没有那个,您将密码"生成为随机字节的方法也不起作用.PKCS5 标准实际上定义了 PBKDF2 将密码作为

Even without that, your method of generating the 'password' as random bytes wouldn't work either. The PKCS5 standard actually defines PBKDF2 to take the password as

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

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.)

许多系统更重视互操作编码,尤其是 Java(从一开始就设计为面向全球)定义 PBEKeySpec 以包含 字符 -- Java 中的 char[] 是 UTF-16——在执行 PBKDF2 时编码为 UTF-8.相比之下,openssl 是一个可追溯到世纪之交之前的 C 程序,当时 C 开始承认美国以外的国家的存在,因此它只知道字节——可能 是 ASCII,或其他一些单字节代码,如 EBCDIC,但可能根本不是字符,当然也不是任何不适合一个字节的奇怪外来字符.32 个随机字节序列是有效 UTF-8 的概率非常低;分析数字对我来说工作量太大,但我对 1 亿个随机值进行了测试,结果只有一个适合您的方案.(我打算测试 10 亿,但厌倦了等待.)

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't fit in a byte. The probability of a sequence of 32 random bytes being valid UTF-8 is very low; it'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.)

另外,由于密码应该是文本,openssl 读取 -pass file: 作为 text 文件并将其视为细绳.这意味着,如果任何随机字节是空字节或对应于换行符的字节,则文件中的其余数据将被丢弃并在 key-and-IV 派生时被忽略.对于随机的 32 字节值,这种情况平均大约有四分之一发生,大约 20 分之一会在文件中足够早地发生,从而使结果在密码学上变得脆弱和易碎.

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.

这就提出了一个问题:你为什么要使用基于密码的加密?如果您的密钥"是来自一个体面的安全 RNG 的 32 个字节——openssl rand 是——您不需要加强它,它已经作为密钥有效.您可以使用 openssl enc 进行基于密钥的加密,而不是基于密码的加密,而且在 Java 中它更高效、更安全且更容易——这是一个巨大的胜利.如果您为每个加密使用一个新的随机密钥,您甚至不必使用真正的 IV,您可以像我在下面所做的那样使用零 IV.但是,如果您要重用该/任何密钥,则需要为每个加密使用唯一且不可预测的——通常是随机的——IV,并将其与数据一起传送,也许只是将其放入在前面.

Which raises the point: why are you using password-based encryption at all? If your 'key' is 32 bytes from a decent secure RNG -- which openssl rand is -- you don't need to strengthen it, it's already valid as a key. You can use openssl enc to do key-based encryption, not password-based, and it'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'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.

无论如何,这里有一个相当简单的 Java 程序,它可以处理任何一种情况:pdbkf2 的 openssl 形式,其密码"实际上不是密码也不是 UTF-8,或者更明智的基于密钥的形式(但对于这个具有零 IV 的演示):

So anyway, here's a fairly simple Java program which can handle either case: the openssl form of pdbkf2 with a 'password' that isn't actually a password and isn'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("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
$

这篇关于使用带有 aes-cbc-256 的 openssl 解密文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆