Java AEADBadTagException标记不匹配套接字数据

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

我有一个服务器,让套接字连接到它,通过inputstreams发送数据,这些数据在一个名为Cryptographer的类中使用
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