decrypt static method
Verifies the encrypted hash before AES-GCM authentication and verifies the original hash after decryption.
Implementation
static Future<Uint8List> decrypt({
required Uint8List ciphertext,
required Nip17FileMetadata metadata,
}) async {
if (metadata.size != null && metadata.size != ciphertext.length) {
throw const FormatException('Encrypted file size does not match');
}
if (sha256.convert(ciphertext).toString() != metadata.encryptedSha256) {
throw const FormatException('Encrypted file hash does not match');
}
if (ciphertext.length <= _authenticationTagLength) {
throw const FormatException('Encrypted file is too short');
}
final keyBytes = tryDecodeParameter(
metadata.decryptionKey,
allowedLengths: const {16, 32},
);
final nonce = tryDecodeParameter(
metadata.decryptionNonce,
allowedLengths: const {12, 16},
);
if (keyBytes == null ||
(keyBytes.length != 16 && keyBytes.length != 32) ||
nonce == null ||
(nonce.length != 12 && nonce.length != 16)) {
throw const FormatException('Invalid AES-GCM key or nonce');
}
final algorithm = keyBytes.length == 32
? AesGcm.with256bits(nonceLength: nonce.length)
: AesGcm.with128bits(nonceLength: nonce.length);
final macOffset = ciphertext.length - _authenticationTagLength;
final box = SecretBox(
ciphertext.sublist(0, macOffset),
nonce: nonce,
mac: Mac(ciphertext.sublist(macOffset)),
);
final plaintext = Uint8List.fromList(
await algorithm.decrypt(box, secretKey: SecretKey(keyBytes)),
);
if (sha256.convert(plaintext).toString() != metadata.originalSha256) {
throw const FormatException('Original file hash does not match');
}
return plaintext;
}