简体   繁体   English

如何在Java中用CBC(带有32bit IV和密钥)实现AES 128bit?

[英]how to implement AES 128bit with CBC (with 32bit IV and key)in java?

I am trying to implement AES 256 bit CBC algorithm in java. 我正在尝试在Java中实现AES 256位CBC算法。 I want to make something like this. 我想做这样的事情。 Click here 点击这里

Below is the picture for sample run. 下面是示例运行的图片。 样品运行

I am using below program from multiple SO threads , following is the code I am using to encrypt/decrypt. 我正在使用来自多个SO线程的以下程序,以下是我用于加密/解密的代码。 Updated the code as per @dave_thompson suggestion but still same error for length of IV. 根据@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);
    }
}

Problem with current code shows me the following error, but if I change IV back to 16bit it works. 当前代码的问题向我显示了以下错误,但是如果我将IV更改回16位,它将起作用。

Following are the SO threads I am referring to. 以下是我所指的SO线程。

The IV size for CBC mode encryption is the same as the block size. 对于CBC模式加密的IV尺寸相同的块大小。 AES is a subset of Rijndael with certain restrictions. AES是Rijndael的子集,具有某些限制。 One of these restrictions is a block size that's always 128 bits. 这些限制之一是块大小始终为 128位。

Rijndael is not standardized when used outside AES parameters. 在AES参数之外使用Rijndael时未标准化。 This means it is often not implemented. 这意味着它通常不被实施。 Oracle's Java does not implement Rijndael outside AES boundaries. Oracle的Java不在AES边界之外实现Rijndael。 Bouncy Castle does, but it doesn't expose higher block sizes to the outside . 有弹性的城堡可以,但不会将较大的块暴露在外面

So the only thing you can do is to use Rijndael from the - so called - lightweight API of the Bouncy Castle provider. 因此,您唯一可以做的就是使用Bouncy Castle提供程序的轻量级API的Rijndael。 Basically you'd be calling the underlying implementation of the Bouncy Castle provider directly through Bouncy's proprietary interface. 基本上,您将直接通过Bouncy的专有接口调用Bouncy Castle提供程序的基础实现。

Warning : below code uses a static (zeroed) key and IV. 警告 :以下代码使用静态(归零)键和IV。 It's just used to demonstrate the Rijndael cipher with a block size of 256 bits. 它仅用于演示具有256位块大小的Rijndael密码。 It does not follow (cryptographic) best practice. 它没有遵循(加密)最佳实践。

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

The IV should be the same size as the block size of AES which is 128 bits (16 bytes). IV的大小应与AES的块大小相同,即128位(16字节)。

The IV you define in your encrypt you actually don't pass to the cipher .. and thus the cipher generates one for you (that is 16 bytes). 您在加密中定义的IV实际上不会传递给密码..因此,密码会为您生成一个(即16字节)。

Okay, now we have a specific target: AES-CBC (although your example data is only one block so CBC doesn't really matter) with 128-bit key expressed in hex, (128-bit) IV all zero, and zero padding (omitted if exact block). 好的,现在我们有一个特定的目标: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
}

which produces the result 产生结果

encrypt hex->e6b426aca323815fd6583cbcc4293c8d
decrypt chars->Hello world

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM