Exemple #1
0
        public void ModifiedCiphertextFails()
        {
            var rnd = new Random();
            var key = new byte[Snuffle.KEY_SIZE_IN_BYTES];

            rnd.NextBytes(key);

            var aad = new byte[16];

            rnd.NextBytes(aad);

            var message = new byte[32];

            rnd.NextBytes(message);

            var aead       = new ChaCha20Poly1305(key);
            var ciphertext = aead.Encrypt(message, aad);

            // Flipping bits
            for (var b = 0; b < ciphertext.Length; b++)
            {
                for (var bit = 0; bit < 8; bit++)
                {
                    var modified = new byte[ciphertext.Length];
                    Array.Copy(ciphertext, modified, ciphertext.Length);

                    modified[b] ^= (byte)(1 << bit);

                    Assert.Throws <CryptographicException>(() => aead.Decrypt(modified, aad), SnufflePoly1305.AEAD_EXCEPTION_INVALID_TAG);
                }
            }

            // Truncate the message
            for (var length = 0; length < ciphertext.Length; length++)
            {
                var modified = new byte[length];
                Array.Copy(ciphertext, modified, length);

                Assert.Throws <CryptographicException>(() => aead.Decrypt(modified, aad), SnufflePoly1305.AEAD_EXCEPTION_INVALID_TAG);
            }

            // Modify AAD
            for (var b = 0; b < aad.Length; b++)
            {
                for (var bit = 0; bit < 8; bit++)
                {
                    var modified = new byte[aad.Length];
                    Array.Copy(aad, modified, aad.Length);

                    modified[b] ^= (byte)(1 << bit);

                    Assert.Throws <CryptographicException>(() => aead.Decrypt(modified, aad), SnufflePoly1305.AEAD_EXCEPTION_INVALID_TAG);
                }
            }
        }
Exemple #2
0
        public void EncryptDecryptWithNonceTest()
        {
            var rnd = new Random();
            var key = new byte[Snuffle.KEY_SIZE_IN_BYTES];

            rnd.NextBytes(key);

            var aead = new ChaCha20Poly1305(key);

            for (var i = 0; i < 100; i++)
            {
                var message = new byte[100];
                rnd.NextBytes(message);

                var aad = new byte[16];
                rnd.NextBytes(aad);

                var nonce = new byte[12];
                rnd.NextBytes(nonce);

                var ciphertext = aead.Encrypt(message, aad, nonce);
                var decrypted  = aead.Decrypt(ciphertext, aad, nonce);

                //Assert.AreEqual(message, decrypted);
                Assert.IsTrue(CryptoBytes.ConstantTimeEquals(message, decrypted));
            }
        }
Exemple #3
0
        public void EncryptDecryptLongMessagesWithNonceTest()
        {
            var rnd = new Random();

            var dataSize = 16;

            while (dataSize <= (1 << 24))
            {
                var plaintext = new byte[dataSize];
                rnd.NextBytes(plaintext);

                var aad = new byte[dataSize / 3];
                rnd.NextBytes(aad);

                var nonce = new byte[12];
                rnd.NextBytes(nonce);

                var key = new byte[Snuffle.KEY_SIZE_IN_BYTES];
                rnd.NextBytes(key);

                var aead       = new ChaCha20Poly1305(key);
                var ciphertext = aead.Encrypt(plaintext, aad, nonce);
                var decrypted  = aead.Decrypt(ciphertext, aad, nonce);

                //Assert.AreEqual(plaintext, decrypted);
                Assert.IsTrue(CryptoBytes.ConstantTimeEquals(plaintext, decrypted));
                dataSize += 5 * dataSize / 11;
            }
        }
Exemple #4
0
        public void Decrypt(Tests.Vectors.Rfc8439TestVector test)
        {
            var aead      = new ChaCha20Poly1305(test.Key);
            var plaintext = new byte[test.CipherText.Length];

            aead.Decrypt(test.Nonce, test.CipherText, test.Tag, plaintext, test.Aad);
        }
Exemple #5
0
        public void DecryptWhenNonceLengthIsInvalidFails()
        {
            // Arrange, Act & Assert
            var aead = new ChaCha20Poly1305(new byte[Snuffle.KEY_SIZE_IN_BYTES]);

            Assert.Throws <CryptographicException>(() => aead.Decrypt(new byte[50], new byte[0], new byte[12 + TestHelpers.ReturnRandomPositiveNegative()]), EXCEPTION_MESSAGE_NONCE_LENGTH);
        }
