decrypt static method

Future<Uint8List> decrypt({
  1. required Uint8List ciphertext,
  2. required Nip17FileMetadata metadata,
})

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;
}