decrypt method

  1. @override
Future<List<int>> decrypt(
  1. SecretBox secretBox, {
  2. required SecretKey secretKey,
  3. List<int> aad = const <int>[],
  4. int keyStreamIndex = 0,
})
override

Decrypts SecretBox and returns the bytes.

Subclasses of Cipher do the following: 1.Authenticates SecretBox.mac with macAlgorithm. 2.Decrypts SecretBox.cipherText. 3.Returns the cleartext.

The SecretBox is authenticated with [SecretBox.checkMac()), which will throw SecretBoxAuthenticationError if the MAC is incorrect.

You must give a SecretKey that has the correct length and type.

Optional parameter nonce (also known as "initialization vector", "IV", or "salt") is some non-secret unique sequence of bytes. If you don't define it, the cipher will generate nonce for you.

Parameter aad can be used to pass Associated Authenticated Data (AAD). If you pass a non-empty list and the underlying cipher doesn't support AAD, the method will throw ArgumentError.

Implementation

@override
Future<List<int>> decrypt(
  SecretBox secretBox, {
  required SecretKey secretKey,
  List<int> aad = const <int>[],
  int keyStreamIndex = 0,
}) async {
  // Validate arguments
  final secretKeyData = await secretKey.extract();
  final actualSecretKeyLength = secretKeyData.bytes.length;
  final expectedSecretKeyLength = secretKeyLength;
  if (actualSecretKeyLength != expectedSecretKeyLength) {
    throw ArgumentError.value(
      secretKey,
      'secretKey',
      'Expected $secretKeyLength bytes, got $actualSecretKeyLength bytes',
    );
  }
  final nonceLength = secretBox.nonce.length;
  if (nonceLength != 16) {
    throw ArgumentError.value(
      secretBox,
      'secretBox',
      'Expected nonce with 16 bytes, got $nonceLength bytes',
    );
  }
  if (keyStreamIndex < 0) {
    throw ArgumentError.value(
      keyStreamIndex,
      'keyStreamIndex',
    );
  }

  // Authenticate
  await secretBox.checkMac(
    macAlgorithm: macAlgorithm,
    secretKey: secretKeyData,
    aad: aad,
  );

  final secretKeyBytes = secretKeyData.bytes;
  final cipherText = secretBox.cipherText;
  if (cipherText.length % 16 != 0) {
    throw ArgumentError.value(
      secretBox,
      'secretBox',
      'Invalid cipherText length: ${cipherText.length}',
    );
  }

  // Expand key
  final preparedKey =
      aesExpandKeyForDecrypting(SecretKeyData(secretKeyBytes));

  // Construct output
  final outputAsUint32List = Uint32List(cipherText.length ~/ 16 * 4);
  final outputAsUint8List = Uint8List.view(outputAsUint32List.buffer);
  outputAsUint8List.setAll(0, cipherText);
  assert(outputAsUint8List.length == cipherText.length);

  // Current block
  var e0 = 0;
  var e1 = 0;
  var e2 = 0;
  var e3 = 0;

  for (var i = 0; i < outputAsUint32List.length; i += 4) {
    // Memorize previous encrypted block
    final p0 = e0;
    final p1 = e1;
    final p2 = e2;
    final p3 = e3;

    // Memorize encrypted block
    e0 = outputAsUint32List[i];
    e1 = outputAsUint32List[i + 1];
    e2 = outputAsUint32List[i + 2];
    e3 = outputAsUint32List[i + 3];

    // Block function
    aesDecryptBlock(
      outputAsUint32List,
      i,
      outputAsUint32List,
      i,
      preparedKey,
    );

    if (i == 0) {
      // block ^= nonce
      final nonceBytes = secretBox.nonce;
      for (var i = 0; i < nonceBytes.length; i++) {
        outputAsUint8List[i] ^= nonceBytes[i];
      }
    } else {
      // block ^= previous_block
      outputAsUint32List[i] ^= p0;
      outputAsUint32List[i + 1] ^= p1;
      outputAsUint32List[i + 2] ^= p2;
      outputAsUint32List[i + 3] ^= p3;
    }
  }

  // PKCS7 padding:
  // The last byte has padding length.
  final paddingLength = outputAsUint8List.last;
  if (paddingLength == 0 || paddingLength > 16) {
    throw StateError(
      'The decrypted bytes have invalid PKCS7 padding length in the end: $paddingLength',
    );
  }

  // Check that all padding bytes are correct PKCS7 padding bytes.
  for (var i = outputAsUint8List.length - paddingLength;
      i < outputAsUint8List.length;
      i++) {
    if (outputAsUint8List[i] != paddingLength) {
      throw StateError('The decrypted bytes are missing padding');
    }
  }
  if (paddingLength == 0) {
    return outputAsUint8List;
  }
  return Uint8List.view(
    outputAsUint32List.buffer,
    outputAsUint32List.offsetInBytes,
    outputAsUint32List.lengthInBytes - paddingLength,
  );
}