Exemple #6
0
        public void DecryptWhenNonceIsEmptyFails()
        {
            // Arrange, Act & Assert
            var aead = new ChaCha20Poly1305(new byte[Snuffle.KEY_SIZE_IN_BYTES]);

            Assert.Throws <CryptographicException>(() => aead.Decrypt(new byte[50], new byte[0], new byte[0]), EXCEPTION_MESSAGE_NONCE_LENGTH);
        }
Exemple #7
0
        public string Decode(string token, int?ttl = null)
        {
            var tokenBytes = token.FromBase62();

            var headerBytes = new byte[HeaderBytes];

            Buffer.BlockCopy(tokenBytes, 0, headerBytes, 0, HeaderBytes);

            var nonceBytes = new byte[NonceBytes];

            Buffer.BlockCopy(headerBytes, VersionByte + TimestampBytes, nonceBytes, 0, NonceBytes);

            var cipherBytes = new byte[tokenBytes.Length - HeaderBytes];

            Buffer.BlockCopy(tokenBytes, headerBytes.Length, cipherBytes, 0, cipherBytes.Length);

            var keyBytes = new byte[Snuffle.KEY_SIZE_IN_BYTES];

            Buffer.BlockCopy(_key.ToCharArray(), 0, keyBytes, 0, _key.ToCharArray().Length);

            var aead       = new ChaCha20Poly1305(keyBytes);
            var cipherText = aead.Decrypt(cipherBytes, null, nonceBytes);

            return(cipherText.GetString());
        }
Exemple #8
0
        public void DecryptWithNonceWhenCiphertextIsTooShortFails()
        {
            // Arrange & Act
            var aead = new ChaCha20Poly1305(new byte[Snuffle.KEY_SIZE_IN_BYTES]);

            // Assert
            Assert.Throws <CryptographicException>(() => aead.Decrypt(new byte[27], new byte[1], new byte[1]));
        }
Exemple #9
0
        public static void TwoEncryptionsAndDecryptionsUsingOneInstance()
        {
            byte[] key             = "fde37f01fe9ca260f432e0ed98b3e0bb23895ca1ca1ce2cfcaaca2ccc98889d7".HexToByteArray();
            byte[] originalData1   = Enumerable.Range(1, 15).Select((x) => (byte)x).ToArray();
            byte[] originalData2   = Enumerable.Range(14, 97).Select((x) => (byte)x).ToArray();
            byte[] associatedData2 = Enumerable.Range(100, 109).Select((x) => (byte)x).ToArray();
            byte[] nonce1          = "b41329dd64af2c3036661b46".HexToByteArray();
            byte[] nonce2          = "8ba10892e8b87d031196bf99".HexToByteArray();

            byte[] expectedCiphertext1 = "75f5aafbbabab80a3cfa2ecfd1bc58".HexToByteArray();
            byte[] expectedTag1        = "1ed70acc454fba01f0354e93eba9b428".HexToByteArray();

            byte[] expectedCiphertext2 = (
                "f95cc19929463ba96a2cfc21fac5345ec308e2748995ba285af6b21ca3d665bc" +
                "00144604b38e9645fb2d5f5893fc78871bd8f5fc91caaa013eac5f80397fd65c" +
                "358c239f013f3c75da17ddbd14de01eb67f5204dfa787986fb27a098fe21b2c5" +
                "07").HexToByteArray();
            byte[] expectedTag2 = "9877f87f29f68b5f9efb071c1351ccf6".HexToByteArray();

            using (var chaChaPoly = new ChaCha20Poly1305(key))
            {
                byte[] ciphertext1 = new byte[originalData1.Length];
                byte[] tag1        = new byte[expectedTag1.Length];
                chaChaPoly.Encrypt(nonce1, originalData1, ciphertext1, tag1);
                Assert.Equal(expectedCiphertext1, ciphertext1);
                Assert.Equal(expectedTag1, tag1);

                byte[] ciphertext2 = new byte[originalData2.Length];
                byte[] tag2        = new byte[expectedTag2.Length];
                chaChaPoly.Encrypt(nonce2, originalData2, ciphertext2, tag2, associatedData2);
                Assert.Equal(expectedCiphertext2, ciphertext2);
                Assert.Equal(expectedTag2, tag2);

                byte[] plaintext1 = new byte[originalData1.Length];
                chaChaPoly.Decrypt(nonce1, ciphertext1, tag1, plaintext1);
                Assert.Equal(originalData1, plaintext1);

                byte[] plaintext2 = new byte[originalData2.Length];
                chaChaPoly.Decrypt(nonce2, ciphertext2, tag2, plaintext2, associatedData2);
                Assert.Equal(originalData2, plaintext2);
            }
        }
