Расшифровка зашифрованных данных Dart AES в Kotlin приводит к исключению BadPaddingException

У меня есть приложение Dart, которое шифрует строку с использованием шифрования AES, и я пытаюсь расшифровать эту строку в приложении Kotlin. Однако, когда я пытаюсь расшифровать, я сталкиваюсь с исключением javax.crypto.BadPaddingException. Я не могу изменить код Dart, поэтому мне нужно настроить код Kotlin, чтобы правильно расшифровать данные.

Вот соответствующая часть кода шифрования Dart:

import 'dart:convert';
import 'dart:typed_data';

import 'package:crypto/crypto.dart';
import 'package:encrypt/encrypt.dart';

class CryptoService {
  static String encryptString(String input) {
    const keyString = "ABCdefg1234!!";
    final keyBytes = sha256.convert(utf8.encode(keyString)).bytes;
    final key = Key(Uint8List.fromList(keyBytes));
    final iv = IV.fromSecureRandom(16);
    final encrypter = Encrypter(AES(key));

    final encrypted = encrypter.encrypt(input, iv: iv);
    final combined = iv.bytes + encrypted.bytes;

    return base64Url.encode(combined);
  }

  static String decryptString(String encoded) {
    const keyString = "ABCdefg1234!!";
    final keyBytes = sha256.convert(utf8.encode(keyString)).bytes;
    final key = Key(Uint8List.fromList(keyBytes));
    final encrypter = Encrypter(AES(key));

    final combinedBytes = base64Url.decode(encoded);
    final ivBytes = combinedBytes.sublist(0, 16);
    final encryptedBytes = combinedBytes.sublist(16);

    final iv = IV(Uint8List.fromList(ivBytes));
    final encrypted = Encrypted(encryptedBytes);
    final decrypted = encrypter.decrypt(encrypted, iv: iv);

    return decrypted;
  }
}

void main() {
  print(CryptoService.encryptString("abcdef"));
}

А вот код Kotlin, который должен его расшифровать:

import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.util.*
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import java.security.SecureRandom

object CryptoService {
    private const val keyString = "ABCdefg1234!!"

    private fun generateKey(): SecretKey {
        val keyBytes = MessageDigest.getInstance("SHA-256").digest(keyString.toByteArray(StandardCharsets.UTF_8))
        return SecretKeySpec(keyBytes, "AES")
    }

    fun encryptString(input: String): String {
        val key = generateKey()
        val iv = ByteArray(16).apply { SecureRandom().nextBytes(this) }
        val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding").apply {
            init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(iv))
        }
        val encryptedBytes = cipher.doFinal(input.toByteArray(StandardCharsets.UTF_8))
        val combined = iv + encryptedBytes
        return Base64.getUrlEncoder().encodeToString(combined)
    }

    fun decryptString(encoded: String): String {
        val key = generateKey()
        val combinedBytes = Base64.getUrlDecoder().decode(encoded)
        val iv = combinedBytes.sliceArray(0 until 16)
        val encryptedBytes = combinedBytes.sliceArray(16 until combinedBytes.size)
        val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding").apply {
            init(Cipher.DECRYPT_MODE, key, IvParameterSpec(iv))
        }
        val decryptedBytes = cipher.doFinal(encryptedBytes)
        return String(decryptedBytes, StandardCharsets.UTF_8)
    }
}


fun main() {
   println(CryptoService.decryptString("FzaO3XI3ZVtUDAdM1So357dhQwg37fh0vzVr6jkPDaY = "))

   println("end")
}

Сообщение об ошибке, которое я получаю:

Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
 at com.sun.crypto.provider.CipherCore.unpad (:-1) 
 at com.sun.crypto.provider.CipherCore.fillOutputBuffer (:-1) 
 at com.sun.crypto.provider.CipherCore.doFinal (:-1) 

Как я могу настроить код Kotlin для правильной расшифровки данных, зашифрованных приложением Dart?

Похоже, что Dart (по умолчанию?) использует режим счетчика - использование AES/CTR/PKCS5Padding в Java (эквивалент Kotlin) дает мне правильный результат.

dave_thompson_085 02.09.2024 05:17

@dave_thompson_085 Спасибо за предложение AES/CTR/PKCS5Padding. Однако я получил сообщение «Невозможно найти поставщика, поддерживающего AES/CTR/PKCS5Padding» в Java/Kotlin, поэтому вместо этого я использовал AES/CTR/NoPadding, который работает для Dart в Kotlin, но иногда добавляет дополнительные символы новой строки. Кроме того, расшифровка Dart в Kotlin выдает: «Длина входных данных должна быть кратна размеру блока шифра». Я использую JDK 8.

Sol 02.09.2024 06:16
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
2
73
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Мне удалось решить проблемы совместимости между шифрованием Kotlin и Dart, переключившись на режим AES/CTR и используя Bouncy Castle для шифрования и дешифрования в Kotlin. Ниже приведено рабочее решение:

import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.util.*
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import java.security.SecureRandom
import org.bouncycastle.jce.provider.BouncyCastleProvider
import javax.crypto.SecretKey

object CryptoService {
    private const val keyString = "ABCdefg1234!!"

    init {
        Security.addProvider(BouncyCastleProvider())
    }

    private fun generateKey(): SecretKey {
        val keyBytes = MessageDigest.getInstance("SHA-256").digest(keyString.toByteArray(StandardCharsets.UTF_8))
        return SecretKeySpec(keyBytes, "AES")
    }

    fun encryptString(input: String): String {
        val key = generateKey()
        val iv = ByteArray(16).apply { SecureRandom().nextBytes(this) }
        val cipher = Cipher.getInstance("AES/CTR/PKCS5Padding", "BC").apply {
            init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(iv))
        }
        val encryptedBytes = cipher.doFinal(input.toByteArray(StandardCharsets.UTF_8))
        val combined = iv + encryptedBytes
        return Base64.getUrlEncoder().encodeToString(combined)
    }

    fun decryptString(encoded: String): String {
        val key = generateKey()
        val combinedBytes = Base64.getUrlDecoder().decode(encoded)
        val iv = combinedBytes.sliceArray(0 until 16)
        val encryptedBytes = combinedBytes.sliceArray(16 until combinedBytes.size)
        val cipher = Cipher.getInstance("AES/CTR/PKCS5Padding", "BC").apply {
            init(Cipher.DECRYPT_MODE, key, IvParameterSpec(iv))
        }
        val decryptedBytes = cipher.doFinal(encryptedBytes)
        return String(decryptedBytes, StandardCharsets.UTF_8)
    }
}


fun main() {
   println(CryptoService.decryptString("FzaO3XI3ZVtUDAdM1So357dhQwg37fh0vzVr6jkPDaY = "))

   println("end")
}

В этом решении используется режим AES/CTR (режим счетчика), который, по-видимому, более точно соответствует поведению шифрования Dart по умолчанию. Как заметил комментатор dave_thompson_085, Dart, похоже, по умолчанию использует режим счетчика. Используя AES/CTR/PKCS5Padding в Kotlin с провайдером Bouncy Castle (BC), результаты становятся совместимыми с процессами шифрования/дешифрования Dart.

Альтернативно вы можете отключить отступы на стороне Dart: Encrypter(AES(key, mode: AESMode.ctr, padding: null)). CTR — это режим потокового шифрования, который не требует заполнения, поэтому заполнение здесь неэффективно (к сожалению, в отличие от многих других библиотек, пакет шифрования не отключает автоматически заполнение для режимов потокового шифрования).

Topaco 02.09.2024 08:25

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