Преглед изворни кода

Use System.Security.Cryptography for TripleDesCipher (#1546)

* Use System.Security.Cryptography in DesCipher and TripleDesCipher; Fall back to use BouncyCastle if BCL doesn't support

* Drop DesCipher; Replace PKCS7Padding with BouncyCastle's implementation.

* Restore `CbcCipherMode`

* Restore AesCipherMode; Use BlockImpl instead of BouncyCastleImpl for 3DES-CFB on lower targets.

* Restore the xml doc comment
Scott Xu пре 10 месеци
родитељ
комит
14c652cb45
26 измењених фајлова са 751 додато и 988 уклоњено
  1. 0 1
      README.md
  2. 3 2
      src/Renci.SshNet/ConnectionInfo.cs
  3. 3 2
      src/Renci.SshNet/PrivateKeyFile.OpenSSH.cs
  4. 4 7
      src/Renci.SshNet/PrivateKeyFile.PKCS1.cs
  5. 3 2
      src/Renci.SshNet/PrivateKeyFile.SSHCOM.cs
  6. 0 3
      src/Renci.SshNet/PrivateKeyFile.cs
  7. 7 3
      src/Renci.SshNet/Security/Cryptography/BlockCipher.cs
  8. 12 17
      src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.BclImpl.cs
  9. 6 14
      src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.BlockImpl.cs
  10. 2 12
      src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.CtrImpl.cs
  11. 8 18
      src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.cs
  12. 0 65
      src/Renci.SshNet/Security/Cryptography/Ciphers/CipherPadding.cs
  13. 0 479
      src/Renci.SshNet/Security/Cryptography/Ciphers/DesCipher.cs
  14. 0 72
      src/Renci.SshNet/Security/Cryptography/Ciphers/Paddings/PKCS7Padding.cs
  15. 130 0
      src/Renci.SshNet/Security/Cryptography/Ciphers/TripleDesCipher.BclImpl.cs
  16. 48 0
      src/Renci.SshNet/Security/Cryptography/Ciphers/TripleDesCipher.BlockImpl.cs
  17. 51 119
      src/Renci.SshNet/Security/Cryptography/Ciphers/TripleDesCipher.cs
  18. 0 1
      test/Data/Key.RSA.Encrypted.Des.CBC.12345.pub
  19. 0 30
      test/Data/Key.RSA.Encrypted.Des.CBC.12345.txt
  20. 52 0
      test/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/TripleDesCipherBenchmarks.cs
  21. 0 1
      test/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs
  22. 6 5
      test/Renci.SshNet.Tests/Classes/Security/Cryptography/BlockCipherTest.cs
  23. 0 58
      test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/DesCipherTest.cs
  24. 0 63
      test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/Paddings/PKCS7PaddingTest.cs
  25. 151 0
      test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/TripleDesCipherTest.Gen.cs.txt
  26. 265 14
      test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/TripleDesCipherTest.cs

+ 0 - 1
README.md

@@ -121,7 +121,6 @@ The main types provided by this library are:
 Private keys in OpenSSL traditional PEM format can be encrypted using one of the following cipher methods:
 * DES-EDE3-CBC
 * DES-EDE3-CFB
-* DES-CBC
 * AES-128-CBC
 * AES-192-CBC
 * AES-256-CBC

+ 3 - 2
src/Renci.SshNet/ConnectionInfo.cs

@@ -12,7 +12,8 @@ using Renci.SshNet.Messages.Connection;
 using Renci.SshNet.Security;
 using Renci.SshNet.Security.Cryptography;
 using Renci.SshNet.Security.Cryptography.Ciphers;
-using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
+
+using CipherMode = System.Security.Cryptography.CipherMode;
 
 namespace Renci.SshNet
 {
@@ -372,7 +373,7 @@ namespace Renci.SshNet
                     { "aes128-cbc", new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)) },
                     { "aes192-cbc", new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)) },
                     { "aes256-cbc", new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)) },
-                    { "3des-cbc", new CipherInfo(192, (key, iv) => new TripleDesCipher(key, new CbcCipherMode(iv), padding: null)) },
+                    { "3des-cbc", new CipherInfo(192, (key, iv) => new TripleDesCipher(key, iv, CipherMode.CBC, pkcs7Padding: false)) },
                 };
 
             HmacAlgorithms = new Dictionary<string, HashInfo>

+ 3 - 2
src/Renci.SshNet/PrivateKeyFile.OpenSSH.cs

@@ -8,7 +8,8 @@ using Renci.SshNet.Common;
 using Renci.SshNet.Security;
 using Renci.SshNet.Security.Cryptography;
 using Renci.SshNet.Security.Cryptography.Ciphers;
-using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
+
+using CipherMode = System.Security.Cryptography.CipherMode;
 
 namespace Renci.SshNet
 {
@@ -91,7 +92,7 @@ namespace Renci.SshNet
                     {
                         case "3des-cbc":
                             ivLength = 8;
-                            cipherInfo = new CipherInfo(192, (key, iv) => new TripleDesCipher(key, new CbcCipherMode(iv), padding: null));
+                            cipherInfo = new CipherInfo(192, (key, iv) => new TripleDesCipher(key, iv, CipherMode.CBC, pkcs7Padding: false));
                             break;
                         case "aes128-cbc":
                             cipherInfo = new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false));

+ 4 - 7
src/Renci.SshNet/PrivateKeyFile.PKCS1.cs

@@ -9,8 +9,8 @@ using System.Text;
 using Renci.SshNet.Common;
 using Renci.SshNet.Security;
 using Renci.SshNet.Security.Cryptography.Ciphers;
-using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
-using Renci.SshNet.Security.Cryptography.Ciphers.Paddings;
+
+using CipherMode = System.Security.Cryptography.CipherMode;
 
 namespace Renci.SshNet
 {
@@ -51,13 +51,10 @@ namespace Renci.SshNet
                     switch (_cipherName)
                     {
                         case "DES-EDE3-CBC":
-                            cipher = new CipherInfo(192, (key, iv) => new TripleDesCipher(key, new CbcCipherMode(iv), new PKCS7Padding()));
+                            cipher = new CipherInfo(192, (key, iv) => new TripleDesCipher(key, iv, CipherMode.CBC, pkcs7Padding: true));
                             break;
                         case "DES-EDE3-CFB":
-                            cipher = new CipherInfo(192, (key, iv) => new TripleDesCipher(key, new CfbCipherMode(iv), padding: null));
-                            break;
-                        case "DES-CBC":
-                            cipher = new CipherInfo(64, (key, iv) => new DesCipher(key, new CbcCipherMode(iv), new PKCS7Padding()));
+                            cipher = new CipherInfo(192, (key, iv) => new TripleDesCipher(key, iv, CipherMode.CFB, pkcs7Padding: false));
                             break;
                         case "AES-128-CBC":
                             cipher = new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: true));

+ 3 - 2
src/Renci.SshNet/PrivateKeyFile.SSHCOM.cs

@@ -7,7 +7,8 @@ using System.Text;
 using Renci.SshNet.Common;
 using Renci.SshNet.Security;
 using Renci.SshNet.Security.Cryptography.Ciphers;
-using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
+
+using CipherMode = System.Security.Cryptography.CipherMode;
 
 namespace Renci.SshNet
 {
@@ -51,7 +52,7 @@ namespace Renci.SshNet
                     }
 
                     var key = GetCipherKey(_passPhrase, 192 / 8);
-                    var ssh2Сipher = new TripleDesCipher(key, new CbcCipherMode(new byte[8]), padding: null);
+                    var ssh2Сipher = new TripleDesCipher(key, new byte[8], CipherMode.CBC, pkcs7Padding: false);
                     keyData = ssh2Сipher.Decrypt(reader.ReadBytes(blobSize));
                 }
                 else

+ 0 - 3
src/Renci.SshNet/PrivateKeyFile.cs

@@ -48,9 +48,6 @@ namespace Renci.SshNet
     ///         <description>DES-EDE3-CFB</description>
     ///     </item>
     ///     <item>
-    ///         <description>DES-CBC</description>
-    ///     </item>
-    ///     <item>
     ///         <description>AES-128-CBC</description>
     ///     </item>
     ///     <item>

+ 7 - 3
src/Renci.SshNet/Security/Cryptography/BlockCipher.cs

@@ -1,5 +1,7 @@
 using System;
 
+using Org.BouncyCastle.Crypto.Paddings;
+
 using Renci.SshNet.Common;
 using Renci.SshNet.Security.Cryptography.Ciphers;
 using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
@@ -13,7 +15,7 @@ namespace Renci.SshNet.Security.Cryptography
     {
         private readonly CipherMode _mode;
 
-        private readonly CipherPadding _padding;
+        private readonly IBlockCipherPadding _padding;
 
         /// <summary>
         /// Gets the size of the block in bytes.
@@ -56,7 +58,7 @@ namespace Renci.SshNet.Security.Cryptography
         /// <param name="mode">Cipher mode.</param>
         /// <param name="padding">Cipher padding.</param>
         /// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
-        protected BlockCipher(byte[] key, byte blockSize, CipherMode mode, CipherPadding padding)
+        protected BlockCipher(byte[] key, byte blockSize, CipherMode mode, IBlockCipherPadding padding)
             : base(key)
         {
             _blockSize = blockSize;
@@ -81,7 +83,9 @@ namespace Renci.SshNet.Security.Cryptography
             if (_padding is not null)
             {
                 paddingLength = _blockSize - (length % _blockSize);
-                input = _padding.Pad(input, offset, length, paddingLength);
+                input = input.Take(offset, length);
+                Array.Resize(ref input, length + paddingLength);
+                _ = _padding.AddPadding(input, length);
                 length += paddingLength;
                 offset = 0;
             }

+ 12 - 17
src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.BclImpl.cs

@@ -52,6 +52,8 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers
                 {
                     if (_aes.Mode is System.Security.Cryptography.CipherMode.CFB or System.Security.Cryptography.CipherMode.OFB)
                     {
+                        // Manually pad the input for cfb and ofb cipher mode as BCL doesn't support partial block.
+                        // See https://github.com/dotnet/runtime/blob/e7d837da5b1aacd9325a8b8f2214cfaf4d3f0ff6/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SymmetricPadding.cs#L20-L21
                         paddingLength = BlockSize - (length % BlockSize);
                         input = input.Take(offset, length);
                         length += paddingLength;
@@ -69,6 +71,7 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers
 
                 if (paddingLength > 0)
                 {
+                    // Manually unpad the output.
                     Array.Resize(ref output, output.Length - paddingLength);
                 }
 
@@ -89,11 +92,12 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers
                 {
                     if (_aes.Mode is System.Security.Cryptography.CipherMode.CFB or System.Security.Cryptography.CipherMode.OFB)
                     {
+                        // Manually pad the input for cfb and ofb cipher mode as BCL doesn't support partial block.
+                        // See https://github.com/dotnet/runtime/blob/e7d837da5b1aacd9325a8b8f2214cfaf4d3f0ff6/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SymmetricPadding.cs#L20-L21
                         paddingLength = BlockSize - (length % BlockSize);
-                        var newInput = new byte[input.Length + paddingLength];
-                        Buffer.BlockCopy(input, offset, newInput, 0, length);
-                        input = newInput;
-                        length = input.Length;
+                        input = input.Take(offset, length);
+                        length += paddingLength;
+                        Array.Resize(ref input, length);
                         offset = 0;
                     }
                 }
@@ -107,6 +111,7 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers
 
                 if (paddingLength > 0)
                 {
+                    // Manually unpad the output.
                     Array.Resize(ref output, output.Length - paddingLength);
                 }
 
@@ -123,21 +128,11 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers
                 throw new NotImplementedException($"Invalid usage of {nameof(DecryptBlock)}.");
             }
 
-            private void Dispose(bool disposing)
-            {
-                if (disposing)
-                {
-                    _aes.Dispose();
-                    _encryptor.Dispose();
-                    _decryptor.Dispose();
-                }
-            }
-
             public void Dispose()
             {
-                // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
-                Dispose(disposing: true);
-                GC.SuppressFinalize(this);
+                _aes.Dispose();
+                _encryptor.Dispose();
+                _decryptor.Dispose();
             }
         }
     }

+ 6 - 14
src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.BlockImpl.cs

@@ -1,6 +1,8 @@
 using System;
 using System.Security.Cryptography;
 
+using Org.BouncyCastle.Crypto.Paddings;
+
 namespace Renci.SshNet.Security.Cryptography.Ciphers
 {
     public partial class AesCipher
@@ -11,7 +13,7 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers
             private readonly ICryptoTransform _encryptor;
             private readonly ICryptoTransform _decryptor;
 
-            public BlockImpl(byte[] key, CipherMode mode, CipherPadding padding)
+            public BlockImpl(byte[] key, CipherMode mode, IBlockCipherPadding padding)
                 : base(key, 16, mode, padding)
             {
                 var aes = Aes.Create();
@@ -33,21 +35,11 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers
                 return _decryptor.TransformBlock(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
             }
 
-            private void Dispose(bool disposing)
-            {
-                if (disposing)
-                {
-                    _aes.Dispose();
-                    _encryptor.Dispose();
-                    _decryptor.Dispose();
-                }
-            }
-
             public void Dispose()
             {
-                // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
-                Dispose(disposing: true);
-                GC.SuppressFinalize(this);
+                _aes.Dispose();
+                _encryptor.Dispose();
+                _decryptor.Dispose();
             }
         }
     }

+ 2 - 12
src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.CtrImpl.cs

@@ -105,20 +105,10 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers
                 }
             }
 
