Java AEADBadTagException标记不匹配套接字数据
我有一个服务器,让套接字连接到它,通过inputstreams发送数据,这些数据在一个名为Cryptographer的类中使用Java AEADBadTagException标记不匹配套接字数据,java,encryption,cryptography,aes,aes-gcm,Java,Encryption,Cryptography,Aes,Aes Gcm,我有一个服务器,让套接字连接到它,通过inputstreams发送数据,这些数据在一个名为Cryptographer的类中使用AES/GCM/NoPadding进行加密。服务器具有为连接的客户端保存功能的线程,每个线程都在ConnectionRead类中表示,该类保存对正在服务器类中初始化的cryptograhper类的引用 问题: 当我发送第一个命令时,它工作得很好,一点问题也没有。但不知何故,当我发送第二个命令if时,会给出以下stacktrace: javax.crypto.AEADBad
AES/GCM/NoPadding
进行加密。服务器具有为连接的客户端保存功能的线程,每个线程都在ConnectionRead类中表示,该类保存对正在服务器类中初始化的cryptograhper类的引用
问题:
当我发送第一个命令时,它工作得很好,一点问题也没有。但不知何故,当我发送第二个命令if时,会给出以下stacktrace:
javax.crypto.AEADBadTagException: Tag mismatch!
at java.base/com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:595)
at java.base/com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1116)
at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1053)
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.decrypt(Cryptographer.java:53)
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)
阅读器
package com.company.security;
import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class Cryptographer {
private Key secretKey;
private GCMParameterSpec gcmParameterSpec;
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();
byte[] IV = new byte[12];
gcmParameterSpec = new GCMParameterSpec(16 * 8, IV);
System.arraycopy(secretBytes, 0, secret, 0, secretBytes.length);
secretKey = new SecretKeySpec(secret, "AES");
}
/**
* Encrypt data.
* @param data to encrypt
* @return encrypted data
*/
public byte[] encrypt(byte[] data) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec);
byte[] encrypted = cipher.doFinal(data);
return encrypted;
} catch (InvalidKeyException | BadPaddingException
| IllegalBlockSizeException | NoSuchPaddingException
| NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
return null;
}
}
/**
* Decrypt data.
* @param data to decrypt
* @return decrypted data
*/
public byte[] decrypt(byte[] data) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec);
return cipher.doFinal(data);
} catch (InvalidKeyException | BadPaddingException
| IllegalBlockSizeException | NoSuchPaddingException
| NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
return null;
}
}
}
package com.company.client;
import com.company.FileLoader;
import com.company.client.helpers.ClientFileHelper;
import com.company.client.workers.MessageSender;
import com.company.security.Cryptographer;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
public class Reader implements Runnable {
private InputStream inputStream;
private ClientFileHelper fileHelper;
private Cryptographer cryptographer;
private FileLoader fileLoader;
private BufferedReader bufferedReader;
private MessageSender messageSender;
private boolean isActive = true;
private boolean isReceivingFile = false;
public Reader(BufferedReader bufferedReader, MessageSender messageSender, InputStream inputStream) {
this.bufferedReader = bufferedReader;
this.messageSender = messageSender;
this.inputStream = inputStream;
cryptographer = new Cryptographer();
}
@Override
public void run() {
while (isActive) {
try {
int count;
byte[] buffer;
if(!isReceivingFile) {
buffer = new byte[inputStream.available()];
} else {
buffer = new byte[inputStream.available()];
}
while ((count = inputStream.read(buffer)) > 0)
{
byte[] decrypted = cryptographer.decrypt(buffer);
if(!isReceivingFile) {
handleInput(new String(decrypted));
} else {
if(fileHelper.getFileBytes().length == 0) {
fileHelper.setFileBytes(decrypted);
} else {
fileHelper.saveFile();
isReceivingFile = false;
}
}
}
} catch (IOException e) {
e.printStackTrace();
break;
}
}
}
/**
* Handle the user input form the console.
* @param input user input from console
*/
private void handleInput(String input) {
try {
if (input.equals("PING")) { // If we get a PING message we send back a PONG message.
messageSender.send("PONG");
} else if (input.contains("FILE")) {
setupFileAccept(input);
isReceivingFile = true;
} else {
System.out.println(input);
}
} catch (Exception ex) {
isActive = false;
}
}
/**
* Setup the file helper for the client that's going to receive a file.
* @param line command
*/
private void setupFileAccept(String line) {
String[] args = line.split(" ");
if(args[0].equals("FILE")) {
fileHelper = new ClientFileHelper(args[1], Integer.valueOf(args[2]));
}
}
}
连接读取也有类似的读取功能,如下所示:
while (isActive) {
try {
int count;
byte[] buffer;
if(!isReceivingFile) {
buffer = new byte[inputStream.available()];
} else {
buffer = fileHelper.getFileBytes();
}
while ((count = inputStream.read(buffer)) > 0)
{
byte[] decrypted = server.cryptographer.decrypt(buffer);
if(!isReceivingFile) {
getInput(new String(decrypted));
} else {
fileHelper.setFileBytes(decrypted);
// bytes received, now we can send the file!
if(fileHelper.sendToReceiver()) {
writeToClient(fileHelper.getReceiverName()
+ " received " + fileHelper.getFilename());
fileHelper = null;
}
}
}
} catch (IOException e) {
e.printStackTrace();
break;
}
}
public class Cryptographer {
private SecretKey secretKey;
private byte[] aad;
private SecureRandom secureRandom;
private byte[] IV;
public Cryptographer(SecretKey secretKey) {
this.secretKey = secretKey;
secureRandom = new SecureRandom();
IV = new byte[12];
secureRandom.nextBytes(IV);
aad = "association".getBytes();
}
/**
* Encrypt data.
* @param data to encrypt
* @return encrypted data
*/
public byte[] encrypt(byte[] data) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
secureRandom.nextBytes(IV);
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, IV);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
cipher.updateAAD(aad);
return toByteBuffer(cipher.doFinal(data));
} catch (InvalidKeyException | BadPaddingException
| IllegalBlockSizeException | NoSuchPaddingException
| NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
return null;
}
}
/**
* Decrypt data.
* @param data to decrypt
* @return decrypted data
*/
public byte[] decrypt(byte[] data) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
// get the data from the byte buffer
data = fromByteBuffer(data);
// create the gcm parameter with the received IV.
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, IV);
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
cipher.updateAAD(aad);
return cipher.doFinal(data);
} catch (InvalidKeyException | BadPaddingException
| IllegalBlockSizeException | NoSuchPaddingException
| NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
return null;
}
}
/**
* Put the encrypted data through a byte buffer.
* This buffer will contain information about the IV array.
* @param data encrypted data
* @return the ByteBuffer result as byte array
*/
private byte[] toByteBuffer(byte[] data) {
ByteBuffer byteBuffer = ByteBuffer.allocate(4 + IV.length + data.length);
byteBuffer.putInt(IV.length);
byteBuffer.put(IV);
byteBuffer.put(data);
return byteBuffer.array();
}
/**
* Gets data from a ByteBuffer and sets up data needed for decryption.
* @param data ByteBuffer data as byte array
* @return ByteBuffer encrypted data
*/
private byte[] fromByteBuffer(byte[] data) {
ByteBuffer byteBuffer = ByteBuffer.wrap(data);
int ivLength = byteBuffer.getInt();
if(ivLength < 12 || ivLength >= 16) {
throw new IllegalArgumentException("invalid iv length");
}
IV = new byte[ivLength];
byteBuffer.get(IV);
byte[] remaining = new byte[byteBuffer.remaining()];
byteBuffer.get(remaining);
return remaining;
}
}
在本例中,只需假设服务器类正确初始化了cryptographer属性,情况总是如此
我的猜测是某个值出错了,但我不确定。我不知道该怎么解决这个问题。有人能帮我指出错误并提出可能的解决方案来解决这个问题吗?我的java版本是12.0.1我鼓励大家使用SSL/TLS或DTLS,而不是尝试重新实现它的一部分 我不确定这是否会导致您的错误,但如果我对的解释正确,您应该为每条消息更改GCMParameterSpec: 每次使用GCM模式进行加密操作后,呼叫者应 使用GCM参数重新初始化密码对象,该参数具有 不同IV值 以及: GCM模式对使用给定密钥进行加密时使用的IVs有唯一性要求。当重复IVs进行GCM加密时,此类使用会受到伪造攻击 此外,您没有使用updateAAD(附加身份验证数据),尽管根据错误消息,这是可选的,但它听起来像是在导致错误,但它可能只是一个误导性的错误消息 更新: 我为Cryptographer类编写了很多单元测试,只有在再次解密密文之前,我开始对密文进行随机更改,我才会经常收到相同的错误。因为我们可以信任TCP/IP在连接的另一端复制完全相同的字节,所以我们会遇到以下问题:
不,我还没有编写和测试我自己的实现,但是有一些例子,比如,我鼓励cosider使用SSL/TLS或DTL,而不是尝试重新实现它的一部分 我不确定这是否会导致您的错误,但如果我对的解释正确,您应该为每条消息更改GCMParameterSpec: 每次使用GCM模式进行加密操作后,呼叫者应 使用GCM参数重新初始化密码对象,该参数具有 不同IV值 以及: GCM模式对使用给定密钥进行加密时使用的IVs有唯一性要求。当重复使用IVs进行GCM加密时,此类使用会受到伪造攻击 此外,您没有使用updateAAD(附加身份验证数据),尽管根据错误消息,这是可选的,但它听起来像是在导致错误,但它可能只是一个误导性的错误消息 更新: 我为Cryptographer类编写了很多单元测试,只有在我再次解密之前开始对密文进行随机更改,我经常会遇到相同的错误。因为我们可以信任TCP/IP在连接的另一端复制完全相同的字节,所以我们可能会遇到以下问题:
不,我还没有编写和测试我自己的实现,但是还有一些例子,比如,来自感谢JohannesB为我指明了正确的方向 我现在已经解决了我的问题。它首先从阅读方法开始,我不得不改为:
byte[] buffer;
while (inputStream.available() > 0)
{
int read = inputStream.read(buffer);
if(read == 0)
break;
}
// An if statement checking if the buffer has been filled and based on this
// It will execute methods
我的密码程序类现在看起来像这样:
while (isActive) {
try {
int count;
byte[] buffer;
if(!isReceivingFile) {
buffer = new byte[inputStream.available()];
} else {
buffer = fileHelper.getFileBytes();
}
while ((count = inputStream.read(buffer)) > 0)
{
byte[] decrypted = server.cryptographer.decrypt(buffer);
if(!isReceivingFile) {
getInput(new String(decrypted));
} else {
fileHelper.setFileBytes(decrypted);
// bytes received, now we can send the file!
if(fileHelper.sendToReceiver()) {
writeToClient(fileHelper.getReceiverName()
+ " received " + fileHelper.getFilename());
fileHelper = null;
}
}
}
} catch (IOException e) {
e.printStackTrace();
break;
}
}
public class Cryptographer {
private SecretKey secretKey;
private byte[] aad;
private SecureRandom secureRandom;
private byte[] IV;
public Cryptographer(SecretKey secretKey) {
this.secretKey = secretKey;
secureRandom = new SecureRandom();
IV = new byte[12];
secureRandom.nextBytes(IV);
aad = "association".getBytes();
}
/**
* Encrypt data.
* @param data to encrypt
* @return encrypted data
*/
public byte[] encrypt(byte[] data) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
secureRandom.nextBytes(IV);
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, IV);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
cipher.updateAAD(aad);
return toByteBuffer(cipher.doFinal(data));
} catch (InvalidKeyException | BadPaddingException
| IllegalBlockSizeException | NoSuchPaddingException
| NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
return null;
}
}
/**
* Decrypt data.
* @param data to decrypt
* @return decrypted data
*/
public byte[] decrypt(byte[] data) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
// get the data from the byte buffer
data = fromByteBuffer(data);
// create the gcm parameter with the received IV.
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, IV);
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
cipher.updateAAD(aad);
return cipher.doFinal(data);
} catch (InvalidKeyException | BadPaddingException
| IllegalBlockSizeException | NoSuchPaddingException
| NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
return null;
}
}
/**
* Put the encrypted data through a byte buffer.
* This buffer will contain information about the IV array.
* @param data encrypted data
* @return the ByteBuffer result as byte array
*/
private byte[] toByteBuffer(byte[] data) {
ByteBuffer byteBuffer = ByteBuffer.allocate(4 + IV.length + data.length);
byteBuffer.putInt(IV.length);
byteBuffer.put(IV);
byteBuffer.put(data);
return byteBuffer.array();
}
/**
* Gets data from a ByteBuffer and sets up data needed for decryption.
* @param data ByteBuffer data as byte array
* @return ByteBuffer encrypted data
*/
private byte[] fromByteBuffer(byte[] data) {
ByteBuffer byteBuffer = ByteBuffer.wrap(data);
int ivLength = byteBuffer.getInt();
if(ivLength < 12 || ivLength >= 16) {
throw new IllegalArgumentException("invalid iv length");
}
IV = new byte[ivLength];
byteBuffer.get(IV);
byte[] remaining = new byte[byteBuffer.remaining()];
byteBuffer.get(remaining);
return remaining;
}
}
公共类密码器{
私密密钥;
专用字节[]aad;
私有安全随机安全随机;
专用字节[]IV;
公共密码器(SecretKey SecretKey){
this.secretKey=secretKey;
secureRandom=新的secureRandom();
IV=新字节[12];
安全随机。下一字节(IV);
aad=“association”。getBytes();
}
/**
*加密数据。
*@param要加密的数据
*@返回加密数据
*/
公共字节[]加密(字节[]数据){
试一试{
Cipher Cipher=Cipher.getInstance(“AES/GCM/NoPadding”);
SecretKeySpec keySpec=新的SecretKeySpec(secretKey.getEncoded(),“AES”);
安全随机。下一字节(IV);
GCMPParameterSpec GCMPParameterSpec=新的GCMPParameterSpec(128,IV);
cipher.init(cipher.ENCRYPT_模式,keySpec,gcmParameterSpec);
cipher.updateAAD(aad);
返回到字节缓冲区(cipher.doFinal(data));
}catch(InvalidKeyException | BadPaddingException
|IllegalBlockSizeException |无此填充异常
|NoSuchAlgorithmException |无效算法参数Exception e){
e、 printStackTrace();
返回null;
}
}
/**
*解密数据。
*@param要解密的数据
*@return解密数据
*/
公共字节[]解密(字节[]数据){
试一试{
Cipher Cipher=Cipher.getInstance(“AES/GCM/N