给定最终块在 AES 解密时未正确填充

Given Final Block not properly padded while AES decryption

首先,我要说明我的主要目标是什么。我将在客户端使用 AES 加密一些内容,然后使用 RSA public 密钥加密重要的 AES 规范,并将 AES 加密数据和 RSA 加密 AES 规范发送到服务器。因此,在服务器上,我将使用 RSA 私钥解密 AES 密钥规范,然后使用这些 AES 规范,我将解密 AES 加密数据。我已经通过测试加密和解密成功地使 RSA 部分工作。在实现 RSa 之前,我必须让这个 AES 艺术工作。

对于客户端,我使用的是 crypto-js

<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/pbkdf2.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/components/enc-base64-min.js"></script>
<script type="text/javascript" src="jquery-1.7.1.js"></script>
<script type="text/javascript">

    $("#submit").click(function() {
        var salt = CryptoJS.lib.WordArray.random(16);
        var iv = CryptoJS.lib.WordArray.random(16);
        var pass = CryptoJS.lib.WordArray.random(16);
        var message = "Test Message for encryption";
        var key128Bits = CryptoJS.PBKDF2(pass, salt, { keySize: 128 }); 
        var key128Bits10Iterations = CryptoJS.PBKDF2(pass, salt, { keySize: 128, iterations: 10 });
        var encrypted = CryptoJS.AES.encrypt(message, key128Bits10Iterations, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7  });
        var cipherData = encrypted.toString()+":"+salt.toString()+":"+iv.toString()+":"+pass.toString();
        console.log(cipherData);

        $.ajax({
            url: 'encryption',
            type: 'POST',
            data: {
                cipherData: cipherData
            },
            success: function(data) {
                console.log(data);
            },
            failure: function(data) {

            }
        });
    });

</script>

这是我在服务器端使用的代码

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String encryptedData = request.getParameter("cipherData");
    String data[] = encryptedData.split(":");

    String encrypted = data[0];     
    String salt = data[1];
    String iv = data[2];
    String password = data[3];

    byte[] saltBytes = hexStringToByteArray(salt);
    byte[] ivBytes = hexStringToByteArray(iv);
    IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);        
    SecretKeySpec sKey = null;
    try {
        sKey = (SecretKeySpec) generateKeyFromPassword(password, saltBytes);
    } catch (GeneralSecurityException e) {
        e.printStackTrace();
    }
    try {
        System.out.println( decrypt( encrypted , sKey ,ivParameterSpec));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public static SecretKey generateKeyFromPassword(String password, byte[] saltBytes) throws GeneralSecurityException {

    KeySpec keySpec = new PBEKeySpec(password.toCharArray(), saltBytes, 10, 128);
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    SecretKey secretKey = keyFactory.generateSecret(keySpec);

    return new SecretKeySpec(secretKey.getEncoded(), "AES");
}

public static byte[] hexStringToByteArray(String s) {

    int len = s.length();
    byte[] data = new byte[len / 2];

    for (int i = 0; i < len; i += 2) {
        data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                + Character.digit(s.charAt(i+1), 16));
    }

    return data;

}

public static String decrypt(String encryptedData, SecretKeySpec sKey, IvParameterSpec ivParameterSpec) throws Exception { 

    Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
    c.init(Cipher.DECRYPT_MODE, sKey, ivParameterSpec);
    byte[] decordedValue = Base64.decodeBase64(encryptedData);
    byte[] decValue = c.doFinal(decordedValue);
    String decryptedValue = new String(decValue);

    return decryptedValue;
}

首先,我必须确保服务器收到的数据与我发送的数据相同。所以我通过 sysout 测试了它的加密、salt、iv 和密码。它收到了相同的数据。但是我在第

行遇到了异常
byte[] decValue = c.doFinal(decordedValue);

javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:966)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
at javax.crypto.Cipher.doFinal(Cipher.java:2121)
at com.Encryption.decrypt(Encryption.java:95)
at com.Encryption.doPost(Encryption.java:60)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:644)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:503)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:136)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:526)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1078)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:655)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:222)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1566)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1523)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)

你可以看到在 Java 脚本端,它是 CryptoJS.pad.Pkcs7 而在服务器端它是 AES/CBC/PKCS5Padding ,我已经做了一些搜索,发现两者都是完全相同的。我既不能将其更改为 CryptoJS.pad.Pkcs5 也不能将 AES/CBC/PKCS7Padding 更改为 Crypto-js 库和 Java 内置库都没有定义。

