AesCipherTest.Gen.cs.txt 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. // Used to generate tests in AesCipherTest.cs
  2. // The script works by running "openssl enc [...]" (via WSL) to generate the
  3. // expected encrypted values, and also verifies those values against the .NET
  4. // BCL implementation as an extra validation before generating the tests.
  5. Dictionary<string, (string, CipherMode?)> modes = new()
  6. {
  7. ["ecb"] = ("mode: null", CipherMode.ECB),
  8. ["cbc"] = ("new CbcCipherMode((byte[])iv.Clone())", CipherMode.CBC),
  9. ["cfb"] = ("new CfbCipherMode((byte[])iv.Clone())", CipherMode.CFB),
  10. ["ctr"] = ("new CtrCipherMode((byte[])iv.Clone())", null),
  11. ["ofb"] = ("new OfbCipherMode((byte[])iv.Clone())", CipherMode.OFB),
  12. };
  13. Random random = new(123);
  14. using IndentedTextWriter tw = new(Console.Out);
  15. foreach ((string mode, (string modeCode, CipherMode? bclMode)) in modes)
  16. {
  17. foreach (int keySize in new int[] { 128, 192, 256 })
  18. {
  19. foreach (int inputLength in new int[] { 16, 32, 64 })
  20. {
  21. byte[] input = new byte[inputLength];
  22. random.NextBytes(input);
  23. byte[] key = new byte[keySize / 8];
  24. random.NextBytes(key);
  25. byte[] iv = new byte[16];
  26. random.NextBytes(iv);
  27. StringBuilder openSslCmd = new();
  28. openSslCmd.Append($"echo -n -e '{string.Join("", input.Select(b => $"\\x{b:x2}"))}' |");
  29. openSslCmd.Append($" openssl enc -e -aes-{keySize}-{mode}");
  30. openSslCmd.Append($" -K {Convert.ToHexString(key)}");
  31. if (mode != "ecb")
  32. {
  33. openSslCmd.Append($" -iv {Convert.ToHexString(iv)}");
  34. }
  35. openSslCmd.Append(" -nopad");
  36. ProcessStartInfo pi = new("wsl", openSslCmd.ToString())
  37. {
  38. RedirectStandardOutput = true,
  39. RedirectStandardError = true,
  40. };
  41. byte[] expected;
  42. string error;
  43. using (MemoryStream ms = new())
  44. {
  45. var p = Process.Start(pi);
  46. p.StandardOutput.BaseStream.CopyTo(ms);
  47. error = p.StandardError.ReadToEnd();
  48. p.WaitForExit();
  49. expected = ms.ToArray();
  50. }
  51. tw.WriteLine("[TestMethod]");
  52. tw.WriteLine($"public void AES_{mode.ToUpper()}_{keySize}_Length{inputLength}()");
  53. tw.WriteLine("{");
  54. tw.Indent++;
  55. WriteBytes(input);
  56. WriteBytes(key);
  57. if (mode != "ecb")
  58. {
  59. WriteBytes(iv);
  60. }
  61. tw.WriteLine();
  62. if (!string.IsNullOrWhiteSpace(error))
  63. {
  64. tw.WriteLine($"// {openSslCmd}");
  65. tw.WriteLine($"Assert.Fail(@\"{error}\");");
  66. tw.Indent--;
  67. tw.WriteLine("}");
  68. tw.WriteLine();
  69. continue;
  70. }
  71. tw.WriteLine($"// {openSslCmd} | hd"); // pipe to hexdump
  72. WriteBytes(expected);
  73. tw.WriteLine();
  74. tw.WriteLine($"var actual = new AesCipher(key, {modeCode}, padding: null).Encrypt(input);");
  75. tw.WriteLine();
  76. tw.WriteLine($"CollectionAssert.AreEqual(expected, actual);");
  77. if (bclMode is not null and not CipherMode.OFB)
  78. {
  79. // Verify the OpenSSL result is the same as the .NET BCL, just to be sure
  80. Aes bcl = Aes.Create();
  81. bcl.Key = key;
  82. bcl.IV = iv;
  83. bcl.Mode = bclMode.Value;
  84. bcl.Padding = PaddingMode.None;
  85. bcl.FeedbackSize = 128; // .NET is CFB8 by default, OpenSSL is CFB128
  86. byte[] bclBytes = bcl.CreateEncryptor().TransformFinalBlock(input, 0, input.Length);
  87. if (!bclBytes.AsSpan().SequenceEqual(expected))
  88. {
  89. tw.WriteLine();
  90. tw.WriteLine(@"Assert.Inconclusive(@""OpenSSL does not match the .NET BCL");
  91. tw.Indent++;
  92. tw.WriteLine($@"OpenSSL: {Convert.ToHexString(expected)}");
  93. tw.WriteLine($@"BCL: {Convert.ToHexString(bclBytes)}"");");
  94. tw.Indent--;
  95. }
  96. }
  97. tw.WriteLine();
  98. tw.WriteLine($"var decrypted = new AesCipher(key, {modeCode}, padding: null).Decrypt(actual);");
  99. tw.WriteLine();
  100. tw.WriteLine($"CollectionAssert.AreEqual(input, decrypted);");
  101. tw.Indent--;
  102. tw.WriteLine("}");
  103. tw.WriteLine();
  104. }
  105. }
  106. }
  107. void WriteBytes(byte[] bytes, [CallerArgumentExpression(nameof(bytes))] string name = null)
  108. {
  109. tw.WriteLine($"var {name} = new byte[]");
  110. tw.WriteLine("{");
  111. tw.Indent++;
  112. foreach (byte[] chunk in bytes.Chunk(16))
  113. {
  114. tw.WriteLine(string.Join(", ", chunk.Select(b => $"0x{b:x2}")) + ',');
  115. }
  116. tw.Indent--;
  117. tw.WriteLine("};");
  118. }