2016-05-16 95 views
-1

我想在java中实现AES 256位CBC算法。我想做出这样的事情。 Click here如何在Java中使用CBC(带有32位IV和密钥)实现AES 128位?

以下是运行示例图片。 Sample Run

我使用下面的程序从多个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线程。

+0

请不要混淆比特和字节。如果CBC模式有这种情况,那么16位IV将基本无用。 –

+0

将16字节的OMG设置为0,或十六进制* 16 *字符的字符。啧。 –

+0

(调整)该网站输入的十六进制密钥和IV,所以你的例子有128位密钥不是256位,128位(零),因为IV不是256.它直接使用密钥,而不是做PBKDF2或任何其他密钥派生,并且它正在执行零填充而不是PKCS#5填充。简而言之,如果你想要做那些网站正在做的事情,那么你的代码中几乎所有的东西都是错误的,如果你想做一些与你的代码中的东西接近的网站是完全错误的。 ** PICK ONE。**但是注意零填充是不常见的,通常是一个坏主意。 –

回答

1

好了,现在我们有一个具体的目标: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 
1

的IV应的尺寸的块大小AES其是128位(16个字节)相同。

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

+0

感谢@Ebbe的回复,我如何让我的算法能够使用32字节四。如何使它与256字节一起工作。有任何想法吗? –

+0

AES总是使用16个字节的IV ..这与密钥大小 –

+0

无关,正如您在我的问题中所看到的,客户端要求使用32位iv。我如何根据这种行为改变你能否提出一些改变。如果你能提供一些指导,这将是一件好事。 –

2

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

Rijndael在AES参数外部使用时未标准化。这意味着它通常没有实现。 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)); 
     } 
    } 
} 
+0

对不起,懒得处理异常。确保区分程序性异常和输入相关的异常。 –

+0

(固定)这是Rijndael的一个很好的答案,只不过你把钥匙和IV留在了常量 - 全零的位置,这不是很安全,但我怀疑这里是个问题。任何知道非AES分析Rijndael的人甚至可能知道指定'Rijndael'而不是'AES'。 –

+0

试图沿着这个工作。 +1仍然 –

相关问题