Шифровальщик Swift AES GCM и расшифровка Java - проблемы с заполнением

У меня есть быстрая функция для шифрования строки.

Когда я даю ему «abc», он создает зашифрованный текст без заполнения.

Когда я даю ему «a», он создает зашифрованный текст с дополнением «==».

Такое поведение понятно. Проблема в том, что мне нужно расшифровать строку в java.


Код Java отлично расшифровывает зашифрованный текст «abc», но не «a».

Выдает ошибку "Входной массив байтов имеет неверный конечный байт в...".

Очевидно, что он не может расшифровать байты заполнения.


Как решить эту проблему в java-коде? Я попытался создать экземпляр Cipher с помощью AES/GCM/PKCS5Padding, но он говорит, что не может найти провайдера, поддерживающего это дополнение.


БЫСТРЫЙ ШИФРОВАТЕЛЬ

static func encrypt() {
        
            let plain = "a" // another string "abc"
      
            static let secret = "my-xxx-bit-secret-my-secret-my-s"
            static let nonceString = "fv1nixTVoYpSvpdA"
            static let nonce = try! AES.GCM.Nonce(data: Data(base64Encoded: nonceString)!)
            static let symKey = SymmetricKey(data: secret.data(using: .utf8)!)

            let sealedBox = try! AES.GCM.seal(plain.data(using: .utf8)!, using: symKey, nonce: nonce)
            let ciphertext = sealedBox.ciphertext.base64EncodedString()
            let tag = sealedBox.tag
            print("ciphertext: .\(ciphertext).")
            print("tag: \(tag.base64EncodedString())")
                 

}

ДЕКРИПТОР ЯВА

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;

public class StringDecryptor {
    public static void main(String[] args) throws Exception {
    
        String actualText1 = "abc";
        String cipherText1 = "UoRs"; 
        String tag1 = "7VhlWAPpKka0CkmpshyOjw= = ";
        decryptSimpleString(cipherText1, tag1);
        
        String actualText2 = "a";
        String cipherText2 = "Ug= = ";  
        String tag2 = "hkjeGS301OgQyGqdGDuHAA= = ";
        decryptSimpleString(cipherText2, tag2);
     }
    
    public static void decryptSimpleString(String cipherText, String tag) throws Exception {
        String secret = "my-xxx-bit-secret-my-secret-my-s";
        byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
        
        String nonce = "fv1nixTVoYpSvpdA";
        byte[] nonceBytes = Base64.getDecoder().decode(nonce);
        
        byte[] tagBytes = Base64.getDecoder().decode(tag);
        
        String ciphterTextWithTag = cipherText + tag;
        byte[] ciphertextBytes = Base64.getDecoder().decode(ciphterTextWithTag); 

        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
        GCMParameterSpec gcmSpec = new GCMParameterSpec(128, nonceBytes);
        cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);

        byte[] plaintextBytes = cipher.doFinal(ciphertextBytes);
        String plaintext = new String(plaintextBytes, StandardCharsets.UTF_8);
        System.out.println("plain text was "+plaintext);
    }
    
    
}

Вывод Java-кода

plain text was abc

Exception in thread "main" java.lang.IllegalArgumentException: Input byte array has incorrect ending byte at 4
    at java.base/java.util.Base64$Decoder.decode0(Base64.java:875)
    at java.base/java.util.Base64$Decoder.decode(Base64.java:566)
    at java.base/java.util.Base64$Decoder.decode(Base64.java:589)
    at com.mydomain.crypto.StringDecryptor.decryptSimpleString(StringDecryptor.java:34)
    at com.mydomain.crypto.StringDecryptor.main(StringDecryptor.java:21)
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
0
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если вы сомневаетесь, проверьте документацию.

Документация для Base64 начинается словами:

Этот класс состоит исключительно из статических методов для получения кодировщиков и декодеров для схемы кодирования Base64. Реализация этого класса поддерживает следующие типы Base64, указанные в RFC 4648 и RFC 2045.

Если мы перейдем по этой первой ссылке, мы увидим интернет-стандарт для кодировки Base64 (и некоторых других кодировок). Раздел 4 говорит:

Используется 65-символьный подмножество US-ASCII, что позволяет использовать 6 битов. представлено на печатный символ. (Дополнительный 65-й символ, "=", используется для обозначения специальной функции обработки.)

А чуть ниже написано:

Заполнение в конце данных выполняется с помощью символа '='. Поскольку все входные данные с основанием 64 представляют собой целое число октетов, могут возникнуть только следующие случаи:

  1. Конечный квант ввода кодирования является целым числом, кратным 24 битам; здесь конечная единица закодированного вывода будет целым числом, кратным 4 символам, без заполнения "=".
  2. Конечный квант ввода кодирования составляет ровно 8 бит; здесь последней единицей закодированного вывода будут два символа, за которыми следуют два символа заполнения "=".
  3. Окончательный квант ввода кодирования составляет ровно 16 бит; здесь последней единицей закодированного вывода будут три символа, за которыми следует один символ заполнения "=".

Надеюсь, это проясняет, что символ = может появляться только в конце байтов, закодированных в Base64.

В вашем коде есть это:

String cipherText2 = "Ug= = ";  
String tag2 = "hkjeGS301OgQyGqdGDuHAA= = ";
decryptSimpleString(cipherText2, tag2);

что означает, что первым аргументом decryptSimpleString, cipherText, является «Ug==", а вторым аргументом decyptSimpleString, tag, является более длинная последовательность байтов в кодировке Base64.

Метод decryptSimpleString содержит следующее:

String ciphterTextWithTag = cipherText + tag;
byte[] ciphertextBytes = Base64.getDecoder().decode(ciphterTextWithTag); 

Упс. cipherText содержит символы =, а поскольку в спецификации Base64 четко указано, что = может появляться только в конце содержимого, закодированного в Base64, cipherText + tag не является допустимой последовательностью Base64.

Таким образом, вы не можете просто соединить две последовательности Base64 и предположить, что результат сам по себе является допустимой последовательностью Base64.

Если у вас есть две последовательности Base64 и вы хотите создать из них одну последовательность байтов, декодируйте каждую из них отдельно:

Base64.Decoder decoder = Base64.getDecoder();
byte[] decodedCipherText = decoder.decode(cipherText);
byte[] decodedTag = decoder.decode(tag);

byte[] ciphertextBytes =
    new byte[decodedCipherText.length + decodedTag.length];
ByteBuffer.wrap(ciphertextBytes).put(decodedCipherText).put(decodedTag);

Огромное спасибо! Я новичок в шифровании, и вы очень хорошо это объяснили. Действительно, большое спасибо!

nirav dinmali 03.04.2023 15:25

Другие вопросы по теме