如何在java中用CBC(32位IV和密钥)实现AES 128位?

如何在java中用CBC(32位IV和密钥)实现AES 128位?,java,encryption,cryptography,aes,Java,Encryption,Cryptography,Aes,我正在尝试用java实现AES 256位CBC算法。我想做这样的东西 下面是示例运行的图片。 我正在使用来自多个SO线程的以下程序,下面是我用来加密/解密的代码。按照@dave_thompson的建议更新了代码,但对于IV的长度仍然存在相同的错误 import java.security.AlgorithmParameters; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; i

我正在尝试用java实现AES 256位CBC算法。我想做这样的东西

下面是示例运行的图片。

我正在使用来自多个SO线程的以下程序,下面是我用来加密/解密的代码。按照@dave_thompson的建议更新了代码,但对于IV的长度仍然存在相同的错误

import java.security.AlgorithmParameters;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Scanner;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

public class EncryptionDecryption {

    private static String salt;
    private static int iterations = 65536  ;
    private static int keySize = 256;
    private static byte[] ivBytes = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,};
    private static SecretKey secretKey;

    public static void main(String []args) throws Exception {
        Scanner in = new Scanner(System.in);
        salt = getSalt();
        String s = in.nextLine();
        char[] message = s.toCharArray();

        System.out.println("Message: " + String.valueOf(message));
        System.out.println("Encrypted: " + encrypt(message));
        System.out.println("Decrypted: " + decrypt(encrypt(message).toCharArray()));
    }

    public static String encrypt(char[] plaintext) throws Exception {
        byte[] saltBytes = salt.getBytes();

        SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        PBEKeySpec spec = new PBEKeySpec(plaintext, saltBytes, iterations, keySize);
        secretKey = skf.generateSecret(spec);
        SecretKeySpec secretSpec = new SecretKeySpec(secretKey.getEncoded(), "AES");

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretSpec);
        AlgorithmParameters params = cipher.getParameters();
        byte[] encryptedTextBytes = cipher.doFinal(String.valueOf(plaintext).getBytes("UTF-8"));

        return DatatypeConverter.printBase64Binary(encryptedTextBytes);
    }

    public static String decrypt(char[] encryptedText) throws Exception {

        System.out.println(encryptedText);

        byte[] encryptedTextBytes = DatatypeConverter.parseBase64Binary(new String(encryptedText));
        SecretKeySpec secretSpec = new SecretKeySpec(secretKey.getEncoded(), "AES");

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretSpec, new IvParameterSpec(ivBytes));

        byte[] decryptedTextBytes = null;

        try {
            decryptedTextBytes = cipher.doFinal(encryptedTextBytes);
        }   catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        }   catch (BadPaddingException e) {
            e.printStackTrace();
        }

        return new String(decryptedTextBytes);

    }

    public static String getSalt() throws Exception {

        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        byte[] salt = new byte[20];
        sr.nextBytes(salt);
        return new String(salt);
    }
}
当前代码的问题向我显示了以下错误,但如果我将IV改回16位,它就会工作

以下是我所指的SO线程


IV的大小应与AES的块大小相同,即128位(16字节)


在加密中定义的IV实际上不会传递给密码。。因此,密码会为您生成一个(即16字节)。

CBC模式加密的IV大小与块大小相同。AES是Rijndael的一个子集,具有某些限制。这些限制之一是块大小始终为128位

Rijndael在AES参数之外使用时未标准化。这意味着它通常没有实现。Oracle的Java不在AES边界之外实现Rijndael。蹦蹦跳跳的城堡有,但是

因此,您唯一能做的就是使用Bouncy Castle提供程序的所谓轻量级API中的Rijndael。基本上,您可以通过Bouncy的专有接口直接调用Bouncy Castle提供程序的底层实现

警告:下面的代码使用静态(归零)密钥和IV。它仅用于演示块大小为256位的Rijndael密码。它不遵循(加密)最佳实践

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;

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.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.RijndaelEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;

public class RijndaelTestJava {

    private static final boolean FOR_ENCRYPTION = true;

    public static void main(String[] args) throws Exception {
        rijndael256BouncyLW();
        rijndael256BouncyProvider();
    }