Exemple #10
0
        public static void EncryptDecryptNullTag()
        {
            byte[] key        = "fde37f01fe9ca260f432e0ed98b3e0bb23895ca1ca1ce2cfcaaca2ccc98889d7".HexToByteArray();
            byte[] nonce      = new byte[NonceSizeInBytes];
            byte[] plaintext  = new byte[0];
            byte[] ciphertext = new byte[0];

            using (var chaChaPoly = new ChaCha20Poly1305(key))
            {
                Assert.Throws <ArgumentNullException>(() => chaChaPoly.Encrypt(nonce, plaintext, ciphertext, (byte[])null));
                Assert.Throws <ArgumentNullException>(() => chaChaPoly.Decrypt(nonce, ciphertext, (byte[])null, plaintext));
            }
        }
            internal Deserializer(CryptoDtoChannelStore channelStore, ReadOnlySpan <byte> bytes, bool ignoreSequence)
            {
                sequenceValid = false;
                headerLength  = Unsafe.ReadUnaligned <ushort>(ref MemoryMarshal.GetReference(bytes));           //.NET Standard 2.0 doesn't have BitConverter.ToUInt16(Span<T>)
                if (bytes.Length < (2 + headerLength))
                {
                    throw new CryptographicException("Not enough bytes to process packet.");
                }

                ReadOnlySpan <byte> headerDataBuffer = bytes.Slice(2, headerLength);

                header = MessagePackSerializer.Deserialize <CryptoDtoHeaderDto>(headerDataBuffer.ToArray());
                ReadOnlySpan <byte> receiveKey = channelStore.GetReceiveKey(header.ChannelTag, header.Mode);                 //This will throw exception if channel tag isn't in the store

                switch (header.Mode)
                {
                case CryptoDtoMode.ChaCha20Poly1305:
                {
                    int aeLength = bytes.Length - (2 + headerLength);
                    ReadOnlySpan <byte> aePayloadBuffer = bytes.Slice(2 + headerLength, aeLength);

                    ReadOnlySpan <byte> adBuffer = bytes.Slice(0, 2 + headerLength);

                    Span <byte> nonceBuffer = stackalloc byte[Aead.NonceSize];
                    BinaryPrimitives.WriteUInt64LittleEndian(nonceBuffer.Slice(4), header.Sequence);

                    var aead = new ChaCha20Poly1305(receiveKey.ToArray());
                    ReadOnlySpan <byte> decryptedPayload = aead.Decrypt(aePayloadBuffer.ToArray(), adBuffer.ToArray(), nonceBuffer);

                    if (ignoreSequence)
                    {
                        sequenceValid = channelStore.IsReceivedSequenceAllowed(header.ChannelTag, header.Sequence);
                    }
                    else
                    {
                        channelStore.CheckReceivedSequence(header.ChannelTag, header.Sequence);             //The packet has passed MAC, so now check if it's being duplicated or replayed
                        sequenceValid = true;
                    }

                    dtoNameLength = Unsafe.ReadUnaligned <ushort>(ref MemoryMarshal.GetReference(decryptedPayload));           //.NET Standard 2.0 doesn't have BitConverter.ToUInt16(Span<T>)
                    dtoNameBuffer = decryptedPayload.Slice(2, dtoNameLength);

                    dataLength = Unsafe.ReadUnaligned <ushort>(ref MemoryMarshal.GetReference(decryptedPayload.Slice(2 + dtoNameLength, 2)));           //.NET Standard 2.0 doesn't have BitConverter.ToUInt16(Span<T>)
                    dataBuffer = decryptedPayload.Slice(2 + dtoNameLength + 2, dataLength);
                    break;
                }

                default:
                    throw new CryptographicException("Mode not recognised");
                }
            }
Exemple #12
0
        public static void PlaintextAndCiphertextSizeDiffer(int ptLen, int ctLen)
        {
            byte[] key        = new byte[KeySizeInBytes];
            byte[] nonce      = new byte[NonceSizeInBytes];
            byte[] plaintext  = new byte[ptLen];
            byte[] ciphertext = new byte[ctLen];
            byte[] tag        = new byte[TagSizeInBytes];

            using (var chaChaPoly = new ChaCha20Poly1305(key))
            {
                Assert.Throws <ArgumentException>(() => chaChaPoly.Encrypt(nonce, plaintext, ciphertext, tag));
                Assert.Throws <ArgumentException>(() => chaChaPoly.Decrypt(nonce, ciphertext, tag, plaintext));
            }
        }