-            private void Dispose(bool disposing)
-            {
-                if (disposing)
-                {
-                    _aes.Dispose();
-                    _encryptor.Dispose();
-                }
-            }
-
             public void Dispose()
             {
-                // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
-                Dispose(disposing: true);
-                GC.SuppressFinalize(this);
+                _aes.Dispose();
+                _encryptor.Dispose();
             }
         }
     }

+ 8 - 18
src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.cs

@@ -1,8 +1,9 @@
 using System;
 using System.Security.Cryptography;
 
+using Org.BouncyCastle.Crypto.Paddings;
+
 using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
-using Renci.SshNet.Security.Cryptography.Ciphers.Paddings;
 
 namespace Renci.SshNet.Security.Cryptography.Ciphers
 {
@@ -17,8 +18,8 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers
         /// Initializes a new instance of the <see cref="AesCipher"/> class.
         /// </summary>
         /// <param name="key">The key.</param>
-        /// <param name="mode">The mode.</param>
         /// <param name="iv">The IV.</param>
+        /// <param name="mode">The mode.</param>
         /// <param name="pkcs7Padding">Enable PKCS7 padding.</param>
         /// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
         /// <exception cref="ArgumentException">Keysize is not valid for this algorithm.</exception>
@@ -28,13 +29,13 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers
             if (mode == AesCipherMode.OFB)
             {
                 // OFB is not supported on modern .NET
-                _impl = new BlockImpl(key, new OfbCipherMode(iv), pkcs7Padding ? new PKCS7Padding() : null);
+                _impl = new BlockImpl(key, new OfbCipherMode(iv), pkcs7Padding ? new Pkcs7Padding() : null);
             }
 #if !NET6_0_OR_GREATER
             else if (mode == AesCipherMode.CFB)
             {
                 // CFB not supported on NetStandard 2.1
-                _impl = new BlockImpl(key, new CfbCipherMode(iv), pkcs7Padding ? new PKCS7Padding() : null);
+                _impl = new BlockImpl(key, new CfbCipherMode(iv), pkcs7Padding ? new Pkcs7Padding() : null);
             }
 #endif
             else if (mode == AesCipherMode.CTR)
@@ -76,24 +77,13 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers
             return _impl.Decrypt(input, offset, length);
         }
 
-        /// <summary>
-        /// Dispose the instance.
-        /// </summary>
-        /// <param name="disposing">Set to True to dispose of resouces.</param>
-        public void Dispose(bool disposing)
+        /// <inheritdoc/>
+        public void Dispose()
         {
-            if (disposing && _impl is IDisposable disposableImpl)
+            if (_impl is IDisposable disposableImpl)
             {
                 disposableImpl.Dispose();
             }
         }
-
-        /// <inheritdoc/>
-        public void Dispose()
-        {
-            // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
-            Dispose(disposing: true);
-            GC.SuppressFinalize(this);
-        }
     }
 }

+ 0 - 65
src/Renci.SshNet/Security/Cryptography/Ciphers/CipherPadding.cs

@@ -1,65 +0,0 @@
-namespace Renci.SshNet.Security.Cryptography.Ciphers
-{
-    /// <summary>
-    /// Base class for cipher padding implementations.
-    /// </summary>
-    public abstract class CipherPadding
-    {
-        /// <summary>
-        /// Pads the specified input to match the block size.
-        /// </summary>
-        /// <param name="blockSize">Size of the block.</param>
-        /// <param name="input">The input.</param>
-        /// <returns>
-        /// Padded data array.
-        /// </returns>
-        public byte[] Pad(int blockSize, byte[] input)
-        {
-            return Pad(blockSize, input, 0, input.Length);
-        }
-
-        /// <summary>
-        /// Pads the specified input to match the block size.
-        /// </summary>
-        /// <param name="blockSize">Size of the block.</param>
-        /// <param name="input">The input.</param>
-        /// <param name="offset">The zero-based offset in <paramref name="input"/> at which the data to pad starts.</param>
-        /// <param name="length">The number of bytes in <paramref name="input"/> to take into account.</param>
-        /// <returns>
-        /// The padded data array.
-        /// </returns>
-        public abstract byte[] Pad(int blockSize, byte[] input, int offset, int length);
-
-        /// <summary>
-        /// Pads the specified input with a given number of bytes.
-        /// </summary>
-        /// <param name="input">The input.</param>
-        /// <param name="paddinglength">The number of bytes to pad the input with.</param>
-        /// <returns>
-        /// The padded data array.
-        /// </returns>
-        public byte[] Pad(byte[] input, int paddinglength)
-        {
-            return Pad(input, 0, input.Length, paddinglength);
-        }
-
-        /// <summary>
-        /// Pads the specified input with a given number of bytes.
-        /// </summary>
-        /// <param name="input">The input.</param>
-        /// <param name="offset">The zero-based offset in <paramref name="input"/> at which the data to pad starts.</param>
-        /// <param name="length">The number of bytes in <paramref name="input"/> to take into account.</param>
-        /// <param name="paddinglength">The number of bytes to pad the input with.</param>
-        /// <returns>
-        /// The padded data array.
-        /// </returns>
-        public abstract byte[] Pad(byte[] input, int offset, int length, int paddinglength);
-
-        /// <summary>
-        /// Gets the padd count from the specified input.
-        /// </summary>
-        /// <param name="input">The input.</param>
-        /// <returns>The padd count.</returns>
-        public abstract int PadCount(byte[] input);
-    }
-}

+ 0 - 479
src/Renci.SshNet/Security/Cryptography/Ciphers/DesCipher.cs

