2
0

TripleDesCipherTest.Gen.cs.txt 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. // Used to generate tests in TripleDesCipherTest.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. ["cbc"] = ("(byte[])iv.Clone(), CipherMode.CBC", CipherMode.CBC),
  8. ["cfb"] = ("(byte[])iv.Clone(), CipherMode.CFB", CipherMode.CFB),
  9. };
  10. Random random = new(123);
  11. using IndentedTextWriter tw = new(Console.Out);
  12. foreach ((string mode, (string modeCode, CipherMode? bclMode)) in modes)
  13. {
  14. foreach (int inputLength in new int[] { 8, 17, 32 })
  15. {
  16. foreach (bool pad in new bool[] { false, true })
  17. {
  18. // It is not allowed to use no padding on non-block lengths
  19. // It makes sense in cfb, ctr and ofb modes
  20. if (!pad && inputLength % 8 != 0 && mode is not "cfb")
  21. {
  22. continue;
  23. }
  24. // It does not make sense to test padding for stream cipher modes
  25. // (and the OpenSSL, BCL implementations differ)
  26. if (pad && mode is "cfb")
  27. {
  28. continue;
  29. }
  30. byte[] input = new byte[inputLength];
  31. random.NextBytes(input);
  32. byte[] key = new byte[64 * 3 / 8];
  33. random.NextBytes(key);
  34. byte[] iv = new byte[8];
  35. random.NextBytes(iv);
  36. StringBuilder openSslCmd = new();
  37. openSslCmd.Append($"echo -n -e '{string.Join("", input.Select(b => $"\\x{b:x2}"))}' |");
  38. openSslCmd.Append($" openssl enc -e -des-ede3-{mode}");
  39. openSslCmd.Append($" -K {Convert.ToHexString(key)}");
  40. openSslCmd.Append($" -iv {Convert.ToHexString(iv)}");
  41. if (!pad)
  42. {
  43. openSslCmd.Append(" -nopad");
  44. }
  45. ProcessStartInfo pi = new("wsl", openSslCmd.ToString())
  46. {
  47. RedirectStandardOutput = true,
  48. RedirectStandardError = true,
  49. };
  50. byte[] expected;
  51. string error;
  52. using (MemoryStream ms = new())
  53. {
  54. var p = Process.Start(pi);
  55. p.StandardOutput.BaseStream.CopyTo(ms);
  56. error = p.StandardError.ReadToEnd();
  57. p.WaitForExit();
  58. expected = ms.ToArray();
  59. }
  60. tw.WriteLine("[TestMethod]");
  61. tw.WriteLine($"public void TripleDes_{mode.ToUpper()}_Length{inputLength}_{(pad ? "Pad" : "NoPad")}()");
  62. tw.WriteLine("{");
  63. tw.Indent++;
  64. WriteBytes(input);
  65. WriteBytes(key);
  66. WriteBytes(iv);
  67. tw.WriteLine();
  68. if (!string.IsNullOrWhiteSpace(error))
  69. {
  70. tw.WriteLine($"// {openSslCmd}");
  71. tw.WriteLine($"Assert.Fail(@\"{error}\");");
  72. tw.Indent--;
  73. tw.WriteLine("}");
  74. tw.WriteLine();
  75. continue;
  76. }
  77. tw.WriteLine($"// {openSslCmd} | hd"); // pipe to hexdump
  78. WriteBytes(expected);
  79. tw.WriteLine();
  80. tw.WriteLine($"var actual = new TripleDesCipher(key, {modeCode}, pkcs7Padding: {(pad ? "true" : "false")}).Encrypt(input);");
  81. tw.WriteLine();
  82. tw.WriteLine($"CollectionAssert.AreEqual(expected, actual);");
  83. if (bclMode is not CipherMode.CFB)
  84. {
  85. // Verify the OpenSSL result is the same as the .NET BCL, just to be sure
  86. TripleDES bcl = TripleDES.Create();
  87. bcl.Key = key;
  88. bcl.IV = iv;
  89. bcl.FeedbackSize = 8 * 8; // .NET is CFB1 by default, OpenSSL is CFB8
  90. bcl.Mode = bclMode.Value;
  91. bcl.Padding = pad ? PaddingMode.PKCS7 : PaddingMode.None;
  92. byte[] bclBytes = bcl.CreateEncryptor().TransformFinalBlock(input, 0, input.Length);
  93. if (!bclBytes.AsSpan().SequenceEqual(expected))
  94. {
  95. tw.WriteLine();
  96. tw.WriteLine(@"Assert.Inconclusive(@""OpenSSL does not match the .NET BCL");
  97. tw.Indent++;
  98. tw.WriteLine($@"OpenSSL: {Convert.ToHexString(expected)}");
  99. tw.WriteLine($@"BCL: {Convert.ToHexString(bclBytes)}"");");
  100. tw.Indent--;
  101. }
  102. }
  103. tw.WriteLine();
  104. tw.WriteLine($"var decrypted = new TripleDesCipher(key, {modeCode}, pkcs7Padding: {(pad ? "true" : "false")}).Decrypt(actual);");
  105. tw.WriteLine();
  106. tw.WriteLine($"CollectionAssert.AreEqual(input, decrypted);");
  107. tw.Indent--;
  108. tw.WriteLine("}");
  109. tw.WriteLine();
  110. }
  111. }
  112. }
  113. void WriteBytes(byte[] bytes, [CallerArgumentExpression(nameof(bytes))] string name = null)
  114. {
  115. tw.WriteLine($"var {name} = new byte[]");
  116. tw.WriteLine("{");
  117. tw.Indent++;
  118. foreach (byte[] chunk in bytes.Chunk(16))
  119. {
  120. tw.WriteLine(string.Join(", ", chunk.Select(b => $"0x{b:x2}")) + ',');
  121. }
  122. tw.Indent--;
  123. tw.WriteLine("};");
  124. }