Exemple #13
0
        /// <summary>Method which verifies that a supplied plain-text password matches the encrypted password.</summary>
        public bool Verify(string plaintext)
        {
            if (EncryptedPasswordb64 == null)
            {
                throw new Exception("You must have an encrypted password to verify against");
            }

            // Unpack the Encrypted Password
            var base10          = Convert.FromBase64String(EncryptedPasswordb64);
            var dec             = new ChaCha20Poly1305(Encoding.ASCII.GetBytes(Key));
            var decryptedBytes  = dec.Decrypt(base10);
            var decryptedString = Encoding.ASCII.GetString(decryptedBytes);

            return(PasswordHash.ArgonHashStringVerify(decryptedString, plaintext));
        }
Exemple #14
0
        public static void Rfc8439Tests(AEADTest testCase)
        {
            using (var chaChaPoly = new ChaCha20Poly1305(testCase.Key))
            {
                byte[] ciphertext = new byte[testCase.Plaintext.Length];
                byte[] tag        = new byte[testCase.Tag.Length];
                chaChaPoly.Encrypt(testCase.Nonce, testCase.Plaintext, ciphertext, tag, testCase.AssociatedData);
                Assert.Equal(testCase.Ciphertext, ciphertext);
                Assert.Equal(testCase.Tag, tag);

                byte[] plaintext = new byte[testCase.Plaintext.Length];
                chaChaPoly.Decrypt(testCase.Nonce, ciphertext, tag, plaintext, testCase.AssociatedData);
                Assert.Equal(testCase.Plaintext, plaintext);
            }
        }
Exemple #15
0
        public static void InplaceEncryptDecrypt()
        {
            byte[] key               = "fde37f01fe9ca260f432e0ed98b3e0bb23895ca1ca1ce2cfcaaca2ccc98889d7".HexToByteArray();
            byte[] nonce             = RandomNumberGenerator.GetBytes(NonceSizeInBytes);
            byte[] originalPlaintext = new byte[] { 1, 2, 8, 12, 16, 99, 0 };
            byte[] data              = (byte[])originalPlaintext.Clone();
            byte[] tag               = new byte[TagSizeInBytes];

            using (var chaChaPoly = new ChaCha20Poly1305(key))
            {
                chaChaPoly.Encrypt(nonce, data, data, tag);
                Assert.NotEqual(originalPlaintext, data);

                chaChaPoly.Decrypt(nonce, data, tag, data);
                Assert.Equal(originalPlaintext, data);
            }
        }
Exemple #16
0
        public static void Rfc8439TestsTamperTag(AEADTest testCase)
        {
            using (var chaChaPoly = new ChaCha20Poly1305(testCase.Key))
            {
                byte[] ciphertext = new byte[testCase.Plaintext.Length];
                byte[] tag        = new byte[testCase.Tag.Length];
                chaChaPoly.Encrypt(testCase.Nonce, testCase.Plaintext, ciphertext, tag, testCase.AssociatedData);
                Assert.Equal(testCase.Ciphertext, ciphertext);
                Assert.Equal(testCase.Tag, tag);

                tag[0] ^= 1;

                byte[] plaintext = RandomNumberGenerator.GetBytes(testCase.Plaintext.Length);
                Assert.Throws <CryptographicException>(
                    () => chaChaPoly.Decrypt(testCase.Nonce, ciphertext, tag, plaintext, testCase.AssociatedData));
                Assert.Equal(new byte[plaintext.Length], plaintext);
            }
        }
Exemple #17
0
        public void ChaCha20Poly1305TestVector2()
        {
            // https://tools.ietf.org/html/rfc8439

            // Arrange
            foreach (var test in Rfc8439TestVector.Rfc7634AeadTestVectors)
            {
                // Act
                var aead = new ChaCha20Poly1305(test.Key);
                var ct   = aead.Encrypt(test.PlainText, test.Aad, test.Nonce);
                Assert.That(ct, Is.EqualTo(CryptoBytes.Combine(test.CipherText, test.Tag)));

                var output = aead.Decrypt(ct, test.Aad, test.Nonce);

                // Assert
                //Assert.That(output, Is.EqualTo(test.PlainText));
                Assert.IsTrue(CryptoBytes.ConstantTimeEquals(test.PlainText, output));
            }
        }