@@ -1,479 +0,0 @@
-using System;
-using System.Buffers.Binary;
-
-namespace Renci.SshNet.Security.Cryptography.Ciphers
-{
-    /// <summary>
-    /// Implements DES cipher algorithm.
-    /// </summary>
-    public class DesCipher : BlockCipher
-    {
-        private int[] _encryptionKey;
-
-        private int[] _decryptionKey;
-
-        private static readonly short[] Bytebit = { 128, 64, 32, 16, 8, 4, 2, 1 };
-
-        private static readonly int[] Bigbyte =
-        {
-            0x800000, 0x400000, 0x200000, 0x100000,
-            0x080000, 0x040000, 0x020000, 0x010000,
-            0x008000, 0x004000, 0x002000, 0x001000,
-            0x000800, 0x000400, 0x000200, 0x000100,
-            0x000080, 0x000040, 0x000020, 0x000010,
-            0x000008, 0x000004, 0x000002, 0x000001
-        };
-
-        /*
-         * Use the key schedule specified in the Standard (ANSI X3.92-1981).
-         */
-
-        private static readonly byte[] Pc1 =
-        {
-            56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17,
-            9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35,
-            62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21,
-            13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3
-        };
-
-        private static readonly byte[] Totrot =
-        {
-            1, 2, 4, 6, 8, 10, 12, 14,
-            15, 17, 19, 21, 23, 25, 27, 28
-        };
-
-        private static readonly byte[] Pc2 =
-        {
-            13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9,
-            22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1,
-            40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47,
-            43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31
-        };
-
-        private static readonly uint[] Sp1 =
-        {
-            0x01010400, 0x00000000, 0x00010000, 0x01010404,
-            0x01010004, 0x00010404, 0x00000004, 0x00010000,
-            0x00000400, 0x01010400, 0x01010404, 0x00000400,
-            0x01000404, 0x01010004, 0x01000000, 0x00000004,
-            0x00000404, 0x01000400, 0x01000400, 0x00010400,
-            0x00010400, 0x01010000, 0x01010000, 0x01000404,
-            0x00010004, 0x01000004, 0x01000004, 0x00010004,
-            0x00000000, 0x00000404, 0x00010404, 0x01000000,
-            0x00010000, 0x01010404, 0x00000004, 0x01010000,
-            0x01010400, 0x01000000, 0x01000000, 0x00000400,
-            0x01010004, 0x00010000, 0x00010400, 0x01000004,
-            0x00000400, 0x00000004, 0x01000404, 0x00010404,
-            0x01010404, 0x00010004, 0x01010000, 0x01000404,
-            0x01000004, 0x00000404, 0x00010404, 0x01010400,
-            0x00000404, 0x01000400, 0x01000400, 0x00000000,
-            0x00010004, 0x00010400, 0x00000000, 0x01010004
-        };
-
-        private static readonly uint[] Sp2 =
-        {
-            0x80108020, 0x80008000, 0x00008000, 0x00108020,
-            0x00100000, 0x00000020, 0x80100020, 0x80008020,
-            0x80000020, 0x80108020, 0x80108000, 0x80000000,
-            0x80008000, 0x00100000, 0x00000020, 0x80100020,
-            0x00108000, 0x00100020, 0x80008020, 0x00000000,
-            0x80000000, 0x00008000, 0x00108020, 0x80100000,
-            0x00100020, 0x80000020, 0x00000000, 0x00108000,
-            0x00008020, 0x80108000, 0x80100000, 0x00008020,
-            0x00000000, 0x00108020, 0x80100020, 0x00100000,
-            0x80008020, 0x80100000, 0x80108000, 0x00008000,
-            0x80100000, 0x80008000, 0x00000020, 0x80108020,
-            0x00108020, 0x00000020, 0x00008000, 0x80000000,
-            0x00008020, 0x80108000, 0x00100000, 0x80000020,
-            0x00100020, 0x80008020, 0x80000020, 0x00100020,
-            0x00108000, 0x00000000, 0x80008000, 0x00008020,
-            0x80000000, 0x80100020, 0x80108020, 0x00108000
-        };
-
-        private static readonly uint[] Sp3 =
-        {
-            0x00000208, 0x08020200, 0x00000000, 0x08020008,
-            0x08000200, 0x00000000, 0x00020208, 0x08000200,
-            0x00020008, 0x08000008, 0x08000008, 0x00020000,
-            0x08020208, 0x00020008, 0x08020000, 0x00000208,
-            0x08000000, 0x00000008, 0x08020200, 0x00000200,
-            0x00020200, 0x08020000, 0x08020008, 0x00020208,
-            0x08000208, 0x00020200, 0x00020000, 0x08000208,
-            0x00000008, 0x08020208, 0x00000200, 0x08000000,
-            0x08020200, 0x08000000, 0x00020008, 0x00000208,
-            0x00020000, 0x08020200, 0x08000200, 0x00000000,
-            0x00000200, 0x00020008, 0x08020208, 0x08000200,
-            0x08000008, 0x00000200, 0x00000000, 0x08020008,
-            0x08000208, 0x00020000, 0x08000000, 0x08020208,
-            0x00000008, 0x00020208, 0x00020200, 0x08000008,
-            0x08020000, 0x08000208, 0x00000208, 0x08020000,
-            0x00020208, 0x00000008, 0x08020008, 0x00020200
-        };
-
-        private static readonly uint[] Sp4 =
-        {
-            0x00802001, 0x00002081, 0x00002081, 0x00000080,
-            0x00802080, 0x00800081, 0x00800001, 0x00002001,
-            0x00000000, 0x00802000, 0x00802000, 0x00802081,
-            0x00000081, 0x00000000, 0x00800080, 0x00800001,
-            0x00000001, 0x00002000, 0x00800000, 0x00802001,
-            0x00000080, 0x00800000, 0x00002001, 0x00002080,
-            0x00800081, 0x00000001, 0x00002080, 0x00800080,
-            0x00002000, 0x00802080, 0x00802081, 0x00000081,
-            0x00800080, 0x00800001, 0x00802000, 0x00802081,
-            0x00000081, 0x00000000, 0x00000000, 0x00802000,
-            0x00002080, 0x00800080, 0x00800081, 0x00000001,
-            0x00802001, 0x00002081, 0x00002081, 0x00000080,
-            0x00802081, 0x00000081, 0x00000001, 0x00002000,
-            0x00800001, 0x00002001, 0x00802080, 0x00800081,
-            0x00002001, 0x00002080, 0x00800000, 0x00802001,
-            0x00000080, 0x00800000, 0x00002000, 0x00802080
-        };
-
-        private static readonly uint[] Sp5 =
-        {
-            0x00000100, 0x02080100, 0x02080000, 0x42000100,
-            0x00080000, 0x00000100, 0x40000000, 0x02080000,
-            0x40080100, 0x00080000, 0x02000100, 0x40080100,
-            0x42000100, 0x42080000, 0x00080100, 0x40000000,
-            0x02000000, 0x40080000, 0x40080000, 0x00000000,
-            0x40000100, 0x42080100, 0x42080100, 0x02000100,
-            0x42080000, 0x40000100, 0x00000000, 0x42000000,
-            0x02080100, 0x02000000, 0x42000000, 0x00080100,
-            0x00080000, 0x42000100, 0x00000100, 0x02000000,
-            0x40000000, 0x02080000, 0x42000100, 0x40080100,
-            0x02000100, 0x40000000, 0x42080000, 0x02080100,
-            0x40080100, 0x00000100, 0x02000000, 0x42080000,
-            0x42080100, 0x00080100, 0x42000000, 0x42080100,
-            0x02080000, 0x00000000, 0x40080000, 0x42000000,
-            0x00080100, 0x02000100, 0x40000100, 0x00080000,
-            0x00000000, 0x40080000, 0x02080100, 0x40000100
-        };
-
-        private static readonly uint[] Sp6 =
-        {
-            0x20000010, 0x20400000, 0x00004000, 0x20404010,
-            0x20400000, 0x00000010, 0x20404010, 0x00400000,
-            0x20004000, 0x00404010, 0x00400000, 0x20000010,
-            0x00400010, 0x20004000, 0x20000000, 0x00004010,
-            0x00000000, 0x00400010, 0x20004010, 0x00004000,
-            0x00404000, 0x20004010, 0x00000010, 0x20400010,
-            0x20400010, 0x00000000, 0x00404010, 0x20404000,
-            0x00004010, 0x00404000, 0x20404000, 0x20000000,
-            0x20004000, 0x00000010, 0x20400010, 0x00404000,
-            0x20404010, 0x00400000, 0x00004010, 0x20000010,
-            0x00400000, 0x20004000, 0x20000000, 0x00004010,
-            0x20000010, 0x20404010, 0x00404000, 0x20400000,
-            0x00404010, 0x20404000, 0x00000000, 0x20400010,
-            0x00000010, 0x00004000, 0x20400000, 0x00404010,
-            0x00004000, 0x00400010, 0x20004010, 0x00000000,
-            0x20404000, 0x20000000, 0x00400010, 0x20004010
-        };
-
-        private static readonly uint[] Sp7 =
-        {
-            0x00200000, 0x04200002, 0x04000802, 0x00000000,
-            0x00000800, 0x04000802, 0x00200802, 0x04200800,
-            0x04200802, 0x00200000, 0x00000000, 0x04000002,
-            0x00000002, 0x04000000, 0x04200002, 0x00000802,
-            0x04000800, 0x00200802, 0x00200002, 0x04000800,
-            0x04000002, 0x04200000, 0x04200800, 0x00200002,
-            0x04200000, 0x00000800, 0x00000802, 0x04200802,
-            0x00200800, 0x00000002, 0x04000000, 0x00200800,
-            0x04000000, 0x00200800, 0x00200000, 0x04000802,
-            0x04000802, 0x04200002, 0x04200002, 0x00000002,
-            0x00200002, 0x04000000, 0x04000800, 0x00200000,
-            0x04200800, 0x00000802, 0x00200802, 0x04200800,
-            0x00000802, 0x04000002, 0x04200802, 0x04200000,
-            0x00200800, 0x00000000, 0x00000002, 0x04200802,
-            0x00000000, 0x00200802, 0x04200000, 0x00000800,
-            0x04000002, 0x04000800, 0x00000800, 0x00200002
-        };
-
-        private static readonly uint[] Sp8 =
-        {
-            0x10001040, 0x00001000, 0x00040000, 0x10041040,
-            0x10000000, 0x10001040, 0x00000040, 0x10000000,
-            0x00040040, 0x10040000, 0x10041040, 0x00041000,
-            0x10041000, 0x00041040, 0x00001000, 0x00000040,
-            0x10040000, 0x10000040, 0x10001000, 0x00001040,
-            0x00041000, 0x00040040, 0x10040040, 0x10041000,
-            0x00001040, 0x00000000, 0x00000000, 0x10040040,
-            0x10000040, 0x10001000, 0x00041040, 0x00040000,
-            0x00041040, 0x00040000, 0x10041000, 0x00001000,
-            0x00000040, 0x10040040, 0x00001000, 0x00041040,
-            0x10001000, 0x00000040, 0x10000040, 0x10040000,
-            0x10040040, 0x10000000, 0x00040000, 0x10001040,
-            0x00000000, 0x10041040, 0x00040040, 0x10000040,
-            0x10040000, 0x10001000, 0x10001040, 0x00000000,
-            0x10041040, 0x00041000, 0x00041000, 0x00001040,
-            0x00001040, 0x00040040, 0x10000000, 0x10041000
-        };
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="DesCipher"/> class.
-        /// </summary>
-        /// <param name="key">The key.</param>
-        /// <param name="mode">The mode.</param>
-        /// <param name="padding">The padding.</param>
-        /// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
-        public DesCipher(byte[] key, CipherMode mode, CipherPadding padding)
-            : base(key, 8, mode, padding)
-        {
-        }
-
-        /// <summary>
-        /// Encrypts the specified region of the input byte array and copies the encrypted data to the specified region of the output byte array.
-        /// </summary>
-        /// <param name="inputBuffer">The input data to encrypt.</param>
-        /// <param name="inputOffset">The offset into the input byte array from which to begin using data.</param>
-        /// <param name="inputCount">The number of bytes in the input byte array to use as data.</param>
-        /// <param name="outputBuffer">The output to which to write encrypted data.</param>
-        /// <param name="outputOffset">The offset into the output byte array from which to begin writing data.</param>
-        /// <returns>
-        /// The number of bytes encrypted.
-        /// </returns>
-        public override int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
-        {
-            if ((inputOffset + BlockSize) > inputBuffer.Length)
-            {
-                throw new ArgumentException("input buffer too short");
-            }
-
-            if ((outputOffset + BlockSize) > outputBuffer.Length)
-            {
-                throw new ArgumentException("output buffer too short");
-            }
-
-            _encryptionKey ??= GenerateWorkingKey(encrypting: true, Key);
-
-            DesFunc(_encryptionKey, inputBuffer, inputOffset, outputBuffer, outputOffset);
-
-            return BlockSize;
-        }
-
-        /// <summary>
-        /// Decrypts the specified region of the input byte array and copies the decrypted data to the specified region of the output byte array.
-        /// </summary>
-        /// <param name="inputBuffer">The input data to decrypt.</param>
-        /// <param name="inputOffset">The offset into the input byte array from which to begin using data.</param>
-        /// <param name="inputCount">The number of bytes in the input byte array to use as data.</param>
-        /// <param name="outputBuffer">The output to which to write decrypted data.</param>
-        /// <param name="outputOffset">The offset into the output byte array from which to begin writing data.</param>
-        /// <returns>
-        /// The number of bytes decrypted.
-        /// </returns>
-        public override int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
-        {
-            if ((inputOffset + BlockSize) > inputBuffer.Length)
-            {
-                throw new ArgumentException("input buffer too short");
-            }
-
-            if ((outputOffset + BlockSize) > outputBuffer.Length)
-            {
-                throw new ArgumentException("output buffer too short");
-            }
-
-            _decryptionKey ??= GenerateWorkingKey(encrypting: false, Key);
-
-            DesFunc(_decryptionKey, inputBuffer, inputOffset, outputBuffer, outputOffset);
-
-            return BlockSize;
-        }
-
-        /// <summary>
-        /// Generates the working key.
-        /// </summary>
-        /// <param name="encrypting">if set to <see langword="true"/> [encrypting].</param>
-        /// <param name="key">The key.</param>
-        /// <returns>Generated working key.</returns>
-        protected int[] GenerateWorkingKey(bool encrypting, byte[] key)
-        {
-            ValidateKey();
-
-            var newKey = new int[32];
-            var pc1m = new bool[56];
-            var pcr = new bool[56];
-
-            for (var j = 0; j < 56; j++)
-            {
-                int l = Pc1[j];
-
-                pc1m[j] = (key[(uint)l >> 3] & Bytebit[l & 07]) != 0;
-            }
-
-            for (var i = 0; i < 16; i++)
-            {
-                int l, m;
-
-                if (encrypting)
-                {
-                    m = i << 1;
-                }
-                else
-                {
-                    m = (15 - i) << 1;
-                }
-
-                var n = m + 1;
-                newKey[m] = newKey[n] = 0;
-
-                for (var j = 0; j < 28; j++)
-                {
-                    l = j + Totrot[i];
-                    if (l < 28)
-                    {
-                        pcr[j] = pc1m[l];
-                    }
-                    else
-                    {
-                        pcr[j] = pc1m[l - 28];
-                    }
-                }
-
-                for (var j = 28; j < 56; j++)
-                {
-                    l = j + Totrot[i];
-                    if (l < 56)
-                    {
-                        pcr[j] = pc1m[l];
-                    }
-                    else
-                    {
-                        pcr[j] = pc1m[l - 28];
-                    }
-                }
-
-                for (var j = 0; j < 24; j++)
-                {
-                    if (pcr[Pc2[j]])
-                    {
-                        newKey[m] |= Bigbyte[j];
-                    }
-
-                    if (pcr[Pc2[j + 24]])
-                    {
-                        newKey[n] |= Bigbyte[j];
-                    }
-                }
-            }
-
-            /*
-             * store the processed key
-             */
-
-            for (var i = 0; i != 32; i += 2)
-            {
-                var i1 = newKey[i];
-                var i2 = newKey[i + 1];
-
-                newKey[i] = (int)((uint)((i1 & 0x00fc0000) << 6) |
-                                   (uint)((i1 & 0x00000fc0) << 10) |
-                                   ((uint)(i2 & 0x00fc0000) >> 10) |
-                                   ((uint)(i2 & 0x00000fc0) >> 6));
-
-                newKey[i + 1] = (int)((uint)((i1 & 0x0003f000) << 12) |
-                                       (uint)((i1 & 0x0000003f) << 16) |
-                                       ((uint)(i2 & 0x0003f000) >> 4) |
-                                       (uint)(i2 & 0x0000003f));
-            }
-
-            return newKey;
-        }
-
-        /// <summary>
-        /// Validates the key.
-        /// </summary>
-        protected virtual void ValidateKey()
-        {
-            var keySize = Key.Length * 8;
-
-            if (keySize != 64)
-            {
-                throw new ArgumentException(string.Format("KeySize '{0}' is not valid for this algorithm.", keySize));
-            }
-        }
-
-        /// <summary>
-        /// Performs DES function.
-        /// </summary>
-        /// <param name="wKey">The w key.</param>
-        /// <param name="input">The input.</param>
-        /// <param name="inOff">The in off.</param>
-        /// <param name="outBytes">The out bytes.</param>
-        /// <param name="outOff">The out off.</param>
-        protected static void DesFunc(int[] wKey, byte[] input, int inOff, byte[] outBytes, int outOff)
-        {
-            var left = BinaryPrimitives.ReadUInt32BigEndian(input.AsSpan(inOff));
-            var right = BinaryPrimitives.ReadUInt32BigEndian(input.AsSpan(inOff + 4));
-
-            var work = ((left >> 4) ^ right) & 0x0f0f0f0f;
-            right ^= work;
-            left ^= work << 4;
-            work = ((left >> 16) ^ right) & 0x0000ffff;
-            right ^= work;
-            left ^= work << 16;
-            work = ((right >> 2) ^ left) & 0x33333333;
-            left ^= work;
-            right ^= work << 2;
-            work = ((right >> 8) ^ left) & 0x00ff00ff;
-            left ^= work;
-            right ^= work << 8;
-            right = (right << 1) | (right >> 31);
-            work = (left ^ right) & 0xaaaaaaaa;
-            left ^= work;
-            right ^= work;
-            left = (left << 1) | (left >> 31);
-
-            for (var round = 0; round < 8; round++)
-            {
-                work = (right << 28) | (right >> 4);
-                work ^= (uint)wKey[(round * 4) + 0];
-                var fval = Sp7[work & 0x3f];
-                fval |= Sp5[(work >> 8) & 0x3f];
-                fval |= Sp3[(work >> 16) & 0x3f];
-                fval |= Sp1[(work >> 24) & 0x3f];
-                work = right ^ (uint)wKey[(round * 4) + 1];
-                fval |= Sp8[work & 0x3f];
-                fval |= Sp6[(work >> 8) & 0x3f];
-                fval |= Sp4[(work >> 16) & 0x3f];
-                fval |= Sp2[(work >> 24) & 0x3f];
-                left ^= fval;
-                work = (left << 28) | (left >> 4);
-                work ^= (uint)wKey[(round * 4) + 2];
-                fval = Sp7[work & 0x3f];
-                fval |= Sp5[(work >> 8) & 0x3f];
-                fval |= Sp3[(work >> 16) & 0x3f];
-                fval |= Sp1[(work >> 24) & 0x3f];
-                work = left ^ (uint)wKey[(round * 4) + 3];
-                fval |= Sp8[work & 0x3f];
-                fval |= Sp6[(work >> 8) & 0x3f];
-                fval |= Sp4[(work >> 16) & 0x3f];
-                fval |= Sp2[(work >> 24) & 0x3f];
-                right ^= fval;
-            }
-
-            right = (right << 31) | (right >> 1);
-            work = (left ^ right) & 0xaaaaaaaa;
-            left ^= work;
-            right ^= work;
-            left = (left << 31) | (left >> 1);
-            work = ((left >> 8) ^ right) & 0x00ff00ff;
-            right ^= work;
-            left ^= work << 8;
-            work = ((left >> 2) ^ right) & 0x33333333;
-            right ^= work;
-            left ^= work << 2;
-            work = ((right >> 16) ^ left) & 0x0000ffff;
-            left ^= work;
-            right ^= work << 16;
-            work = ((right >> 4) ^ left) & 0x0f0f0f0f;
-            left ^= work;
-            right ^= work << 4;
-
-            BinaryPrimitives.WriteUInt32BigEndian(outBytes.AsSpan(outOff), right);
-            BinaryPrimitives.WriteUInt32BigEndian(outBytes.AsSpan(outOff + 4), left);
-        }
-    }
-}