    private static void rijndael256BouncyLW() throws InvalidCipherTextException {
        {
            RijndaelEngine rijndael256 = new RijndaelEngine(256);
            BufferedBlockCipher rijndael256CBC =
                    new BufferedBlockCipher(
                            new CBCBlockCipher(rijndael256));
            KeyParameter key = new KeyParameter(new byte[256 / Byte.SIZE]);
            rijndael256CBC.init(FOR_ENCRYPTION, new ParametersWithIV(key,
                    new byte[256 / Byte.SIZE]));
            byte[] in = new byte[64]; // two blocks
            byte[] out = new byte[64]; // two blocks
            int off = rijndael256CBC.processBytes(in, 0, in.length, out, 0);
            off += rijndael256CBC.doFinal(out, off);
            System.out.println(Hex.toHexString(out));
        }
    }

    private static void rijndael256BouncyProvider() throws NoSuchAlgorithmException,
            NoSuchPaddingException, InvalidKeyException,
            InvalidAlgorithmParameterException, IllegalBlockSizeException,
            BadPaddingException {
        {
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("Rijndael/CBC/PKCS7Padding");
            SecretKeySpec key = new SecretKeySpec(new byte[256 / Byte.SIZE],
                    "Rijndael");
            IvParameterSpec iv = new IvParameterSpec(new byte[256 / Byte.SIZE]);
            // throws an InvalidAlgorithmParameterException: IV must be 16 bytes long.
            cipher.init(Cipher.ENCRYPT_MODE, key, iv);
            byte[] out = cipher.doFinal("owlsteead"
                    .getBytes(StandardCharsets.US_ASCII));
            System.out.println(Hex.toHexString(out));
        }
    }
}

好的,现在我们有一个特定的目标:AES-CBC(尽管您的示例数据只有一个块,所以CBC并不重要),128位密钥以十六进制表示,(128位)IV全部为零,零填充(如果是精确的块,则省略)

结果是什么

encrypt hex->e6b426aca323815fd6583cbcc4293c8d
decrypt chars->Hello world

感谢回复@Ebbe,我如何使我的算法与32字节IV一起工作。如何使其与256字节一起工作。有什么想法吗?AES总是使用16字节的IV。。这与您在我的问题中看到的关键尺寸无关,客户要求使用32位iv。如何根据此行为进行更改?您能建议一些更改吗。如果您能对此提供一些指导,那就太好了。一些链接会很有用。@KaranMer 32位IV for AES是不可能的,32字节也是不可能的。另外,使用值为48的字节,就像你正在做的那样,虽然合法,但会很奇怪。在密码学(以及其他计算领域)中,以十六进制写入二进制值是很常见的,并且零的32个十六进制数字(有时称为Nybles)是16字节或128位,这对于AES来说完全正确;我敢打赌这就是你的客户想要的,而你只是误解了。由于IV是一个字节数组,Java将原始数组默认为零,因此您可以使用新字节[16]。请不要混淆位和字节。如果CBC-mode.OMG的16字节设置为0,或者16对十六进制字符,那么16位IV基本上是无用的。Sheesh.(调整)该网站正在以十六进制输入密钥和IV,因此您的示例具有128位密钥而不是256,128位(零)作为IV而不是256。它直接使用密钥,不进行PBKDF2或任何其他密钥派生,它进行零填充而不是PKCS#5填充。简言之,如果你想做网站正在做的事情,那么代码中的几乎所有内容都是错误的,如果你想做一些接近代码中内容的事情,那么网站是完全错误的。挑一个。但请注意,零填充并不常见,而且通常是个坏主意。@dave_thompson_085非常感谢您让我意识到这一点。我需要让它像那个网站一样工作。那我该怎么做呢?任何对特定想法的推动都会有帮助。对不起,太懒了,无法处理异常。确保区分编程异常和与输入相关的异常。(已修复)这是Rijndael的一个很好的答案,只是您将密钥和IV保持为常量全零,这不是很安全,但我怀疑这是问题所在;任何知道非AES分析Rijndael是可能的人都知道指定“Rijndael”而不是“AES”。@dave_thompson_085有一些API使用Rijndael类实现AES功能。对于那些类(例如.NET中的
RijndaelManaged
,当然还有PHP中的mcrypt),使用更大的块大小和更大的键大小是一个很容易的错误。mcrypt在其原始API示例代码中甚至有256位的块大小(但同样,很少有mcrypt不严重缺乏的地方)。
encrypt hex->e6b426aca323815fd6583cbcc4293c8d
decrypt chars->Hello world