decrypt static method
Дешифрует 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,
);
}