【问题标题】:Given Final Block not properly padded while AES decryption给定最终块在 AES 解密时未正确填充
【发布时间】:2015-03-31 13:23:32
【问题描述】:

首先,我将说明我的主要目标是什么。我将使用 AES 加密客户端的一些内容,然后使用 RSA 公钥加密重要的 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 测试了它的加密、盐、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)

您可以看到在 Javascript 端是 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) 发送到服务器并立即解密数据,而无需再次执行生成密钥的过程。我对这个..是对的吗?我也试过这个,我得到“无效的 AES 密钥长度异常”。为了维护安全,我将在客户端使用 RSA 公钥加密密钥和 Iv。使用非对称实现对称的原因之一是由于 RSA 的待加密数据大小有限。但是如果我不能清除这个 BadPaddingException,我就不能实现它。

【问题讨论】:

  • 你可以把这一切都留给 HTTPS+TLS 反正它或多或少做同样的事情。
  • 是的,我知道,但这是我客户的需要。而且我还可以学习密码学,这是我第一次在密码学中编码。但我假设我选择了正确的加密方案。

标签: java encryption cryptography aes cryptojs


【解决方案1】:

由于您想使用 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);

【讨论】:

  • 所以我应该使用 var key = CryptoJS.lib.WordArray.random(128/8);在这两种情况下.. 如果是 128、128/8 还是 256、256/8?我还尝试通过 Key k = new Key(Base64.decodeBase64(data[2])); 在 java 中实例化 Key , (data[2] 是键值)但它告诉我不能实例化键,我将键作为 key.toString(Crypto.enc.Base64);从客户端检查它是否在服务器中工作..
  • 你不能实例化Key,因为它是一个接口。你应该使用SecretKeySpec
  • 只是有一个疑问.. 在发送到服务器之前使用 RSa 仅加密 AES 密钥是否足够?还是我应该考虑在发送到服务器之前使用 RSa 加密 AES“iv”和“key”数据?
  • 当然可以,但您不必加密 IV。你要做的是,让它随机。有时IV会直接附加到密文中,以使编程接口独立于它。
  • 我想你可以更好地解释我这个stackoverflow.com/questions/28285120/…
【解决方案2】:

感谢 Artjom B。

解决方案:工作代码

Javascript/客户端代码

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

【讨论】:

    猜你喜欢
    • 2015-05-22
    • 2015-10-07
    • 1970-01-01
    • 1970-01-01
    • 2017-03-17
    • 2014-07-26
    • 2014-01-22
    • 2013-12-18
    • 1970-01-01
    相关资源
    最近更新 更多