decrypt static method

Future<Package> decrypt(
  1. Token token, {
  2. required SecretKey secretKey,
  3. List<int>? implicit,
})

Дешифрует PASETO v4.local токен.

Implementation

static Future<Package> decrypt(
  Token token, {
  required SecretKey secretKey,
  List<int>? implicit,
}) async {
  // Получаем ключ для дешифрования
  final keyBytes = await secretKey.extractBytes();

  // Проверяем размер ключа
  if (keyBytes.length != keyLength) {
    throw ArgumentError(
        'Ключ должен быть $keyLength байт, получено: ${keyBytes.length}');
  }

  // Проверяем, что токен соответствует v4.local
  if (token.header != header) {
    throw FormatException('Неверный заголовок токена для v4.local');
  }

  // Получаем payload и разбираем его составляющие
  final payload = token.payloadLocal;
  if (payload == null) {
    throw FormatException('Неверный формат токена для v4.local');
  }

  // Извлекаем nonce, cipherText и MAC
  final nonce = payload.nonce;
  if (nonce == null || nonce.bytes.length != nonceLength) {
    throw FormatException('Неверный формат nonce в токене v4.local');
  }

  final secretBox = payload.secretBox;
  if (secretBox == null) {
    throw FormatException('Шифротекст отсутствует в токене v4.local');
  }

  // ИСПРАВЛЕНО: MAC НЕ извлекается из payload, а вычисляется отдельно
  // согласно официальной спецификации PASETO v4.local

  // 1. Генерируем ключи шифрования (Ek) и аутентификации (Ak)
  final encKeys = await _deriveKeys(keyBytes, nonce.bytes);
  final encryptionKey = encKeys.encryptionKey;
  final counterNonce = encKeys.counterNonce;
  final authKey = encKeys.authKey;

  // 2. Подготавливаем данные для проверки аутентификации
  final implicitBytes = implicit ?? <int>[];
  final footer = token.footer ?? <int>[];

  // 3. PAE(h, n, c, f, i) - c должен быть без MAC!
  final headerBytes = utf8.encode(header.toTokenString);
  final cipherTextForAuth = secretBox.cipherText.length >= 32
      ? secretBox.cipherText.sublist(0, secretBox.cipherText.length - 32)
      : secretBox.cipherText;
  final preAuth = pae([
    Uint8List.fromList(headerBytes), // h - заголовок
    Uint8List.fromList(nonce.bytes), // n - nonce
    Uint8List.fromList(cipherTextForAuth), // c - шифротекст БЕЗ MAC
    Uint8List.fromList(footer), // f - футер (опциональный)
    Uint8List.fromList(
        implicitBytes), // i - implicit assertion (опциональный)
  ]);

  // 4. Вычисляем BLAKE2b-MAC и извлекаем ожидаемый MAC из ciphertext
  final calculatedMacBytes = _computeMac(preAuth, authKey);

  // Извлекаем MAC из конца ciphertext (последние 32 байта)
  if (secretBox.cipherText.length < 32) {
    throw FormatException('Токен слишком короткий для содержания MAC');
  }

  final expectedMacBytes =
      secretBox.cipherText.sublist(secretBox.cipherText.length - 32);

  // Сравниваем MAC с постоянным временем
  if (!_constantTimeEquals(calculatedMacBytes, expectedMacBytes)) {
    throw Exception('Проверка аутентичности токена не прошла (неверный MAC)');
  }

  // 5. В официальных тестовых векторах, ciphertext содержит зашифрованные данные + MAC
  // Последние 32 байта - это MAC, который нужно отделить
  final cipherTextOnly = secretBox.cipherText.length >= 32
      ? secretBox.cipherText.sublist(0, secretBox.cipherText.length - 32)
      : secretBox.cipherText;

  // 6. Дешифруем сообщение с помощью собственной реализации XChaCha20
  // Поскольку мы уже проверили MAC, мы знаем, что сообщение целостно
  // Используем нашу собственную реализацию XChaCha20 для дешифрования
  final decrypted = await _decryptCipherText(
    cipherTextOnly,
    encryptionKey,
    counterNonce,
  );

  // 7. Возвращаем дешифрованный пакет
  return Package(
    content: decrypted,
    footer: token.footer,
  );
}