【问题标题】:AES BadPaddingException when decrypting解密时出现 AES BadPaddingException
【发布时间】:2020-03-23 18:52:42
【问题描述】:

所以我正在尝试制作一个 Web 套接字,该套接字发送将被编码为 base64 然后使用 AES 加密的数据,生成的字节数组将通过套接字和服务器之间的流发送。这工作正常,直到我尝试发出某个命令给我BadPaddingException。所有使用Cryptographer 类的代码都使用完全相同的类和相同的秘密。

websockets 对每个连接都有线程来读取和写入数据。所有这些线程都使用同一个 cyptographer。

在连接线程中解密结果时给我异常的函数在名为MessageSender的类中这样编写,解密sendFile()的结果时会发生异常强>:

package com.company.client.workers;

import com.company.security.Cryptographer;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.util.Base64;

public class MessageSender {
    private PrintWriter writer;
    private OutputStream outputStream;
    private Cryptographer cryptographer;

    public MessageSender(OutputStream outputStream) {
        this.outputStream = outputStream;
        this.writer = new PrintWriter(this.outputStream);
        cryptographer = new Cryptographer();
    }

    /**
     * Sends a message to the PrintWriter.
     * @param message to send.
     */
    public void send(String message) {
        try {
            String b64 = Base64.getEncoder().encodeToString(message.getBytes());
            byte[] bytes = cryptographer.getData(b64.getBytes(), 0);
            outputStream.write(bytes, 0, bytes.length);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Send a file over the server.
     * @param receiver to send to
     * @param path location of file
     */
    public void sendFile(String receiver, String path) {
        File file = new File(path);
        try {
            byte[] bytes = Files.readAllBytes(file.toPath());
            // The encrypted result of this will throw the exception once the server tries to decrypt it.
            String message = "SFC " + receiver + " " + bytes.length + " " + getFileName(path);
            send(message);
            outputStream.write(cryptographer.getData(bytes, 0), 0, bytes.length);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private String getFileName(String path) {
        String[] parts = path.split("/");
        return parts[parts.length-1];
    }
}

以下运行函数将从输入流(这是套接字)读取数据。它还将调用密码学家类来加密和解密数据,这是同一个类,也将在客户端使用一个单独的实例。在此异常的情况下,IsReceiving 仍设置为 false。这是Cryptographer 类:

package com.company.security;

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

public class Cryptographer {
private Key secretKey;

public Cryptographer() {
    byte[] secret = new byte[16]; // 128 bit is 16 bytes, and AES accepts 16 bytes, and a few others.
    byte[] secretBytes = "secret".getBytes();
    System.arraycopy(secretBytes, 0, secret, 0, secretBytes.length);
    secretKey = new SecretKeySpec(secret, "AES");
}

/**
 * Get data from either encryption and decryption.
 * @param data to encrypt or decrypt
 * @param mode 0 to encrypt en other numbers to decrypt
 * @return result data as byte array format.
 */
public byte[] getData(byte[] data, int mode) {
    Cipher c;

    try {
        c = Cipher.getInstance("AES");
    } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
        e.printStackTrace();
        return null;
    }

    try {
        if(mode == 0) { // 0 is encrypt mode.
            c.init(Cipher.ENCRYPT_MODE, secretKey);
        } else { // other numbers are decrypt mode.
            c.init(Cipher.DECRYPT_MODE, secretKey);
        }
    } catch (InvalidKeyException e) {
        e.printStackTrace();
        return null;
    }

    try {
        return c.doFinal(data);
    } catch (IllegalBlockSizeException | BadPaddingException e) {
        e.printStackTrace();
        return null;
    }
}

/**
 * Decode base64 byte array.
 * @param encoded encoded byte array
 * @return decoded string
 */
public String decodeBaseToString(byte[] encoded) {
    return new String(Base64.getDecoder().decode(encoded));
}

/**
 * Decode base64 byte array.
 * @param encoded encoded byte array
 * @return decoded byte array
 */
public byte[] decodeBaseToBytes(byte[] encoded) {
    return Base64.getDecoder().decode(encoded);
}

/**
 * Encode byte array to base64.
 * @param source array to encode
 * @return encoded base64 byte array
 */
public byte[] encodeBase(byte[] source) {
    return Base64.getEncoder().encode(source);
}

}

异常堆栈跟踪:

javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
    at java.base/com.sun.crypto.provider.CipherCore.unpad(CipherCore.java:975)
    at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1056)
    at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853)
    at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
    at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2208)
    at com.company.security.Cryptographer.getData(Cryptographer.java:48)
    at com.company.client.Reader.run(Reader.java:45)
    at java.base/java.lang.Thread.run(Thread.java:835)
Exception in thread "Thread-3" java.lang.NullPointerException
    at java.base/java.lang.String.<init>(String.java:623)
    at com.company.client.Reader.run(Reader.java:47)
    at java.base/java.lang.Thread.run(Thread.java:835)

加密适用于所有其他命令,并且当我不在sendFile() 中包含文件名时它也适用。我在这里做错了什么?

【问题讨论】:

    标签: java encryption aes


    【解决方案1】:

    明显的错误;

    1. 你在加密之前对数据库进行了base64编码,不需要这个。

    2. 您以二进制形式发送数据,which can cause errors。发送前将字节转换为base64,接收后解码。在加密中

       byte[] encrypted = cipher.doFinal(data.getBytes());
       return Base64.encodeBase64String(encrypted);
      

    在解密中

        byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));
    
    1. 用getData命名/实现加密/解密功能。使它们成为独立的功能。

    2. 使用Cipher.getInstance("AES"),您正在使用ECB mode 的操作。这是不安全的泄漏模式,请参阅 Wikipedia's Penguin。

    3. 始终明确定义您的操作模式和填充,例如

       Cipher.getInstance("AES/CBC/PKCS5Padding");
      

      这是用于 CBC 模式的。

    4. 首选 AES-GCM

       Cipher.getInstance("AES/GCM/NoPadding");
      

      这将提供机密性(通过 CTR mode of operation)和完整性和身份验证(通过 GHash 两者一起构成 GCM)。 CTR 模式不需要填充,因此可以消除填充错误和填充预言攻击(如果适用)。并在 Cryptography.SE 上查看 What are the rules for using AES-GCM correctly?

      确保 IV 更新在同一键下重复,否则可能导致灾难性结果。就像,揭示明文而不是伪造品。可以使用计数器/LFSR 来确保 IV 永远不会重复。如果出现系统故障,nonce = random||(LFSR|Counter) 将是更好的解决方案,可以read more here。

    【讨论】:

    • 那么现在我要实现 GcmParameterSpec 了吗?在一个线程中,我看到有人写了以下行 gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, IV);并且这个值被用作 cipher.init() 中的参数
    • 是的,你需要。 Here 示例代码
    • 啊,谢谢你我已经实现了代码,但是,现在当服务器尝试监听第二个命令时,我得到一个标签不匹配异常,我会更新我的问题。但我不知道我是否应该创建另一篇文章或编辑这篇文章,因为堆栈跟踪和问题现在不同了
    • @B.Hulshof 请提出一个新问题。就是这样。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-07
    • 2013-04-22
    • 2023-04-06
    相关资源
    最近更新 更多