Exemple #18
0
        public static void ValidNonceAndTagSize()
        {
            const int dataLength = 35;

            byte[] plaintext  = Enumerable.Range(1, dataLength).Select((x) => (byte)x).ToArray();
            byte[] ciphertext = new byte[dataLength];
            byte[] key        = RandomNumberGenerator.GetBytes(KeySizeInBytes);
            byte[] nonce      = RandomNumberGenerator.GetBytes(NonceSizeInBytes);
            byte[] tag        = new byte[TagSizeInBytes];

            using (var chaChaPoly = new ChaCha20Poly1305(key))
            {
                chaChaPoly.Encrypt(nonce, plaintext, ciphertext, tag);

                byte[] decrypted = new byte[dataLength];
                chaChaPoly.Decrypt(nonce, ciphertext, tag, decrypted);
                Assert.Equal(plaintext, decrypted);
            }
        }
Exemple #19
0
        public static void Test(string[] testVector)
        {
            var plaintext  = testVector[0];
            var aad        = testVector[1];
            var key        = testVector[2];
            var nonce      = testVector[3];
            var ciphertext = testVector[4];
            var tag        = testVector[5];

            var a = new ChaCha20Poly1305();

            using (var k = Key.Import(a, key.DecodeHex(), KeyBlobFormat.RawSymmetricKey))
            {
                var b = a.Encrypt(k, new Nonce(nonce.DecodeHex(), 0), aad.DecodeHex(), plaintext.DecodeHex());
                Assert.Equal((ciphertext + tag).DecodeHex(), b);

                var r = a.Decrypt(k, new Nonce(nonce.DecodeHex(), 0), aad.DecodeHex(), b);
                Assert.Equal(plaintext.DecodeHex(), r);
            }
        }
Exemple #20
0
        public static void EncryptTamperAADDecrypt(int dataLength, int additionalDataLength)
        {
            byte[] additionalData = new byte[additionalDataLength];
            RandomNumberGenerator.Fill(additionalData);

            byte[] plaintext  = Enumerable.Range(1, dataLength).Select((x) => (byte)x).ToArray();
            byte[] ciphertext = new byte[dataLength];
            byte[] key        = RandomNumberGenerator.GetBytes(KeySizeInBytes);
            byte[] nonce      = RandomNumberGenerator.GetBytes(NonceSizeInBytes);
            byte[] tag        = new byte[TagSizeInBytes];

            using (var chaChaPoly = new ChaCha20Poly1305(key))
            {
                chaChaPoly.Encrypt(nonce, plaintext, ciphertext, tag, additionalData);

                additionalData[0] ^= 1;

                byte[] decrypted = new byte[dataLength];
                Assert.Throws <CryptographicException>(
                    () => chaChaPoly.Decrypt(nonce, ciphertext, tag, decrypted, additionalData));
            }
        }
        public int DecryptWithAd(ReadOnlySpan <byte> ad, ReadOnlySpan <byte> ciphertext, Span <byte> plaintext)
        {
            Debug.Assert(_key.Length == Aead.KEY_SIZE);
            Debug.Assert(ciphertext.Length >= Aead.TAG_SIZE);
            Debug.Assert(plaintext.Length >= ciphertext.Length - Aead.TAG_SIZE);

            Span <byte> nonce = stackalloc byte[Aead.NONCE_SIZE];

            BinaryPrimitives.WriteUInt64LittleEndian(nonce.Slice(4), _nonce);

            var cipher = new ChaCha20Poly1305(_key);

            var cipherTextWithoutTag = ciphertext.Slice(0, ciphertext.Length - Aead.TAG_SIZE);
            var tag = ciphertext.Slice(ciphertext.Length - Aead.TAG_SIZE);

            _logger.LogDebug($"Decrypting text length - {plaintext.Length} with nonce - {_nonce}");

            cipher.Decrypt(nonce, cipherTextWithoutTag, tag, plaintext, ad);

            _nonce++;

            return(cipherTextWithoutTag.Length);
        }
