| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 | // Used to generate tests in AesCipherTest.cs// The script works by running "openssl enc [...]" (via WSL) to generate the// expected encrypted values, and also verifies those values against the .NET// BCL implementation as an extra validation before generating the tests.Dictionary<string, (string, CipherMode?)> modes = new(){    ["ecb"] = ("iv: null, AesCipherMode.ECB", CipherMode.ECB),    ["cbc"] = ("(byte[])iv.Clone(), AesCipherMode.CBC", CipherMode.CBC),    ["cfb"] = ("(byte[])iv.Clone(), AesCipherMode.CFB", CipherMode.CFB),    ["ctr"] = ("(byte[])iv.Clone(), AesCipherMode.CTR", null),    ["ofb"] = ("(byte[])iv.Clone(), AesCipherMode.OFB", CipherMode.OFB),};Random random = new(123);using IndentedTextWriter tw = new(Console.Out);foreach ((string mode, (string modeCode, CipherMode? bclMode)) in modes){    foreach (int keySize in new int[] { 128, 192, 256 })    {        foreach (int inputLength in new int[] { 16, 35, 64 })        {            foreach (bool pad in new bool[] { false, true })            {                // It is not allowed to use no padding on non-block lengths                // It makes sense in cfb, ctr and ofb modes                if (!pad && inputLength % 16 != 0 && mode is not ("cfb" or "ctr" or "ofb"))                {                    continue;                }                // It does not make sense to test padding for stream cipher modes                // (and the OpenSSL, BCL implementations differ)                if (pad && mode is "cfb" or "ctr" or "ofb")                {                    continue;                }                byte[] input = new byte[inputLength];                random.NextBytes(input);                byte[] key = new byte[keySize / 8];                random.NextBytes(key);                byte[] iv = new byte[16];                random.NextBytes(iv);                StringBuilder openSslCmd = new();                openSslCmd.Append($"echo -n -e '{string.Join("", input.Select(b => $"\\x{b:x2}"))}' |");                openSslCmd.Append($" openssl enc -e -aes-{keySize}-{mode}");                openSslCmd.Append($" -K {Convert.ToHexString(key)}");                if (mode != "ecb")                {                    openSslCmd.Append($" -iv {Convert.ToHexString(iv)}");                }                if (!pad)                {                    openSslCmd.Append(" -nopad");                }                ProcessStartInfo pi = new("wsl", openSslCmd.ToString())                {                    RedirectStandardOutput = true,                    RedirectStandardError = true,                };                byte[] expected;                string error;                using (MemoryStream ms = new())                {                    var p = Process.Start(pi);                    p.StandardOutput.BaseStream.CopyTo(ms);                    error = p.StandardError.ReadToEnd();                    p.WaitForExit();                    expected = ms.ToArray();                }                tw.WriteLine("[TestMethod]");                tw.WriteLine($"public void AES_{mode.ToUpper()}_{keySize}_Length{inputLength}_{(pad ? "Pad" : "NoPad")}()");                tw.WriteLine("{");                tw.Indent++;                WriteBytes(input);                WriteBytes(key);                if (mode != "ecb")                {                    WriteBytes(iv);                }                tw.WriteLine();                if (!string.IsNullOrWhiteSpace(error))                {                    tw.WriteLine($"// {openSslCmd}");                    tw.WriteLine($"Assert.Fail(@\"{error}\");");                    tw.Indent--;                    tw.WriteLine("}");                    tw.WriteLine();                    continue;                }                tw.WriteLine($"// {openSslCmd} | hd"); // pipe to hexdump                WriteBytes(expected);                tw.WriteLine();                tw.WriteLine($"var actual = new AesCipher(key, {modeCode}, pkcs7Padding: {(pad ? "true" : "false")}).Encrypt(input);");                tw.WriteLine();                tw.WriteLine($"CollectionAssert.AreEqual(expected, actual);");                if (bclMode is not null and not CipherMode.OFB and not CipherMode.CFB)                {                    // Verify the OpenSSL result is the same as the .NET BCL, just to be sure                    Aes bcl = Aes.Create();                    bcl.Key = key;                    bcl.IV = iv;                    bcl.Mode = bclMode.Value;                    bcl.Padding = pad ? PaddingMode.PKCS7 : PaddingMode.None;                    bcl.FeedbackSize = 128; // .NET is CFB8 by default, OpenSSL is CFB128                    byte[] bclBytes = bcl.CreateEncryptor().TransformFinalBlock(input, 0, input.Length);                    if (!bclBytes.AsSpan().SequenceEqual(expected))                    {                        tw.WriteLine();                        tw.WriteLine(@"Assert.Inconclusive(@""OpenSSL does not match the .NET BCL");                        tw.Indent++;                        tw.WriteLine($@"OpenSSL: {Convert.ToHexString(expected)}");                        tw.WriteLine($@"BCL:     {Convert.ToHexString(bclBytes)}"");");                        tw.Indent--;                    }                }                tw.WriteLine();                tw.WriteLine($"var decrypted = new AesCipher(key, {modeCode}, pkcs7Padding: {(pad ? "true" : "false")}).Decrypt(actual);");                tw.WriteLine();                tw.WriteLine($"CollectionAssert.AreEqual(input, decrypted);");                tw.Indent--;                tw.WriteLine("}");                tw.WriteLine();            }        }    }}void WriteBytes(byte[] bytes, [CallerArgumentExpression(nameof(bytes))] string name = null){    tw.WriteLine($"var {name} = new byte[]");    tw.WriteLine("{");    tw.Indent++;    foreach (byte[] chunk in bytes.Chunk(16))    {        tw.WriteLine(string.Join(", ", chunk.Select(b => $"0x{b:x2}")) + ',');    }    tw.Indent--;    tw.WriteLine("};");}
 |