如何在 java 中使用 CBC(使用 32 位 IV 和密钥)实现 AES 128 位?
how to implement AES 128bit with CBC (with 32bit IV and key)in java?
我正在尝试在 java 中实现 AES 256 位 CBC 算法。我想做这样的东西。 Click here
下面是示例图片运行。
我在多个 SO 线程中使用以下程序,以下是我在 encrypt/decrypt 中使用的代码。根据@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 线程。
- Java 256-bit AES Password-Based Encryption
- decrypting data with AES/CBC/PKCS5Padding using blackberry
- Java 256-bit AES Password-Based Encryption
IV 的大小应与 AES 的块大小相同,即 128 位(16 字节)。
您在加密中定义的 IV 实际上不会传递给密码 .. 因此密码会为您生成一个(即 16 字节)。
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));
}
}
}
好的,现在我们有一个特定的目标: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
我正在尝试在 java 中实现 AES 256 位 CBC 算法。我想做这样的东西。 Click here
下面是示例图片运行。
我在多个 SO 线程中使用以下程序,以下是我在 encrypt/decrypt 中使用的代码。根据@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 线程。
- Java 256-bit AES Password-Based Encryption
- decrypting data with AES/CBC/PKCS5Padding using blackberry
- Java 256-bit AES Password-Based Encryption
IV 的大小应与 AES 的块大小相同,即 128 位(16 字节)。
您在加密中定义的 IV 实际上不会传递给密码 .. 因此密码会为您生成一个(即 16 字节)。
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));
}
}
}
好的,现在我们有一个特定的目标: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