+ 0 - 72
src/Renci.SshNet/Security/Cryptography/Ciphers/Paddings/PKCS7Padding.cs

@@ -1,72 +0,0 @@
-using System;
-
-using Renci.SshNet.Common;
-
-namespace Renci.SshNet.Security.Cryptography.Ciphers.Paddings
-{
-    /// <summary>
-    /// Implements PKCS7 cipher padding.
-    /// </summary>
-    public class PKCS7Padding : CipherPadding
-    {
-        /// <summary>
-        /// Pads the specified input to match the block size.
-        /// </summary>
-        /// <param name="blockSize">The size of the block.</param>
-        /// <param name="input">The input.</param>
-        /// <param name="offset">The zero-based offset in <paramref name="input"/> at which the data to pad starts.</param>
-        /// <param name="length">The number of bytes in <paramref name="input"/> to take into account.</param>
-        /// <returns>
-        /// The padded data array.
-        /// </returns>
-        public override byte[] Pad(int blockSize, byte[] input, int offset, int length)
-        {
-            var numOfPaddedBytes = blockSize - (length % blockSize);
-            return Pad(input, offset, length, numOfPaddedBytes);
-        }
-
-        /// <summary>
-        /// Pads the specified input with a given number of bytes.
-        /// </summary>
-        /// <param name="input">The input.</param>
-        /// <param name="offset">The zero-based offset in <paramref name="input"/> at which the data to pad starts.</param>
-        /// <param name="length">The number of bytes in <paramref name="input"/> to take into account.</param>
-        /// <param name="paddinglength">The number of bytes to pad the input with.</param>
-        /// <returns>
-        /// The padded data array.
-        /// </returns>
-        public override byte[] Pad(byte[] input, int offset, int length, int paddinglength)
-        {
-            var output = new byte[length + paddinglength];
-            Buffer.BlockCopy(input, offset, output, 0, length);
-
-            for (var i = 0; i < paddinglength; i++)
-            {
-                output[length + i] = (byte)paddinglength;
-            }
-
-            return output;
-        }
-
-        /// <inheritdoc/>
-        public override int PadCount(byte[] input)
-        {
-            var padValue = input[input.Length - 1];
-            int count = padValue;
-            var position = input.Length - count;
-
-            var failed = (position | (count - 1)) >> 31;
-            for (var i = 0; i < input.Length; ++i)
-            {
-                failed |= (input[i] ^ padValue) & ~((i - position) >> 31);
-            }
-
-            if (failed != 0)
-            {
-                throw new SshException("pad block corrupted");
-            }
-
-            return count;
-        }
-    }
-}

+ 130 - 0
src/Renci.SshNet/Security/Cryptography/Ciphers/TripleDesCipher.BclImpl.cs

@@ -0,0 +1,130 @@
+using System;
+using System.Security.Cryptography;
+
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Security.Cryptography.Ciphers
+{
+    public partial class TripleDesCipher
+    {
+        private sealed class BclImpl : BlockCipher, IDisposable
+        {
+            private readonly TripleDES _des;
+            private readonly ICryptoTransform _encryptor;
+            private readonly ICryptoTransform _decryptor;
+
+            public BclImpl(
+                byte[] key,
+                byte[] iv,
+                System.Security.Cryptography.CipherMode mode,
+                PaddingMode padding)
+                : base(key, 8, mode: null, padding: null)
+            {
+                var des = TripleDES.Create();
+                des.FeedbackSize = 64; // We use CFB8
+                des.Key = Key;
+                des.IV = iv.Take(8);
+                des.Mode = mode;
+                des.Padding = padding;
+                _des = des;
+                _encryptor = _des.CreateEncryptor();
+                _decryptor = _des.CreateDecryptor();
+            }
+
+            public override byte[] Encrypt(byte[] input, int offset, int length)
+            {
+                if (_des.Padding != PaddingMode.None)
+                {
+                    return _encryptor.TransformFinalBlock(input, offset, length);
+                }
+
+                var paddingLength = 0;
+                if (length % BlockSize > 0)
+                {
+                    if (_des.Mode is System.Security.Cryptography.CipherMode.CFB or System.Security.Cryptography.CipherMode.OFB)
+                    {
+                        // Manually pad the input for cfb and ofb cipher mode as BCL doesn't support partial block.
+                        // See https://github.com/dotnet/runtime/blob/e7d837da5b1aacd9325a8b8f2214cfaf4d3f0ff6/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SymmetricPadding.cs#L20-L21
+                        paddingLength = BlockSize - (length % BlockSize);
+                        input = input.Take(offset, length);
+                        length += paddingLength;
+                        Array.Resize(ref input, length);
+                        offset = 0;
+                    }
+                }
+
+                // Otherwise, (the most important case) assume this instance is
+                // used for one direction of an SSH connection, whereby the
+                // encrypted data in all packets are considered a single data
+                // stream i.e. we do not want to reset the state between calls to Encrypt.
+                var output = new byte[length];
+                _ = _encryptor.TransformBlock(input, offset, length, output, 0);
+
+                if (paddingLength > 0)
+                {
+                    // Manually unpad the output.
+                    Array.Resize(ref output, output.Length - paddingLength);
+                }
+
+                return output;
+            }
+
+            public override byte[] Decrypt(byte[] input, int offset, int length)
+            {
+                if (_des.Padding != PaddingMode.None)
+                {
+                    // If padding has been specified, call TransformFinalBlock to apply
+                    // the padding and reset the state.
+                    return _decryptor.TransformFinalBlock(input, offset, length);
+                }
+
+                var paddingLength = 0;
+                if (length % BlockSize > 0)
+                {
+                    if (_des.Mode is System.Security.Cryptography.CipherMode.CFB or System.Security.Cryptography.CipherMode.OFB)
+                    {
+                        // Manually pad the input for cfb and ofb cipher mode as BCL doesn't support partial block.
+                        // See https://github.com/dotnet/runtime/blob/e7d837da5b1aacd9325a8b8f2214cfaf4d3f0ff6/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SymmetricPadding.cs#L20-L21
+                        paddingLength = BlockSize - (length % BlockSize);
+                        input = input.Take(offset, length);
+                        length += paddingLength;
+                        Array.Resize(ref input, length);
+                        offset = 0;
+                    }
+                }
+
+                // Otherwise, (the most important case) assume this instance is
+                // used for one direction of an SSH connection, whereby the
+                // encrypted data in all packets are considered a single data
+                // stream i.e. we do not want to reset the state between calls to Encrypt.
+                var output = new byte[length];
+                _ = _decryptor.TransformBlock(input, offset, length, output, 0);
+
+                if (paddingLength > 0)
+                {
+                    // Manually unpad the output.
+                    Array.Resize(ref output, output.Length - paddingLength);
+                }
+
+                return output;
+            }
+
+            public override int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
+            {
+                throw new NotImplementedException($"Invalid usage of {nameof(EncryptBlock)}.");
+            }
+
+            public override int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
+            {
+                throw new NotImplementedException($"Invalid usage of {nameof(DecryptBlock)}.");
+            }
+
+            public void Dispose()
+            {
+                _des.Dispose();
+                _encryptor.Dispose();
+                _decryptor.Dispose();
+            }
+        }
+    }
+}

+ 48 - 0
src/Renci.SshNet/Security/Cryptography/Ciphers/TripleDesCipher.BlockImpl.cs

