解密时出现 AES BadPaddingException
AES BadPaddingException when decrypting
所以我正在尝试制作一个网络套接字,它发送的数据将被编码为 base64,然后用 AES 加密,生成的字节数组将通过套接字和服务器之间的流发送。这工作正常,直到我尝试发出某个命令给我 BadPaddingException
。所有使用 Cryptographer
class 的代码都使用完全相同的 class 和相同的秘密。
websockets每个连接都有线程读写数据。所有这些线程都使用相同的 cyptographer。
在连接线程中解密结果时给我异常的函数在一个名为 MessageSender
的 class 中这样写,将发生异常解密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];
}
}
下面的 运行 函数将从输入流(这是套接字)中读取数据。并且它还会调用密码器class来加密和解密数据,这与class相同,也将在客户端使用一个单独的实例。在这种情况下,IsReceiving
仍然设置为 false
。这里是 Cryptographer
class:
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()
中包含文件名时它也适用。我在这里做错了什么?
明显的错误;
你在加密之前对数据库进行64位编码,没有这个必要
您以二进制形式发送数据,which can cause errors。发送前将字节转成base64,接收后解码。在加密
byte[] encrypted = cipher.doFinal(data.getBytes());
return Base64.encodeBase64String(encrypted);
解密中
byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));
Naming/implementing encryption/decryption 函数与 getData。使它们具有独立的功能。
与Cipher.getInstance("AES")
你正在使用ECB mode的操作。这是不安全的,泄漏模式,请参阅维基百科的 Penguin。
始终明确定义您的操作模式和填充方式,例如
Cipher.getInstance("AES/CBC/PKCS5Padding");
这是 CBC 模式。
首选 AES-GCM
Cipher.getInstance("AES/GCM/NoPadding");
这将提供机密性(按点击率 mode of operation) and Integrity and authentication (by GHash both together make GCM). The CTR mode doesn't need padding so one gets rid of padding errors and padding oracle attacks if applicable. And see What are the rules for using AES-GCM correctly? Cryptography.SE
确保更新的 IV 在同一个密钥下重复,否则会导致灾难性的后果。就像,揭示明文,而不是伪造。可以使用 counter/LFSR 来确保 IV 永远不会重复。在系统故障的情况下,nonce = random||(LFSR|Counter)
将是更好的解决方案,可以 read more here.
所以我正在尝试制作一个网络套接字,它发送的数据将被编码为 base64,然后用 AES 加密,生成的字节数组将通过套接字和服务器之间的流发送。这工作正常,直到我尝试发出某个命令给我 BadPaddingException
。所有使用 Cryptographer
class 的代码都使用完全相同的 class 和相同的秘密。
websockets每个连接都有线程读写数据。所有这些线程都使用相同的 cyptographer。
在连接线程中解密结果时给我异常的函数在一个名为 MessageSender
的 class 中这样写,将发生异常解密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];
}
}
下面的 运行 函数将从输入流(这是套接字)中读取数据。并且它还会调用密码器class来加密和解密数据,这与class相同,也将在客户端使用一个单独的实例。在这种情况下,IsReceiving
仍然设置为 false
。这里是 Cryptographer
class:
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()
中包含文件名时它也适用。我在这里做错了什么?
明显的错误;
你在加密之前对数据库进行64位编码,没有这个必要
您以二进制形式发送数据,which can cause errors。发送前将字节转成base64,接收后解码。在加密
byte[] encrypted = cipher.doFinal(data.getBytes()); return Base64.encodeBase64String(encrypted);
解密中
byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));
Naming/implementing encryption/decryption 函数与 getData。使它们具有独立的功能。
与
Cipher.getInstance("AES")
你正在使用ECB mode的操作。这是不安全的,泄漏模式,请参阅维基百科的 Penguin。始终明确定义您的操作模式和填充方式,例如
Cipher.getInstance("AES/CBC/PKCS5Padding");
这是 CBC 模式。
首选 AES-GCM
Cipher.getInstance("AES/GCM/NoPadding");
这将提供机密性(按点击率 mode of operation) and Integrity and authentication (by GHash both together make GCM). The CTR mode doesn't need padding so one gets rid of padding errors and padding oracle attacks if applicable. And see What are the rules for using AES-GCM correctly? Cryptography.SE
确保更新的 IV 在同一个密钥下重复,否则会导致灾难性的后果。就像,揭示明文,而不是伪造。可以使用 counter/LFSR 来确保 IV 永远不会重复。在系统故障的情况下,
nonce = random||(LFSR|Counter)
将是更好的解决方案,可以 read more here.