AES BadPaddingException при расшифровке - PullRequest
1 голос
/ 23 марта 2020

Итак, я пытаюсь создать веб-сокет, который отправляет данные, которые будут закодированы в base64, а затем зашифрованы с помощью AES, полученный байтовый массив будет отправлен через поток между сокетами и сервером. Это работает нормально, пока я не пытаюсь сделать определенную команду, которая дает мне BadPaddingException. Все фрагменты кода, использующие класс Cryptographer, используют один и тот же класс с одинаковым секретом.

В веб-сокетах есть потоки для каждого соединения для чтения и записи данных. Все эти потоки используют один и тот же 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];
    }
}

Следующая функция запуска будет считывать данные из входного потока (это сокет). И он также вызовет класс cryptographer для шифрования и дешифрования данных, это тот же класс, который также будет использоваться на стороне клиента с отдельным экземпляром. В случае этого исключения 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(). Что я тут не так делаю?

1 Ответ

2 голосов
/ 23 марта 2020

Очевидные ошибки;

  1. Вы кодируете базу данных64 перед шифрованием, в этом нет необходимости.
  2. Вы отправляете данные в двоичном виде, которые могут вызвать ошибки . Преобразовать байты в base64 перед отправкой и декодировать после получения. In Encrypt

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

    In decrypt

    byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));
    
  3. Именование / реализация функции шифрования / дешифрования с помощью getData. Сделайте их отдельными функциями.
  4. С Cipher.getInstance("AES") вы используете режим ECB операции. Это небезопасный шаблон с утечками, см. Пингвин из Википедии.
  5. Всегда явно определяйте свой режим работы и отступы, например

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

    , это для режима CB C.

  6. Предпочитают AES-GCM

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

    Это обеспечит конфиденциальность (по режиму работы CTR), а также целостность и аутентификацию (по GHa sh оба вместе делают GCM) , Режим CTR не требует заполнения, поэтому можно избавиться от ошибок заполнения и дополнения oracle атак, если применимо.

    Убедитесь, что IV новее повторяется под тем же ключом, иначе это может привести к катастрофическим результатам c , Мол, раскрыть открытый текст и не только подделки. Можно использовать счетчик / LFSR, чтобы убедиться, что IV никогда не повторяется. В случае системных сбоев nonce = random||(LFSR|Counter) будет лучшим решением, можно подробнее здесь .

...