首页 > 解决方案 > 解密时出现 AES BadPaddingException

问题描述

因此,我正在尝试创建一个 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(). 我在这里做错了什么?

标签: javaencryptionaes

解决方案


明显的错误;

  1. 您在加密之前对数据库进行base64编码,没有必要这样做。

  2. 您以二进制形式发送数据,这可能会导致错误。发送前将字节转换为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操作模式。这是不安全的泄漏模式,请参阅 Wikipedia's Penguin。

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

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

    这是针对 CBC 模式的。

  4. 首选 AES-GCM

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

    这将提供机密性(通过 CTR 操作模式)和完整性和身份验证(通过 GHash 两者一起构成GCM)。CTR 模式不需要填充,因此可以消除填充错误和填充预言攻击(如果适用)。并查看正确使用 AES-GCM 的规则是什么?密码学.SE

    确保 IV 较新的重复在相同的键下,否则会导致灾难性的结果。就像,揭示明文而不是伪造品。可以使用计数器/LFSR 来确保 IV 永远不会重复。在系统故障的情况下,anonce = random||(LFSR|Counter)将是一个更好的解决方案,可以在这里阅读更多内容


推荐阅读