Exemple #22
0
        public void ModifiedAssociatedDataFails()
        {
            var rnd = new Random();
            var key = new byte[Snuffle.KEY_SIZE_IN_BYTES];

            rnd.NextBytes(key);

            var aead = new ChaCha20Poly1305(key);
            var aad  = new byte[0];

            for (var msgSize = 0; msgSize < 75; msgSize++)
            {
                var message = new byte[msgSize];
                rnd.NextBytes(message);

                // encrypting with aad as a 0-length array
                var ciphertext = aead.Encrypt(message, aad);
                var decrypted  = aead.Decrypt(ciphertext, aad);
                //Assert.AreEqual(message, decrypted);
                Assert.IsTrue(CryptoBytes.ConstantTimeEquals(message, decrypted));

                var decrypted2 = aead.Decrypt(ciphertext, null);
                //Assert.AreEqual(message, decrypted2);
                Assert.IsTrue(CryptoBytes.ConstantTimeEquals(message, decrypted2));

                var badAad = new byte[] { 1, 2, 3 };
                Assert.Throws <CryptographicException>(() => aead.Decrypt(ciphertext, badAad), SnufflePoly1305.AEAD_EXCEPTION_INVALID_TAG);

                // encrypting with aad equal to null
                ciphertext = aead.Encrypt(message, null);
                decrypted  = aead.Decrypt(ciphertext, aad);
                //Assert.AreEqual(message, decrypted);
                Assert.IsTrue(CryptoBytes.ConstantTimeEquals(message, decrypted));

                decrypted2 = aead.Decrypt(ciphertext, null);
                //Assert.AreEqual(message, decrypted2);
                Assert.IsTrue(CryptoBytes.ConstantTimeEquals(message, decrypted2));

                Assert.Throws <CryptographicException>(() => aead.Decrypt(ciphertext, badAad), SnufflePoly1305.AEAD_EXCEPTION_INVALID_TAG);
            }
        }
Exemple #23
0
        public byte[] DecryptWithNonce(Tests.Vectors.Rfc8439TestVector test)
        {
            var aead = new ChaCha20Poly1305(test.Key);

            return(aead.Decrypt(CryptoBytes.Combine(test.CipherText, test.Tag), test.Aad, test.Nonce));
        }
Exemple #24
0
        public void WycheproofTestVectors()
        {
            var json = GetWycheproofTestVector();

            var vector = JsonConvert.DeserializeObject <WycheproofVector>(json); //Utf8Json.JsonSerializer.Deserialize<WycheproofVector>(json);

            var errors = 0;

            foreach (var group in vector.TestGroups)
            {
                foreach (var test in group.Tests)
                {
                    var id = $"TestCase {test.TcId}";
                    if (!string.IsNullOrEmpty(test.Comment))
                    {
                        id += $" ({test.Comment})";
                    }

                    var iv         = CryptoBytes.FromHexString(test.Iv);
                    var key        = CryptoBytes.FromHexString(test.Key);
                    var msg        = CryptoBytes.FromHexString(test.Msg);
                    var aad        = CryptoBytes.FromHexString(test.Aad);
                    var ct         = CryptoBytes.FromHexString(test.Ct);
                    var tag        = CryptoBytes.FromHexString(test.Tag);
                    var ciphertext = iv.Concat(ct).Concat(tag).ToArray();

                    // Result is one of "valid", "invalid", "acceptable".
                    // "valid" are test vectors with matching plaintext, ciphertext and tag.
                    // "invalid" are test vectors with invalid parameters or invalid ciphertext and tag.
                    // "acceptable" are test vectors with weak parameters or legacy formats.

                    var result = test.Result;

                    try
                    {
                        var aead      = new ChaCha20Poly1305(key);
                        var decrypted = aead.Decrypt(ciphertext, aad);

                        if (test.Result == "invalid")
                        {
                            TestContext.WriteLine($"FAIL {id}: accepting invalid ciphertext, cleartext: {test.Msg}, decrypted: {CryptoBytes.ToHexStringLower(decrypted)}");
                            errors++;

                            continue;
                        }

                        if (!CryptoBytes.ConstantTimeEquals(msg, decrypted))
                        {
                            TestContext.WriteLine($"FAIL {id}: incorrect decryption, result: {CryptoBytes.ToHexStringLower(decrypted)}, expected: {test.Msg}");
                            errors++;
                        }
                    }
                    catch (Exception ex)
                    {
                        if (test.Result == "valid")
                        {
                            TestContext.WriteLine($"FAIL {id}: cannot decrypt, exception: {ex}");
                            errors++;
                        }
                    }
                }
            }

            Assert.AreEqual(0, errors);
        }