Java 中的 AES 256 加密和解密

AES 256 encrypt and Decrypt in Java

您好,我在尝试解密一个 java 的 Ogg 文件时遇到了一个问题,该文件是用 PHP

加密的

这是创建加密文件的函数

PHP crypt function

我将此代码与 java 一起使用,但生成的文件已损坏

public static String decrypt(String input, String key){
        byte[] bytes = Base64.getDecoder().decode(input);
        if(bytes.length < 17) {
            return null;
        }
        String result = null;
        //byte[] ivBytes = Arrays.copyOfRange(bytes, 0, 16);
        //byte[] contentBytes = Arrays.copyOfRange(bytes, 16, bytes.length);


        try {
            //Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            

            Key skey = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            // Set the appropriate size for mInitVec by Generating a New One
            AlgorithmParameters params = cipher.getParameters();
            byte[] mInitVec = params.getParameterSpec(IvParameterSpec.class).getIV();

            cipher.init(Cipher.DECRYPT_MODE, skey, new IvParameterSpec(mInitVec));
            byte[] decrypted = cipher.doFinal(bytes);
            
            String uploadedFileLocation = "C:\test3.ogg";
            
            FileOutputStream fs = new FileOutputStream(new File(uploadedFileLocation));
            fs.write(decrypted);
            fs.close();
            
            result ="decrypted";
         
        } catch (
                Exception ex
        ) {
            System.out.println(ex.toString());
        }
        return result;
    }

TLDR:主要是 IV

对于像 CBC 这样使用 IV(初始化向量,有时是随机数)的加密模式,解密必须使用与加密相同的 IV( 密钥)。此外,像 CBC 这样的块模式通常需要填充和取消填充数据,而 PHP 使用的 OpenSSL 使用 Java 也支持的 'standard' PKCS5/7 填充来实现,但是必须 select.

$ cat 71865785.php
<?php
  $data = "some test data";
  $cipher = "aes-256-cbc";
  $key = "reallyrealsekritreallyrealsekrit"; // for test only, real key should not be human-chosen
  $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($cipher));
  echo base64_encode($iv)."\n".openssl_encrypt($data,$cipher,$key,0,$iv)."\n";
?>
$ php 71865785.php
LGQXy7XF8DHBilN4Kb8Xrg==
DWI/F97O5e/NGD2czp2OUA==
$ cat SO71865785.java
//nopackage
import java.util.Base64;
import javax.crypto.*;
import javax.crypto.spec.*;

public class SO71865785 /* decrypt from PHP */ {
  public static void main (String[] args) throws Exception {
    byte[] key = "reallyrealsekritreallyrealsekrit".getBytes("ASCII"); // for test only
    byte[] iv = Base64.getDecoder().decode("LGQXy7XF8DHBilN4Kb8Xrg==");
    byte[] ctext = Base64.getDecoder().decode("DWI/F97O5e/NGD2czp2OUA==");

    Cipher ciph = Cipher.getInstance("AES/CBC/PKCS5Padding");
    ciph.init (Cipher.DECRYPT_MODE, new SecretKeySpec(key,"AES"), new IvParameterSpec(iv));
    byte[] plain = ciph.doFinal(ctext);
    System.out.println ("len="+plain.length+":"+new String(plain,"ASCII"));
    // if non-ASCII chars are present must use encoding to match encrypting system which is not known
  }
}
$ javac SO71865785.java
$ java -cp . SO71865785
len=14:some test data

对于实际应用程序,您当然不会使用硬编码数据(通常也不应该对密钥进行硬编码)以及将 PHP 应用程序生成的数据传递给 Java 应用程序各不相同(很多!)。一种 not-uncommon 方法是连接 IV 和密文(最好是 MAC 或 auth 标签)用于传输 and/or 存储,然后用代码将它们分开 Arrays.copyOfRange 你有评论,但对于这个演示,我只是对每个片段进行了 base64 编码(openssl_encrypt 默认情况下已经使用 base64)。

注意 AES-256 密钥必须正好是 32 个字节(并且应该是任意字节,而不是人类可识别的字符)。在 PHP/OpenSSL 中,如果您提供的少于要求,它会默默地填充零字节(又名 NUL),但 Java 不会;如果你在 Java 中有一个 too-short byte[] 你可以方便地 zero-pad 它与例如Arrays.copyOf(poorkey,32).