@@ -0,0 +1,48 @@
+#if !NET6_0_OR_GREATER
+using System;
+using System.Security.Cryptography;
+
+using Org.BouncyCastle.Crypto.Paddings;
+
+namespace Renci.SshNet.Security.Cryptography.Ciphers
+{
+    public partial class TripleDesCipher
+    {
+        private sealed class BlockImpl : BlockCipher, IDisposable
+        {
+            private readonly TripleDES _tripleDES;
+            private readonly ICryptoTransform _encryptor;
+            private readonly ICryptoTransform _decryptor;
+
+            public BlockImpl(byte[] key, CipherMode mode, IBlockCipherPadding padding)
+                : base(key, 8, mode, padding)
+            {
+                var tripleDES = TripleDES.Create();
+                tripleDES.Key = key;
+                tripleDES.Mode = System.Security.Cryptography.CipherMode.ECB;
+                tripleDES.Padding = PaddingMode.None;
+                _tripleDES = tripleDES;
+                _encryptor = tripleDES.CreateEncryptor();
+                _decryptor = tripleDES.CreateDecryptor();
+            }
+
+            public override int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
+            {
+                return _encryptor.TransformBlock(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
+            }
+
+            public override int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
+            {
+                return _decryptor.TransformBlock(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
+            }
+
+            public void Dispose()
+            {
+                _tripleDES.Dispose();
+                _encryptor.Dispose();
+                _decryptor.Dispose();
+            }
+        }
+    }
+}
+#endif

+ 51 - 119
src/Renci.SshNet/Security/Cryptography/Ciphers/TripleDesCipher.cs

@@ -1,154 +1,86 @@
 using System;
+using System.Security.Cryptography;
+
+#if !NET6_0_OR_GREATER
+using Org.BouncyCastle.Crypto.Paddings;
+
+using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
+#endif
 
 namespace Renci.SshNet.Security.Cryptography.Ciphers
 {
     /// <summary>
     /// Implements 3DES cipher algorithm.
     /// </summary>
-    public sealed class TripleDesCipher : DesCipher
+    public sealed partial class TripleDesCipher : BlockCipher, IDisposable
     {
-        private int[] _encryptionKey1;
-        private int[] _encryptionKey2;
-        private int[] _encryptionKey3;
-        private int[] _decryptionKey1;
-        private int[] _decryptionKey2;
-        private int[] _decryptionKey3;
+#if NET
+        private readonly BclImpl _impl;
+#else
+        private readonly BlockCipher _impl;
+#endif
 
         /// <summary>
         /// Initializes a new instance of the <see cref="TripleDesCipher"/> class.
         /// </summary>
         /// <param name="key">The key.</param>
+        /// <param name="iv">The IV.</param>
         /// <param name="mode">The mode.</param>
-        /// <param name="padding">The padding.</param>
+        /// <param name="pkcs7Padding">Enable PKCS7 padding.</param>
         /// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
-        public TripleDesCipher(byte[] key, CipherMode mode, CipherPadding padding)
-            : base(key, mode, padding)
+        public TripleDesCipher(byte[] key, byte[] iv, System.Security.Cryptography.CipherMode mode, bool pkcs7Padding)
+            : base(key, 8, mode: null, padding: null)
         {
-        }
-
-        /// <summary>
-        /// Encrypts the specified region of the input byte array and copies the encrypted data to the specified region of the output byte array.
-        /// </summary>
-        /// <param name="inputBuffer">The input data to encrypt.</param>
-        /// <param name="inputOffset">The offset into the input byte array from which to begin using data.</param>
-        /// <param name="inputCount">The number of bytes in the input byte array to use as data.</param>
-        /// <param name="outputBuffer">The output to which to write encrypted data.</param>
-        /// <param name="outputOffset">The offset into the output byte array from which to begin writing data.</param>
-        /// <returns>
-        /// The number of bytes encrypted.
-        /// </returns>
-        public override int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
-        {
-            if ((inputOffset + BlockSize) > inputBuffer.Length)
-            {
-                throw new ArgumentException("input buffer too short");
-            }
-
-            if ((outputOffset + BlockSize) > outputBuffer.Length)
+#if !NET6_0_OR_GREATER
+            if (mode == System.Security.Cryptography.CipherMode.CFB)
             {
-                throw new ArgumentException("output buffer too short");
+                // CFB8 not supported on .NET Framework, but supported on .NET
+                // see https://github.com/microsoft/referencesource/blob/51cf7850defa8a17d815b4700b67116e3fa283c2/mscorlib/system/security/cryptography/tripledescryptoserviceprovider.cs#L76-L78
+                // see https://github.com/dotnet/runtime/blob/e7d837da5b1aacd9325a8b8f2214cfaf4d3f0ff6/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/TripleDesImplementation.cs#L229-L236
+                _impl = new BlockImpl(key, new CfbCipherMode(iv), pkcs7Padding ? new Pkcs7Padding() : null);
             }
-
-            if (_encryptionKey1 is null || _encryptionKey2 is null || _encryptionKey3 is null)
+            else
+#endif
             {
-                var part1 = new byte[8];
-                var part2 = new byte[8];
-
-                Buffer.BlockCopy(Key, 0, part1, 0, 8);
-                Buffer.BlockCopy(Key, 8, part2, 0, 8);
-
-                _encryptionKey1 = GenerateWorkingKey(encrypting: true, part1);
-                _encryptionKey2 = GenerateWorkingKey(encrypting: false, part2);
-
-                if (Key.Length == 24)
-                {
-                    var part3 = new byte[8];
-                    Buffer.BlockCopy(Key, 16, part3, 0, 8);
-
-                    _encryptionKey3 = GenerateWorkingKey(encrypting: true, part3);
-                }
-                else
-                {
-                    _encryptionKey3 = _encryptionKey1;
-                }
+                _impl = new BclImpl(key, iv, mode, pkcs7Padding ? PaddingMode.PKCS7 : PaddingMode.None);
             }
+        }
 
-            var temp = new byte[BlockSize];
+        /// <inheritdoc/>
+        public override byte[] Encrypt(byte[] input, int offset, int length)
+        {
+            return _impl.Encrypt(input, offset, length);
+        }
 
-            DesFunc(_encryptionKey1, inputBuffer, inputOffset, temp, 0);
-            DesFunc(_encryptionKey2, temp, 0, temp, 0);
-            DesFunc(_encryptionKey3, temp, 0, outputBuffer, outputOffset);
+        /// <inheritdoc/>
+        public override byte[] Decrypt(byte[] input, int offset, int length)
+        {
+            return _impl.Decrypt(input, offset, length);
+        }
 
-            return BlockSize;
+        /// <inheritdoc/>
+        public override int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
+        {
+            return _impl.EncryptBlock(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
         }
 
-        /// <summary>
-        /// Decrypts the specified region of the input byte array and copies the decrypted data to the specified region of the output byte array.
-        /// </summary>
-        /// <param name="inputBuffer">The input data to decrypt.</param>
-        /// <param name="inputOffset">The offset into the input byte array from which to begin using data.</param>
-        /// <param name="inputCount">The number of bytes in the input byte array to use as data.</param>
-        /// <param name="outputBuffer">The output to which to write decrypted data.</param>
-        /// <param name="outputOffset">The offset into the output byte array from which to begin writing data.</param>
-        /// <returns>
-        /// The number of bytes decrypted.
-        /// </returns>
+        /// <inheritdoc/>
         public override int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
         {
-            if ((inputOffset + BlockSize) > inputBuffer.Length)
-            {
-                throw new ArgumentException("input buffer too short");
-            }
-
-            if ((outputOffset + BlockSize) > outputBuffer.Length)
-            {
-                throw new ArgumentException("output buffer too short");
-            }
-
-            if (_decryptionKey1 is null || _decryptionKey2 is null || _decryptionKey3 is null)
-            {
-                var part1 = new byte[8];
-                var part2 = new byte[8];
-
-                Buffer.BlockCopy(Key, 0, part1, 0, 8);
-                Buffer.BlockCopy(Key, 8, part2, 0, 8);
-
-                _decryptionKey1 = GenerateWorkingKey(encrypting: false, part1);
-                _decryptionKey2 = GenerateWorkingKey(encrypting: true, part2);
-
-                if (Key.Length == 24)
-                {
-                    var part3 = new byte[8];
-                    Buffer.BlockCopy(Key, 16, part3, 0, 8);
-
-                    _decryptionKey3 = GenerateWorkingKey(encrypting: false, part3);
-                }
-                else
-                {
-                    _decryptionKey3 = _decryptionKey1;
-                }
-            }
-
-            var temp = new byte[BlockSize];
-
-            DesFunc(_decryptionKey3, inputBuffer, inputOffset, temp, 0);
-            DesFunc(_decryptionKey2, temp, 0, temp, 0);
-            DesFunc(_decryptionKey1, temp, 0, outputBuffer, outputOffset);
-
-            return BlockSize;
+            return _impl.DecryptBlock(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
         }
 
-        /// <summary>
-        /// Validates the key.
-        /// </summary>
-        protected override void ValidateKey()
+        /// <inheritdoc/>
+        public void Dispose()
         {
-            var keySize = Key.Length * 8;
-
-            if (keySize is not (128 or 128 + 64))
+#if NET
+            _impl.Dispose();
+#else
+            if (_impl is IDisposable disposableImpl)
             {
-                throw new ArgumentException(string.Format("KeySize '{0}' is not valid for this algorithm.", keySize));
+                disposableImpl.Dispose();
             }
+#endif
         }
     }
 }

+ 0 - 1
test/Data/Key.RSA.Encrypted.Des.CBC.12345.pub

@@ -1 +0,0 @@
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCs7PAiYUZLgiAKPJpJ62JFBrE1d4lfG0w5vbTkMuwJvYmvWyBPU+Hc7jXxgK1iqsz/s0wDZutENyE9VBilTHAYDBOauXjSfQwlo7zHmK1HZ7h87jcIhltpY0NzBGmd/lQ+yDeXiSFGGoFyjwW6VpOfs0AR+oLA2Hpy4b9lI/QWzGPnSz53LVpALI9ssx15OgwjCNxUW+gjMMNrDN4Gz8EryvY28fwGVgPt6uZeT7bU02aSdcsTvWneGwoNeKIGWuwfIXghiTzIosijMbftWnWVNylM5hQOYlQloxVtCCKe5vnz5PeYfwE38yElu7XV6LqEibFNjor9Mcsc+Rr7d/rN

+ 0 - 30
test/Data/Key.RSA.Encrypted.Des.CBC.12345.txt

@@ -1,30 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-Proc-Type: 4,ENCRYPTED
-DEK-Info: DES-CBC,BD35E157CDD07CAD
-
-VU1aEcNJaFe1bhZ+sEzv70KQB94Pu2H+VoemLXtyIVzEQJV9+cymnRYjkgzykJ5d
-s4J0vJMdiGtMH5lYRYEPQRNroOzJMwLngNyPiV8yLZARMR7tINdQyM7JBUFL4GfA
-+0Jl/e7cZaCRJKAKQZu5L5DGuP6488tG7bKY9Rhzys21hPF+ck/8Gzfp1vC91AxF
-zuy71e8ihEERF55DB5Ai1/lvEN15GUvkx14s0Oonfarxueu3dp/ch4P4PhzkY9Ci
-7/ONnlpfrBR7bLajcXUq2XAO21ftDZejk5m6y0Z0bl0L+HpbJk2zFJGtwM+B+T3h
-WkZqEK+RyTER67KUeecoKl297sh2YQtbxbdoTseUxktCb2/BSkpOHV5cgS64c3Ys
-zGbk4dUmKv4HFs4VJ7HD3Ix6qAhMJyGQXOqze1c7ky9NoU2e05HdfgI/9We0EZQJ
-PbequZZ4GOKnj4f70EbIEFyJldsJgijNCLKCafRYtygIs8zZkj/oE/S92iYZtesn
-q2wxBAat3ZYn6tpkXx+4u9bSZ+U9OfYijxBv2x+BnAf0nQ1zNqmTOolEAIEMLS60
-5WFWKVaOeqAP0Q185TEqpUFqcaTCycV2w1hN/XXuAiYqXaSYwg4ZtCmWmxbn20QH
-SMvRuY6MZSNNncgoLbm+ySq1yP1Z46kRsU1ufAyN3Jue+6DVd8wFrEOXfLJnCbFk
-Eykpd7vxzczHXWgrlakl2sPFy4ltcALc8ZhsmGO+goWNqwp4QPX4LMHeUsA9n86Q
-ABNV+KkCyIzp6FsFVqeHCxD8EoE/MuiuMHy/n0oEGj7zp9Moq5DHrgQDmdKnhrAG
-B2HqYlCH54LDKd3wlgT76/HJ8yviFZECY2q6Z1BnMzm2ikKLCyPBMKln2eaNUqss
-YNt/16DUgxpThoXfS1T8zbYHGCO0niPGMy6LYWFu0XBVgKrFcl+D1mz9vGC2CBto
-VA9YhIhjtlUWwmIaJAmlTXbHCXKWLjaK9/DbsnJOlUYG5XwTC+ntW24jZnDGkHzn
-Vj87JDijGzziM6qte/JM4WrUKxd6Zrvl6AaTqGH3aPxZWdFsKETfJhfbBX3vjw5+
-j6ltm4ZA+YXE2j8DLUQ8XMQlo66FUpiOj559aOXxfEb4HhJNrwo7VLsKuK56LdYb
-keQmeQr+4HkDGjD0T8+0sAOx82B0TkgWYaCFU1wmzIsny+wiQmMoKJIR3T3Mzb07
-9d5ncndS7DqcdAKMuG/F9w46QWW8G0veaJlV8ws7Ags1iBOxgTKrNsQNedB7B+zw
-cTqikGnDgxVfiXOBRzHq7F3ZH9HcT4SSxuM6y2YN91C2DmPbtZAwlJX5nkORANy7
-05kCAW/Md35jfVkZJsLsDDLNfRqukcWadkcKp3XvDB7/4WWFu8BrR9CHBY9j8hEt
-FC4FTxZnnoDnnVg5sC8rYB6avD/MiomOUGOlHgM3MMk/Ta7fmioauCUBR5oXa/We
-uhSoNyRY0/VZgE+fJ7P0Y5hzgnBDncVH5j57G0q4KTiTBDfuHBTLw+h5Htd5VBGS
-5PwhKfrAvIwetRWMyRhfjixPDtcWZ2jx20fWCVxpPp+3MxBtuMgn7A==
------END RSA PRIVATE KEY-----

+ 52 - 0
test/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/TripleDesCipherBenchmarks.cs

@@ -0,0 +1,52 @@
+using BenchmarkDotNet.Attributes;
+
+using Renci.SshNet.Security.Cryptography.Ciphers;
+
+using CipherMode = System.Security.Cryptography.CipherMode;
+
+namespace Renci.SshNet.Benchmarks.Security.Cryptography.Ciphers
+{
+    [MemoryDiagnoser]
+    public class TripleDesCipherBenchmarks
+    {
+        private readonly byte[] _key;
+        private readonly byte[] _iv;
+        private readonly byte[] _data;
+
+        public TripleDesCipherBenchmarks()
+        {
+            _key = new byte[24];
+            _iv = new byte[8];
+            _data = new byte[32 * 1024];
+
+            Random random = new(Seed: 12345);
+            random.NextBytes(_key);
+            random.NextBytes(_iv);
+            random.NextBytes(_data);
+        }
+
+        [Benchmark]
+        public byte[] Encrypt_CBC()
+        {
+            return new TripleDesCipher(_key, _iv, CipherMode.CBC, false).Encrypt(_data);
+        }
+
+        [Benchmark]
+        public byte[] Decrypt_CBC()
+        {
+            return new TripleDesCipher(_key, _iv, CipherMode.CBC, false).Decrypt(_data);
+        }
+
+        [Benchmark]
+        public byte[] Encrypt_CFB()
+        {
+            return new TripleDesCipher(_key, _iv, CipherMode.CFB, false).Encrypt(_data);
+        }
+
+        [Benchmark]
+        public byte[] Decrypt_CFB()
+        {
+            return new TripleDesCipher(_key, _iv, CipherMode.CFB, false).Decrypt(_data);
+        }
+    }
+}

+ 0 - 1
test/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs

@@ -361,7 +361,6 @@ namespace Renci.SshNet.Tests.Classes
         [DataRow("Key.RSA.Encrypted.Aes.128.CBC.12345.txt", "12345", typeof(RsaKey))]
         [DataRow("Key.RSA.Encrypted.Aes.192.CBC.12345.txt", "12345", typeof(RsaKey))]
         [DataRow("Key.RSA.Encrypted.Aes.256.CBC.12345.txt", "12345", typeof(RsaKey))]
-        [DataRow("Key.RSA.Encrypted.Des.CBC.12345.txt", "12345", typeof(RsaKey))]
         [DataRow("Key.RSA.Encrypted.Des.Ede3.CBC.12345.txt", "12345", typeof(RsaKey))]
         [DataRow("Key.RSA.Encrypted.Des.Ede3.CFB.1234567890.txt", "1234567890", typeof(RsaKey))]
         [DataRow("Key.RSA.PKCS8.Encrypted.Aes.256.CBC.12345.txt", "12345", typeof(RsaKey))]

+ 6 - 5
test/Renci.SshNet.Tests/Classes/Security/Cryptography/BlockCipherTest.cs

@@ -3,10 +3,11 @@ using System.Linq;
 
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 
+using Org.BouncyCastle.Crypto.Paddings;
+
 using Renci.SshNet.Security.Cryptography;
 using Renci.SshNet.Security.Cryptography.Ciphers;
 using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
-using Renci.SshNet.Security.Cryptography.Ciphers.Paddings;
 using Renci.SshNet.Tests.Common;
 
 namespace Renci.SshNet.Tests.Classes.Security.Cryptography
@@ -20,7 +21,7 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography
             var input = new byte[] { 0x2c, 0x1a, 0x05, 0x00, 0x68 };
             var output = new byte[] { 0x0a, 0x00, 0x03, 0x02, 0x06, 0x08, 0x07, 0x05 };
             var key = new byte[] { 0x17, 0x78, 0x56, 0xe1, 0x3e, 0xbd, 0x3e, 0x50, 0x1d, 0x79, 0x3f, 0x0f, 0x55, 0x37, 0x45, 0x54 };
-            var blockCipher = new BlockCipherStub(key, 8, null, new PKCS7Padding())
+            var blockCipher = new BlockCipherStub(key, 8, null, new Pkcs7Padding())
             {
                 EncryptBlockDelegate = (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset) =>
                     {
@@ -41,7 +42,7 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography
             var input = new byte[0];
             var output = new byte[] { 0x0a, 0x00, 0x03, 0x02, 0x06, 0x08, 0x07, 0x05 };
             var key = new byte[] { 0x17, 0x78, 0x56, 0xe1, 0x3e, 0xbd, 0x3e, 0x50, 0x1d, 0x79, 0x3f, 0x0f, 0x55, 0x37, 0x45, 0x54 };
-            var blockCipher = new BlockCipherStub(key, 8, null, new PKCS7Padding())
+            var blockCipher = new BlockCipherStub(key, 8, null, new Pkcs7Padding())
             {
                 EncryptBlockDelegate = (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset) =>
                 {
@@ -84,7 +85,7 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography
             var output = new byte[] { 0x2c, 0x1a, 0x05, 0x00, 0x68 };
             var padding = new byte[] { 0x03, 0x03, 0x03 };
             var key = new byte[] { 0x17, 0x78, 0x56, 0xe1, 0x3e, 0xbd, 0x3e, 0x50, 0x1d, 0x79, 0x3f, 0x0f, 0x55, 0x37, 0x45, 0x54 };
-            var blockCipher = new BlockCipherStub(key, 8, null, new PKCS7Padding())
+            var blockCipher = new BlockCipherStub(key, 8, null, new Pkcs7Padding())
             {
                 DecryptBlockDelegate = (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset) =>
                     {
@@ -127,7 +128,7 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography
             public Func<byte[], int, int, byte[], int, int> EncryptBlockDelegate;
             public Func<byte[], int, int, byte[], int, int> DecryptBlockDelegate;
 
-            public BlockCipherStub(byte[] key, byte blockSize, CipherMode mode, CipherPadding padding) : base(key, blockSize, mode, padding)
+            public BlockCipherStub(byte[] key, byte blockSize, CipherMode mode, IBlockCipherPadding padding) : base(key, blockSize, mode, padding)
             {
             }
 

+ 0 - 58
test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/DesCipherTest.cs

@@ -1,58 +0,0 @@
-using System.Text;
-
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-using Renci.SshNet.Common;
-using Renci.SshNet.Security.Cryptography.Ciphers;
-using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
-using Renci.SshNet.Tests.Common;
-
-namespace Renci.SshNet.Tests.Classes.Security.Cryptography.Ciphers
-{
-    /// <summary>
-    /// Implements DES cipher algorithm.
-    /// </summary>
-    [TestClass]
-    public class DesCipherTest : TestBase
-    {
-        [TestMethod]
-        public void Cbc_Encrypt()
-        {
-            var expectedCypher = new byte[]
-                {
-                    0x15, 0x43, 0x3e, 0x97, 0x65, 0x66, 0xea, 0x81, 0x22, 0xab, 0xe3,
-                    0x11, 0x0f, 0x7d, 0xcb, 0x78, 0x56, 0x91, 0x22, 0x3d, 0xd6, 0xca,
-                    0xe3, 0xbd
-                };
-
-            var input = Encoding.ASCII.GetBytes("www.javaCODEgeeks.com!!!");
-            var key = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef };
-            var iv = new byte[] { 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00 };
-
-            var des = new DesCipher(key, new CbcCipherMode(iv), padding: null);
-            var actualCypher = des.Encrypt(input);
-
-            Assert.IsTrue((expectedCypher.IsEqualTo(actualCypher)));
-        }
-
-        [TestMethod]
-        public void Cbc_Decrypt()
-        {
-            var expectedPlain = Encoding.ASCII.GetBytes("www.javaCODEgeeks.com!!!");
-
-            var key = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef };
-            var iv = new byte[] { 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00 };
-            var cypher = new byte[]
-                {
-                    0x15, 0x43, 0x3e, 0x97, 0x65, 0x66, 0xea, 0x81, 0x22, 0xab, 0xe3,
-                    0x11, 0x0f, 0x7d, 0xcb, 0x78, 0x56, 0x91, 0x22, 0x3d, 0xd6, 0xca,
-                    0xe3, 0xbd
-                };
-
-            var des = new DesCipher(key, new CbcCipherMode(iv), padding: null);
-            var plain = des.Decrypt(cypher);
-
-            Assert.IsTrue(expectedPlain.IsEqualTo(plain));
-        }
-    }
-}

+ 0 - 63
test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/Paddings/PKCS7PaddingTest.cs

@@ -1,63 +0,0 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-using Renci.SshNet.Common;
-using Renci.SshNet.Security.Cryptography.Ciphers.Paddings;
-
-namespace Renci.SshNet.Tests.Classes.Security.Cryptography.Ciphers.Paddings
-{
-    [TestClass]
-    public class PKCS7PaddingTest
-    {
-        private PKCS7Padding _padding;
-
-        [TestInitialize]
-        public void SetUp()
-        {
-            _padding = new PKCS7Padding();
-        }
-
-        [TestMethod]
-        public void Pad_BlockSizeAndInput_LessThanBlockSize()
-        {
-            var input = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 };
-            var expected = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x03, 0x03, 0x03 };
-
-            var actual = _padding.Pad(8, input);
-
-            Assert.IsTrue(expected.IsEqualTo(actual));
-        }
-
-        [TestMethod]
-        public void Pad_BlockSizeAndInput_MoreThanBlockSizeButNoMultipleOfBlockSize()
-        {
-            var input = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 };
-            var expected = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07 };
-
-            var actual = _padding.Pad(8, input);
-
-            Assert.IsTrue(expected.IsEqualTo(actual));
-        }
-
-        [TestMethod]
-        public void Pad_BlockSizeAndInputAndOffsetAndLength_LessThanBlockSize()
-        {
-            var input = new byte[] { 0x0f, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
-            var expected = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x03, 0x03, 0x03 };
-
-            var actual = _padding.Pad(8, input, 1, input.Length - 2);
-
-            Assert.IsTrue(expected.IsEqualTo(actual));
-        }
-
-        [TestMethod]
-        public void Pad_BlockSizeAndInputAndOffsetAndLength_MoreThanBlockSizeButNoMultipleOfBlockSize()
-        {
-            var input = new byte[] { 0x0f, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10 };
-            var expected = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07 };
-
-            var actual = _padding.Pad(8, input, 1, input.Length - 2);
-
-            Assert.IsTrue(expected.IsEqualTo(actual));
-        }
-    }
-}

+ 151 - 0
test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/TripleDesCipherTest.Gen.cs.txt

@@ -0,0 +1,151 @@
+// Used to generate tests in TripleDesCipherTest.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()
+{
+    ["cbc"] = ("(byte[])iv.Clone(), CipherMode.CBC", CipherMode.CBC),
+    ["cfb"] = ("(byte[])iv.Clone(), CipherMode.CFB", CipherMode.CFB),
+};
+
+Random random = new(123);
+
+using IndentedTextWriter tw = new(Console.Out);
+
+foreach ((string mode, (string modeCode, CipherMode? bclMode)) in modes)
+{
+    foreach (int inputLength in new int[] { 8, 17, 32 })
+    {
+        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 % 8 != 0 && mode is not "cfb")
+            {
+                continue;
+            }
+
+            // It does not make sense to test padding for stream cipher modes
+            // (and the OpenSSL, BCL implementations differ)
+            if (pad && mode is "cfb")
+            {
+                continue;
+            }
+
+            byte[] input = new byte[inputLength];
+            random.NextBytes(input);
+
+            byte[] key = new byte[64 * 3 / 8];
+            random.NextBytes(key);
+
+            byte[] iv = new byte[8];
+            random.NextBytes(iv);
+
+            StringBuilder openSslCmd = new();
+
+            openSslCmd.Append($"echo -n -e '{string.Join("", input.Select(b => $"\\x{b:x2}"))}' |");
+            openSslCmd.Append($" openssl enc -e -des-ede3-{mode}");
+            openSslCmd.Append($" -K {Convert.ToHexString(key)}");
+            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 TripleDes_{mode.ToUpper()}_Length{inputLength}_{(pad ? "Pad" : "NoPad")}()");
+            tw.WriteLine("{");
+            tw.Indent++;
+
+            WriteBytes(input);
+            WriteBytes(key);
+            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 TripleDesCipher(key, {modeCode}, pkcs7Padding: {(pad ? "true" : "false")}).Encrypt(input);");
+            tw.WriteLine();
+            tw.WriteLine($"CollectionAssert.AreEqual(expected, actual);");
+
+            if (bclMode is not CipherMode.CFB)
+            {
+                // Verify the OpenSSL result is the same as the .NET BCL, just to be sure
+                TripleDES bcl = TripleDES.Create();
+                bcl.Key = key;
+                bcl.IV = iv;
+                bcl.FeedbackSize = 8 * 8; // .NET is CFB1 by default, OpenSSL is CFB8
+                bcl.Mode = bclMode.Value;
+                bcl.Padding = pad ? PaddingMode.PKCS7 : PaddingMode.None;
+                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 TripleDesCipher(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("};");
+}

+ 265 - 14
test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/TripleDesCipherTest.cs

@@ -1,11 +1,10 @@
-using System.Linq;
-
-using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
 
 using Renci.SshNet.Security.Cryptography.Ciphers;
-using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
 using Renci.SshNet.Tests.Common;
 
+using CipherMode = System.Security.Cryptography.CipherMode;
+
 namespace Renci.SshNet.Tests.Classes.Security.Cryptography.Ciphers
 {
     /// <summary>
@@ -14,20 +13,272 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography.Ciphers
     [TestClass]
     public class TripleDesCipherTest : TestBase
     {
+        // All tests below this line were generated by the script in TripleDesCipherTest.Gen.cs.txt
+        [TestMethod]
+        public void TripleDes_CBC_Length8_NoPad()
+        {
+            var input = new byte[]
+            {
+                0x03, 0xe1, 0xe1, 0xaa, 0xa5, 0xbc, 0xa1, 0x9f,
+            };
+            var key = new byte[]
+            {
+                0xba, 0x8c, 0x42, 0x05, 0x8b, 0x4a, 0xbf, 0x28, 0x96, 0x39, 0xec, 0x0d, 0xfc, 0x2d, 0xb2, 0x7c,
+                0xe9, 0x74, 0x8e, 0x5f, 0xb9, 0xf3, 0x99, 0xce,
+            };
+            var iv = new byte[]
+            {
+                0xe1, 0x1a, 0x5c, 0x51, 0xa3, 0x1d, 0xd7, 0x1b,
+            };
+
+            // echo -n -e '\x03\xe1\xe1\xaa\xa5\xbc\xa1\x9f' | openssl enc -e -des-ede3-cbc -K BA8C42058B4ABF289639EC0DFC2DB27CE9748E5FB9F399CE -iv E11A5C51A31DD71B -nopad | hd
+            var expected = new byte[]
+            {
+                0x0c, 0xd8, 0x26, 0xd1, 0xed, 0x41, 0x73, 0x25,
+            };
+
+            var actual = new TripleDesCipher(key, (byte[])iv.Clone(), CipherMode.CBC, pkcs7Padding: false).Encrypt(input);
+
+            CollectionAssert.AreEqual(expected, actual);
+
+            var decrypted = new TripleDesCipher(key, (byte[])iv.Clone(), CipherMode.CBC, pkcs7Padding: false).Decrypt(actual);
+
+            CollectionAssert.AreEqual(input, decrypted);
+        }
+
+        [TestMethod]
+        public void TripleDes_CBC_Length8_Pad()
+        {
+            var input = new byte[]
+            {
+                0x15, 0x8c, 0xad, 0xa6, 0xaf, 0x63, 0x0d, 0x8c,
+            };
+            var key = new byte[]
+            {
+                0x1a, 0xf1, 0x3a, 0x35, 0x8c, 0xca, 0x3f, 0xd6, 0x2f, 0x65, 0xc1, 0x31, 0x2d, 0x41, 0xe5, 0xc7,
+                0xf3, 0x74, 0x23, 0x71, 0xed, 0x6d, 0x84, 0x79,
+            };
+            var iv = new byte[]
+            {
+                0x61, 0xd0, 0xf8, 0x6f, 0x7f, 0x0c, 0xcc, 0x86,
+            };
+
+            // echo -n -e '\x15\x8c\xad\xa6\xaf\x63\x0d\x8c' | openssl enc -e -des-ede3-cbc -K 1AF13A358CCA3FD62F65C1312D41E5C7F3742371ED6D8479 -iv 61D0F86F7F0CCC86 | hd
+            var expected = new byte[]
+            {
+                0x91, 0xcb, 0x5c, 0xdf, 0x6d, 0xf2, 0x85, 0xbf, 0x5c, 0xf8, 0xf9, 0xbd, 0xe1, 0xc4, 0xb9, 0xab,
+            };
+
+            var actual = new TripleDesCipher(key, (byte[])iv.Clone(), CipherMode.CBC, pkcs7Padding: true).Encrypt(input);
+
+            CollectionAssert.AreEqual(expected, actual);
+
+            var decrypted = new TripleDesCipher(key, (byte[])iv.Clone(), CipherMode.CBC, pkcs7Padding: true).Decrypt(actual);
+
+            CollectionAssert.AreEqual(input, decrypted);
+        }
+
+        [TestMethod]
+        public void TripleDes_CBC_Length17_Pad()
+        {
+            var input = new byte[]
+            {
+                0x67, 0x02, 0x45, 0xc8, 0xb8, 0x64, 0x42, 0x17, 0xda, 0x85, 0x21, 0x3e, 0x5c, 0xa6, 0xee, 0xd4,
+                0xa7,
+            };
+            var key = new byte[]
+            {
+                0xe6, 0xb3, 0x3b, 0x3f, 0x9c, 0x9e, 0xac, 0x6c, 0xc1, 0xd3, 0xbb, 0xd2, 0xd0, 0x57, 0x22, 0x99,
+                0x3a, 0xc9, 0x2b, 0xfb, 0x1d, 0x0e, 0x8e, 0x31,
+            };
+            var iv = new byte[]
+            {
+                0x0c, 0x96, 0x68, 0x4c, 0x46, 0x1d, 0xbb, 0xe1,
+            };
+
+            // echo -n -e '\x67\x02\x45\xc8\xb8\x64\x42\x17\xda\x85\x21\x3e\x5c\xa6\xee\xd4\xa7' | openssl enc -e -des-ede3-cbc -K E6B33B3F9C9EAC6CC1D3BBD2D05722993AC92BFB1D0E8E31 -iv 0C96684C461DBBE1 | hd
+            var expected = new byte[]
+            {
+                0xd1, 0x8b, 0xe6, 0x2d, 0x26, 0x99, 0xff, 0x02, 0xd8, 0x45, 0x2b, 0x21, 0x90, 0xc6, 0x85, 0xa3,
+                0x58, 0xa7, 0x5a, 0xfc, 0xc7, 0x20, 0x40, 0x1f,
+            };
+
+            var actual = new TripleDesCipher(key, (byte[])iv.Clone(), CipherMode.CBC, pkcs7Padding: true).Encrypt(input);
+
+            CollectionAssert.AreEqual(expected, actual);
+
+            var decrypted = new TripleDesCipher(key, (byte[])iv.Clone(), CipherMode.CBC, pkcs7Padding: true).Decrypt(actual);
+
+            CollectionAssert.AreEqual(input, decrypted);
+        }
+
+        [TestMethod]
+        public void TripleDes_CBC_Length32_NoPad()
+        {
+            var input = new byte[]
+            {
+                0x23, 0xc8, 0x99, 0x59, 0x90, 0x47, 0xcb, 0x63, 0x99, 0x5b, 0xf7, 0x91, 0x87, 0x44, 0x09, 0x2e,
+                0xff, 0xa4, 0x21, 0xdc, 0xc3, 0xd9, 0x89, 0xd7, 0x24, 0x0a, 0x32, 0x05, 0x36, 0x60, 0x25, 0xa4,
+            };
+            var key = new byte[]
+            {
+                0x17, 0xda, 0xaf, 0x08, 0xbe, 0xc9, 0x08, 0xf3, 0xfe, 0xc7, 0x61, 0xc2, 0x17, 0xfd, 0xaa, 0xc7,
+                0x8d, 0x3a, 0x4c, 0xa2, 0xfb, 0xde, 0x1e, 0x49,
+            };
+            var iv = new byte[]
+            {
+                0x3e, 0xc1, 0x34, 0x86, 0x14, 0xc6, 0x2d, 0x39,
+            };
+
+            // echo -n -e '\x23\xc8\x99\x59\x90\x47\xcb\x63\x99\x5b\xf7\x91\x87\x44\x09\x2e\xff\xa4\x21\xdc\xc3\xd9\x89\xd7\x24\x0a\x32\x05\x36\x60\x25\xa4' | openssl enc -e -des-ede3-cbc -K 17DAAF08BEC908F3FEC761C217FDAAC78D3A4CA2FBDE1E49 -iv 3EC1348614C62D39 -nopad | hd
+            var expected = new byte[]
+            {
+                0x75, 0x50, 0xa3, 0x30, 0xc8, 0xaf, 0x2d, 0xa8, 0x8b, 0x78, 0x07, 0x89, 0xb0, 0x82, 0xa1, 0x74,
+                0xd1, 0x1c, 0x44, 0xd2, 0x1f, 0x74, 0x1b, 0xc7, 0x93, 0x17, 0x5e, 0x31, 0x31, 0x0b, 0x95, 0x50,
+            };
+
+            var actual = new TripleDesCipher(key, (byte[])iv.Clone(), CipherMode.CBC, pkcs7Padding: false).Encrypt(input);
+
+            CollectionAssert.AreEqual(expected, actual);
+
+            var decrypted = new TripleDesCipher(key, (byte[])iv.Clone(), CipherMode.CBC, pkcs7Padding: false).Decrypt(actual);
+
+            CollectionAssert.AreEqual(input, decrypted);
+        }
+
+        [TestMethod]
+        public void TripleDes_CBC_Length32_Pad()
+        {
+            var input = new byte[]
+            {
+                0x35, 0x52, 0x79, 0xad, 0x95, 0x01, 0x6f, 0x36, 0x9b, 0x2e, 0xde, 0xfc, 0x77, 0xc7, 0xc0, 0x27,
+                0x60, 0x6b, 0x78, 0xfc, 0x13, 0x83, 0xa8, 0x38, 0xbb, 0x65, 0xca, 0xfd, 0x94, 0x82, 0xde, 0x38,
+            };
+            var key = new byte[]
+            {
+                0x99, 0x28, 0x8c, 0xc4, 0x84, 0xfd, 0x32, 0x8c, 0xca, 0x16, 0x06, 0xcc, 0x00, 0x22, 0xd2, 0x76,
+                0x00, 0x0d, 0x25, 0xa9, 0x4e, 0x31, 0x25, 0xb1,
+            };
+            var iv = new byte[]
+            {
+                0xaa, 0x40, 0xf1, 0x2f, 0x36, 0x72, 0xa2, 0x18,
+            };
+
+            // echo -n -e '\x35\x52\x79\xad\x95\x01\x6f\x36\x9b\x2e\xde\xfc\x77\xc7\xc0\x27\x60\x6b\x78\xfc\x13\x83\xa8\x38\xbb\x65\xca\xfd\x94\x82\xde\x38' | openssl enc -e -des-ede3-cbc -K 99288CC484FD328CCA1606CC0022D276000D25A94E3125B1 -iv AA40F12F3672A218 | hd
+            var expected = new byte[]
+            {
+                0x0f, 0x9a, 0xb5, 0xc9, 0x30, 0xac, 0xd0, 0x64, 0xaf, 0xdd, 0x5e, 0x0a, 0x89, 0xda, 0xe3, 0xcb,
+                0x08, 0xa1, 0x82, 0x1f, 0x76, 0xd5, 0x3b, 0x59, 0x3a, 0x61, 0xff, 0x87, 0x11, 0xa9, 0x40, 0x5a,
+                0x74, 0x04, 0x8c, 0x1f, 0xa4, 0xca, 0x1b, 0xf7,
+            };
+
+            var actual = new TripleDesCipher(key, (byte[])iv.Clone(), CipherMode.CBC, pkcs7Padding: true).Encrypt(input);
+
+            CollectionAssert.AreEqual(expected, actual);
+
+            var decrypted = new TripleDesCipher(key, (byte[])iv.Clone(), CipherMode.CBC, pkcs7Padding: true).Decrypt(actual);
+
+            CollectionAssert.AreEqual(input, decrypted);
+        }
+
+        [TestMethod]
+        public void TripleDes_CFB_Length8_NoPad()
+        {
+            var input = new byte[]
+            {
+                0x4f, 0xa6, 0x62, 0x4f, 0x3b, 0xfb, 0xa3, 0x63,
+            };
+            var key = new byte[]
+            {
+                0x38, 0xec, 0x32, 0xfd, 0x7d, 0xdb, 0x38, 0x99, 0x93, 0x53, 0xfc, 0x86, 0x5d, 0x35, 0xe9, 0x68,
+                0x02, 0xda, 0x1a, 0x43, 0x0b, 0x02, 0x55, 0x57,
+            };
+            var iv = new byte[]
+            {
+                0x74, 0xed, 0x7d, 0x5a, 0xbf, 0x82, 0x3b, 0x05,
+            };
+
+            // echo -n -e '\x4f\xa6\x62\x4f\x3b\xfb\xa3\x63' | openssl enc -e -des-ede3-cfb -K 38EC32FD7DDB38999353FC865D35E96802DA1A430B025557 -iv 74ED7D5ABF823B05 -nopad | hd
+            var expected = new byte[]
+            {
+                0x28, 0x48, 0x3f, 0xb4, 0x48, 0xce, 0x96, 0xaf,
+            };
+
+            var actual = new TripleDesCipher(key, (byte[])iv.Clone(), CipherMode.CFB, pkcs7Padding: false).Encrypt(input);
+
+            CollectionAssert.AreEqual(expected, actual);
+
+            var decrypted = new TripleDesCipher(key, (byte[])iv.Clone(), CipherMode.CFB, pkcs7Padding: false).Decrypt(actual);
+
+            CollectionAssert.AreEqual(input, decrypted);
+        }
+
         [TestMethod]
-        public void Test_Cipher_3DES_CBC()
+        public void TripleDes_CFB_Length17_NoPad()
         {
-            var input = new byte[] { 0x00, 0x00, 0x00, 0x1c, 0x0a, 0x05, 0x00, 0x00, 0x00, 0x0c, 0x73, 0x73, 0x68, 0x2d, 0x75, 0x73, 0x65, 0x72, 0x61, 0x75, 0x74, 0x68, 0x72, 0x4e, 0x06, 0x08, 0x28, 0x2d, 0xaa, 0xe2, 0xb3, 0xd9 };
-            var key = new byte[] { 0x78, 0xf6, 0xc6, 0xbb, 0x57, 0x03, 0x69, 0xca, 0xba, 0x31, 0x18, 0x2f, 0x2f, 0x4c, 0x35, 0x34, 0x64, 0x06, 0x85, 0x30, 0xbe, 0x78, 0x60, 0xb3 };
-            var iv = new byte[] { 0xc0, 0x75, 0xf2, 0x26, 0x0a, 0x2a, 0x42, 0x96 };
-            var output = new byte[] { 0x28, 0x77, 0x2f, 0x07, 0x3e, 0xc2, 0x27, 0xa6, 0xdb, 0x36, 0x4d, 0xc6, 0x7a, 0x26, 0x7a, 0x38, 0xe6, 0x54, 0x0b, 0xab, 0x07, 0x87, 0xf0, 0xa4, 0x73, 0x1f, 0xde, 0xe6, 0x81, 0x1d, 0x4b, 0x4b };
-            var testCipher = new TripleDesCipher(key, new CbcCipherMode(iv), null);
-            var r = testCipher.Encrypt(input);
+            var input = new byte[]
+            {
+                0x6a, 0xc2, 0x70, 0x62, 0xff, 0x28, 0x34, 0xce, 0x08, 0x58, 0x9c, 0xe3, 0x76, 0x1b, 0xbb, 0x1a,
+                0xbc,
+            };
+            var key = new byte[]
+            {
+                0xf9, 0x4c, 0x60, 0xe1, 0x5f, 0x57, 0x35, 0x96, 0xda, 0x89, 0x8f, 0x5e, 0xde, 0xd9, 0x10, 0x17,
+                0xf6, 0x1b, 0x9a, 0xc4, 0x87, 0x69, 0xda, 0xa5,
+            };
+            var iv = new byte[]
+            {
+                0x4b, 0x3b, 0xb3, 0x66, 0x71, 0xe0, 0x58, 0x31,
+            };
 
-            if (!r.SequenceEqual(output))
+            // echo -n -e '\x6a\xc2\x70\x62\xff\x28\x34\xce\x08\x58\x9c\xe3\x76\x1b\xbb\x1a\xbc' | openssl enc -e -des-ede3-cfb -K F94C60E15F573596DA898F5EDED91017F61B9AC48769DAA5 -iv 4B3BB36671E05831 -nopad | hd
+            var expected = new byte[]
             {
-                Assert.Fail("Invalid encryption");
-            }
+                0x5a, 0x7e, 0x55, 0x4d, 0x63, 0xc1, 0x80, 0x32, 0x84, 0xdc, 0xd0, 0xa7, 0x6c, 0xea, 0x65, 0x42,
+                0xc3,
+            };
+
+            var actual = new TripleDesCipher(key, (byte[])iv.Clone(), CipherMode.CFB, pkcs7Padding: false).Encrypt(input);
+
+            CollectionAssert.AreEqual(expected, actual);
+
+            var decrypted = new TripleDesCipher(key, (byte[])iv.Clone(), CipherMode.CFB, pkcs7Padding: false).Decrypt(actual);
+
+            CollectionAssert.AreEqual(input, decrypted);
+        }
+
+        [TestMethod]
+        public void TripleDes_CFB_Length32_NoPad()
+        {
+            var input = new byte[]
+            {
+                0x62, 0x9d, 0xc6, 0x36, 0xda, 0x23, 0x0b, 0x6b, 0x3b, 0xcb, 0x24, 0x9f, 0xa4, 0x6f, 0x29, 0x7e,
+                0x8b, 0xcb, 0x7f, 0xff, 0x21, 0x56, 0x34, 0x90, 0x72, 0xba, 0x95, 0x23, 0xa3, 0xcf, 0x25, 0xfa,
+            };
+            var key = new byte[]
+            {
+                0x30, 0x5e, 0xfc, 0x40, 0x13, 0xda, 0x3d, 0xd3, 0x10, 0x2f, 0x89, 0xbc, 0x44, 0x3a, 0x01, 0xdb,
+                0x11, 0x34, 0xda, 0xa5, 0x60, 0x58, 0x10, 0x0c,
+            };
+            var iv = new byte[]
+            {
+                0x69, 0x35, 0xc3, 0x1f, 0x8d, 0xe7, 0xc7, 0x6b,
+            };
+
+            // echo -n -e '\x62\x9d\xc6\x36\xda\x23\x0b\x6b\x3b\xcb\x24\x9f\xa4\x6f\x29\x7e\x8b\xcb\x7f\xff\x21\x56\x34\x90\x72\xba\x95\x23\xa3\xcf\x25\xfa' | openssl enc -e -des-ede3-cfb -K 305EFC4013DA3DD3102F89BC443A01DB1134DAA56058100C -iv 6935C31F8DE7C76B -nopad | hd
+            var expected = new byte[]
+            {
+                0xb8, 0xcf, 0xf4, 0xf9, 0x88, 0xfd, 0x02, 0xf1, 0xb9, 0xe9, 0xf0, 0xb3, 0x1d, 0x0a, 0x9b, 0x91,
+                0x30, 0x3e, 0xf7, 0xa2, 0xf6, 0xb4, 0xa5, 0xc4, 0x4d, 0x89, 0x06, 0xed, 0x55, 0xd3, 0x28, 0xd0,
+            };
+
+            var actual = new TripleDesCipher(key, (byte[])iv.Clone(), CipherMode.CFB, pkcs7Padding: false).Encrypt(input);
+
+            CollectionAssert.AreEqual(expected, actual);
+
+            var decrypted = new TripleDesCipher(key, (byte[])iv.Clone(), CipherMode.CFB, pkcs7Padding: false).Decrypt(actual);
+
+            CollectionAssert.AreEqual(input, decrypted);
         }
     }
 }