【问题标题】:how to implement AES 128bit with CBC (with 32bit IV and key)in java?如何在 java 中使用 CBC(带有 32 位 IV 和密钥)实现 AES 128 位?
【发布时间】:2016-05-16 07:14:19
【问题描述】:

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

下面是示例运行的图片。

我正在使用来自多个 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 线程。

【问题讨论】:

  • 请不要混淆位和字节。如果 CBC 模式有这样的东西,16 位 IV 基本上是没用的。
  • OMG 将 16 个字节设置为 0,或十六个 对 十六进制字符。嘘。
  • (tweaked) 该网站正在以十六进制输入密钥和 IV,因此您的示例有 128 位密钥而不是 256,以及 128 位(零)作为 IV 而不是 256。它直接使用密钥,不执行 PBKDF2 或任何其他密钥派生,并且它执行零填充而不是 PKCS#5 填充。简而言之,如果您想做该网站正在做的事情,那么您的代码中的几乎所有内容都是错误的,并且如果您想做与您代码中的内容相近的事情,那么该网站就完全错误了。 选择一个。但请注意,零填充并不常见,通常是个坏主意。
  • @dave_thompson_085 非常感谢您让我意识到这一点。我需要让它像那个网站一样工作。那我该怎么做呢?对特定想法的任何推动都会有所帮助。

标签: java encryption cryptography aes


【解决方案1】:

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

在 AES 参数之外使用时,Rijndael 未标准化。这意味着它通常没有被实施。 Oracle 的 Java 没有在 AES 边界之外实现 Rijndael。充气城堡可以,但it doesn't expose higher block sizes to the outside.

因此,您唯一能做的就是使用来自 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));
        }
    }
}

【讨论】:

  • 抱歉,懒得处理异常了。确保区分编程异常和输入相关异常。
  • (已修复)这对 Rijndael 来说是一个很好的答案,除了您将密钥和 IV 设置为常量全零,这不是很安全,但我怀疑这是这里的问题;任何知道非 AES 配置的 Rijndael 的人甚至都可能知道指定“Rijndael”而不是“AES”。
  • @dave_thompson_085 有一些 API 使用 Rijndael 类来实现 AES 功能。为这些类使用更大的块大小和更大的密钥大小是一个简单的错误(例如,.NET 中的RijndaelManaged,当然还有 PHP 中的 mcrypt)。 mcrypt 在其原始 API 示例代码中甚至有 256 位块大小(但话又说回来,mcrypt 没有严重缺乏的地方很少)。
【解决方案2】:

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

static void SO37248569() throws Exception {
    // fixed key; in real use key should be securely provided or generated
    String keyhex = "6c616d70736865657031323334353637";
    // crude way of converting hex to bytes, better ways are possible
    byte[] key  = new BigInteger (keyhex,16).toByteArray();
    if( key.length > 16 ) key = Arrays.copyOfRange (key, 1, key.length); // maybe signed
    if( key.length != 16 ) throw new Exception ("key length wrong!");
    // all-zero IV, only secure if key is unique every time
    byte[] IV = new byte[16];
    //
    // fixed plaintext for example, in real use obtain as needed
    byte[] plainbytes = "Hello world".getBytes();
    // note: for ASCII-only data the Java default encoding is okay;
    // if real data can or could contain other chars, specify a 
    // suitable encoding; "UTF-8" is good for most text-y data 
    //
    // ENCRYPT: we need to add zero padding ourself since JCE doesn't do that
    // Java makes this easy because arrays are initialized to all-zeros
    if( plainbytes.length %16 !=0 ) 
        plainbytes = Arrays.copyOf (plainbytes, (plainbytes.length /16 +1)*16);
    //
    Cipher aes = Cipher.getInstance ("AES/CBC/NoPadding");
    aes.init (Cipher.ENCRYPT_MODE, new SecretKeySpec (key, "AES"), new IvParameterSpec (IV));
    byte[] cipherbytes = aes.doFinal (plainbytes);
    // crude way of converting bytes to hex, again better possible
    System.out.println ("encrypt hex->" + new BigInteger (1,cipherbytes).toString(16));
    // alternatively just write to a file and let other tools handle
    //
    // DECRYPT: assuming bytes read from file, or already converted from hex
    //same as above: Cipher aes = Cipher.getInstance ("AES/CBC/NoPadding");
    aes.init (Cipher.DECRYPT_MODE, new SecretKeySpec (key, "AES"), new IvParameterSpec (IV));
    byte[] resultbytes = aes.doFinal (cipherbytes);
    //
    // now we need to remove the zero padding, which is ambiguous 
    // this will damage data that actually has trailing zero bytes
    int i; for( i = resultbytes.length; --i>=0 && resultbytes[i]==0; ){}
    resultbytes = Arrays.copyOf (resultbytes, i+1);
    //
    // for example just display, for real use adapt as desired
    System.out.println ("decrypt chars->" + new String (resultbytes));
    // see above about encoding
}

产生结果

encrypt hex->e6b426aca323815fd6583cbcc4293c8d
decrypt chars->Hello world

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 感谢@Ebbe 的回复,我怎样才能使我的算法适用于 32byte IV。如何使它与 256 字节一起工作。有什么想法吗?
    • AES 总是使用 16 字节的 IV .. 这与密钥大小无关
    • 正如您在我的问题中看到的,客户要求使用 32 位 iv。我该如何根据这种行为进行更改,您能否提出一些更改建议。如果您能对此提供一些指导,那就太好了。
    • 一些链接会很有用。
    • @KaranMer 32 bits AES 的 IV 是不可能的,32 bytes 也是不可能的。另外,正如您所做的那样,使用值为 48 的字节虽然合法,但会很奇怪。在加密(以及其他计算领域)中,以十六进制写入二进制值是很常见的,并且 32 十六进制数字零(有时称为 nybbles)是 16 字节或 128 位,这对于 AES 来说是完全正确的;我敢打赌这就是您的客户想要的,而您只是误解了。由于 IV 是 byte 的数组,而 Java 将原始数组默认为零,因此您可以使用 new byte[16]。
    猜你喜欢
    • 1970-01-01
    • 2019-09-30
    • 2017-03-27
    • 1970-01-01
    • 1970-01-01
    • 2019-08-17
    • 2020-04-15
    • 2022-04-29
    • 2016-12-05
    相关资源
    最近更新 更多