我也有以下想法。在 javascript 中,我使用随机盐并通过生成 128 位密钥。使用相同的 salt 和 pass,我通过定义适当的迭代计数和密钥大小在 Java 中生成相同的密钥。为什么我必须通过再次生成相同的密钥来延长 Java 中的过程?我可以简单地将密钥 (encrypted.key)、encrytedData(encrypted.toString()) 和 Iv (encrypted.iv) 发送到服务器并立即解密数据,而无需经过生成再次关键。我对这个..?我也试过了,结果是 "Invalid AES key length exception"。为了维护安全,我将在客户端使用 RSA public 密钥对密钥和 Iv 进行加密。使用非对称实现对称的原因之一是由于 RSA 的加密数据大小有限。但是如果我不能清除这个 BadPaddingException,我就不能实现它。

既然你想使用 RSA 并且已经实现了它,就没有必要使用密码推导。创建一个随机密钥和随机 iv:

var key = CryptoJS.lib.WordArray.random(16); // 128bit
var iv = CryptoJS.lib.WordArray.random(16);  // 128bit
var encrypted = CryptoJS.AES.encrypt(message, key, { iv: iv }); // CBC/PKCS#7 is default

然后你发送iv.toString(Crypto.enc.Base64)encrypted.ciphertext.toString(Crypto.enc.Base64)和"RSAencrypt(key)"到服务器,解码base64编码的iv和密文,解密RSA密文得到AES密钥并结合所有他们解密密文。


您原来的问题可能出在您使用的尺寸上。 CryptoJS 有一个内部表示,每个字由 4 个字节组成。这就是为什么您需要除以 32 以获得 128 位散列的原因:

var key128Bits = CryptoJS.PBKDF2(pass, salt, { keySize: 128/32 });

另一方面,WordArray 仅适用于字节,这就是为什么要除以 8:

var key = CryptoJS.lib.WordArray.random(128/8);

感谢 Artjom B.

解决方案:工作代码

Java脚本/客户端代码

<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/pbkdf2.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/components/enc-base64-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>

<script type="text/javascript">

    $("#submit").click(function() {

        var key = CryptoJS.lib.WordArray.random(16);
        var iv= CryptoJS.lib.WordArray.random(16);
        var message = "<username>user</username>";
        var encrypted = CryptoJS.AES.encrypt(message, key, { iv: iv });

        // If you want the decryption at client side, use the commented code

        /* var decrypted = CryptoJS.AES.decrypt(encrypted, key, { iv:iv });
        function hex2a(hexx) {
            var hex = hexx.toString();//force conversion
            var str = '';
            for (var i = 0; i < hex.length; i += 2)
                str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
            return str;
        }
        console.log("decrypted: "+hex2a(decrypted)); */

        var cipherData = iv.toString(CryptoJS.enc.Base64)+":"+encrypted.ciphertext.toString()+":"+key.toString(CryptoJS.enc.Base64);

        $.ajax({
            url: 'encryption',
            type: 'POST',
            data: {
                cipherData: cipherData
            },
            success: function(data) {
                console.log(data);
            },
            failure: function(data) {

            }
        });
    });

</script>

服务器/Java代码

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

// You have to download and add it into your project, others are Java Inbuilt Libraries
// Do not use sun.misc for Base64 functions
import org.apache.commons.codec.binary.Base64; 

public class BasicDecryption {

//I've done it in post method of servlet

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    String encryptedData = request.getParameter("cipherData");
    String data[] = encryptedData.split(":");
    String iv = data[0];
    byte[] encryptedByteData = hexStringToByteArray(data[1]);
    String keyString = data[2];

    IvParameterSpec iv = new IvParameterSpec(Base64.decodeBase64(iv);
    Key k = new SecretKeySpec(Base64.decodeBase64(keyString),"AES");

    try {
        System.out.println("Decrypted String:"+BasicDecryption.decrypt(Base64.encodeBase64String(encryptedByteData),k,iv));
    } catch (InvalidKeyException | NoSuchAlgorithmException
            | NoSuchPaddingException | IllegalBlockSizeException
            | BadPaddingException | InvalidAlgorithmParameterException e) {
        e.printStackTrace();
    }
}

public static final String decrypt(final String encrypted,final Key key, final IvParameterSpec iv) throws InvalidKeyException,
NoSuchAlgorithmException, NoSuchPaddingException,
IllegalBlockSizeException, BadPaddingException, IOException, InvalidAlgorithmParameterException {

      Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
      cipher.init(Cipher.DECRYPT_MODE, key,iv);
      byte[] raw = Base64.decodeBase64(encrypted);
      byte[] stringBytes = cipher.doFinal(raw);
      String clearText = new String(stringBytes, "UTF8");
      return clearText;
}

public static byte[] hexStringToByteArray(String s) {

    int len = s.length();
    byte[] data = new byte[len / 2];

    for (int i = 0; i < len; i += 2) {
        data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                + Character.digit(s.charAt(i+1), 16));
    }
    return data;
}
}