diff --git a/README.md b/README.md index ca0e5b327..6aa3bb813 100644 --- a/README.md +++ b/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 diff --git a/src/Renci.SshNet/ConnectionInfo.cs b/src/Renci.SshNet/ConnectionInfo.cs index 52f614554..3a60ad3ec 100644 --- a/src/Renci.SshNet/ConnectionInfo.cs +++ b/src/Renci.SshNet/ConnectionInfo.cs @@ -12,7 +12,8 @@ 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 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy { "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 diff --git a/src/Renci.SshNet/PrivateKeyFile.OpenSSH.cs b/src/Renci.SshNet/PrivateKeyFile.OpenSSH.cs index ead73bb50..a91635146 100644 --- a/src/Renci.SshNet/PrivateKeyFile.OpenSSH.cs +++ b/src/Renci.SshNet/PrivateKeyFile.OpenSSH.cs @@ -8,7 +8,8 @@ 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 @@ public Key Parse() { 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)); diff --git a/src/Renci.SshNet/PrivateKeyFile.PKCS1.cs b/src/Renci.SshNet/PrivateKeyFile.PKCS1.cs index f0dc68ec6..606bb00fb 100644 --- a/src/Renci.SshNet/PrivateKeyFile.PKCS1.cs +++ b/src/Renci.SshNet/PrivateKeyFile.PKCS1.cs @@ -9,8 +9,8 @@ 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 @@ public Key Parse() 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)); diff --git a/src/Renci.SshNet/PrivateKeyFile.SSHCOM.cs b/src/Renci.SshNet/PrivateKeyFile.SSHCOM.cs index 290494c4b..568170ee8 100644 --- a/src/Renci.SshNet/PrivateKeyFile.SSHCOM.cs +++ b/src/Renci.SshNet/PrivateKeyFile.SSHCOM.cs @@ -7,7 +7,8 @@ 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 @@ public Key Parse() } 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 diff --git a/src/Renci.SshNet/PrivateKeyFile.cs b/src/Renci.SshNet/PrivateKeyFile.cs index b9dbdf5e1..7a130e0e6 100644 --- a/src/Renci.SshNet/PrivateKeyFile.cs +++ b/src/Renci.SshNet/PrivateKeyFile.cs @@ -48,9 +48,6 @@ namespace Renci.SshNet /// DES-EDE3-CFB /// /// - /// DES-CBC - /// - /// /// AES-128-CBC /// /// diff --git a/src/Renci.SshNet/Security/Cryptography/BlockCipher.cs b/src/Renci.SshNet/Security/Cryptography/BlockCipher.cs index a1b4b2eba..bfd640ebe 100644 --- a/src/Renci.SshNet/Security/Cryptography/BlockCipher.cs +++ b/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 @@ public abstract class BlockCipher : SymmetricCipher { private readonly CipherMode _mode; - private readonly CipherPadding _padding; + private readonly IBlockCipherPadding _padding; /// /// Gets the size of the block in bytes. @@ -56,7 +58,7 @@ public byte BlockSize /// Cipher mode. /// Cipher padding. /// is . - 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 @@ public override byte[] Encrypt(byte[] input, int offset, int length) 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; } diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.BclImpl.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.BclImpl.cs index 2e1850896..76e43e949 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.BclImpl.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.BclImpl.cs @@ -52,6 +52,8 @@ public override byte[] Encrypt(byte[] input, int offset, int length) { 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 @@ public override byte[] Encrypt(byte[] input, int offset, int length) if (paddingLength > 0) { + // Manually unpad the output. Array.Resize(ref output, output.Length - paddingLength); } @@ -89,11 +92,12 @@ public override byte[] Decrypt(byte[] input, int offset, int length) { 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 @@ public override byte[] Decrypt(byte[] input, int offset, int length) if (paddingLength > 0) { + // Manually unpad the output. Array.Resize(ref output, output.Length - paddingLength); } @@ -123,21 +128,11 @@ public override int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputC 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(); } } } diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.BlockImpl.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.BlockImpl.cs index ff261d767..0f5a8d204 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.BlockImpl.cs +++ b/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 @@ private sealed class BlockImpl : BlockCipher, IDisposable 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 @@ public override int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputC 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(); } } } diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.CtrImpl.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.CtrImpl.cs index fb60acdf2..0d4dde5cd 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.CtrImpl.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.CtrImpl.cs @@ -105,20 +105,10 @@ private static void ArrayXOR(byte[] buffer, byte[] data, int offset, int length) } } - 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(); } } } diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.cs index a7c16f72c..e92b66d13 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.cs +++ b/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 @@ public sealed partial class AesCipher : BlockCipher, IDisposable /// Initializes a new instance of the class. /// /// The key. - /// The mode. /// The IV. + /// The mode. /// Enable PKCS7 padding. /// is . /// Keysize is not valid for this algorithm. @@ -28,13 +29,13 @@ public AesCipher(byte[] key, byte[] iv, AesCipherMode mode, bool pkcs7Padding = 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 @@ public override byte[] Decrypt(byte[] input, int offset, int length) return _impl.Decrypt(input, offset, length); } - /// - /// Dispose the instance. - /// - /// Set to True to dispose of resouces. - public void Dispose(bool disposing) + /// + public void Dispose() { - if (disposing && _impl is IDisposable disposableImpl) + if (_impl is IDisposable disposableImpl) { disposableImpl.Dispose(); } } - - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } } } diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/CipherPadding.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/CipherPadding.cs deleted file mode 100644 index cfe5df882..000000000 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/CipherPadding.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace Renci.SshNet.Security.Cryptography.Ciphers -{ - /// - /// Base class for cipher padding implementations. - /// - public abstract class CipherPadding - { - /// - /// Pads the specified input to match the block size. - /// - /// Size of the block. - /// The input. - /// - /// Padded data array. - /// - public byte[] Pad(int blockSize, byte[] input) - { - return Pad(blockSize, input, 0, input.Length); - } - - /// - /// Pads the specified input to match the block size. - /// - /// Size of the block. - /// The input. - /// The zero-based offset in at which the data to pad starts. - /// The number of bytes in to take into account. - /// - /// The padded data array. - /// - public abstract byte[] Pad(int blockSize, byte[] input, int offset, int length); - - /// - /// Pads the specified input with a given number of bytes. - /// - /// The input. - /// The number of bytes to pad the input with. - /// - /// The padded data array. - /// - public byte[] Pad(byte[] input, int paddinglength) - { - return Pad(input, 0, input.Length, paddinglength); - } - - /// - /// Pads the specified input with a given number of bytes. - /// - /// The input. - /// The zero-based offset in at which the data to pad starts. - /// The number of bytes in to take into account. - /// The number of bytes to pad the input with. - /// - /// The padded data array. - /// - public abstract byte[] Pad(byte[] input, int offset, int length, int paddinglength); - - /// - /// Gets the padd count from the specified input. - /// - /// The input. - /// The padd count. - public abstract int PadCount(byte[] input); - } -} diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/DesCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/DesCipher.cs deleted file mode 100644 index 9d38df923..000000000 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/DesCipher.cs +++ /dev/null @@ -1,479 +0,0 @@ -using System; -using System.Buffers.Binary; - -namespace Renci.SshNet.Security.Cryptography.Ciphers -{ - /// - /// Implements DES cipher algorithm. - /// - 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 - }; - - /// - /// Initializes a new instance of the class. - /// - /// The key. - /// The mode. - /// The padding. - /// is . - public DesCipher(byte[] key, CipherMode mode, CipherPadding padding) - : base(key, 8, mode, padding) - { - } - - /// - /// Encrypts the specified region of the input byte array and copies the encrypted data to the specified region of the output byte array. - /// - /// The input data to encrypt. - /// The offset into the input byte array from which to begin using data. - /// The number of bytes in the input byte array to use as data. - /// The output to which to write encrypted data. - /// The offset into the output byte array from which to begin writing data. - /// - /// The number of bytes encrypted. - /// - 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; - } - - /// - /// Decrypts the specified region of the input byte array and copies the decrypted data to the specified region of the output byte array. - /// - /// The input data to decrypt. - /// The offset into the input byte array from which to begin using data. - /// The number of bytes in the input byte array to use as data. - /// The output to which to write decrypted data. - /// The offset into the output byte array from which to begin writing data. - /// - /// The number of bytes decrypted. - /// - 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; - } - - /// - /// Generates the working key. - /// - /// if set to [encrypting]. - /// The key. - /// Generated working key. - 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; - } - - /// - /// Validates the key. - /// - 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)); - } - } - - /// - /// Performs DES function. - /// - /// The w key. - /// The input. - /// The in off. - /// The out bytes. - /// The out off. - 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); - } - } -} diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/Paddings/PKCS7Padding.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/Paddings/PKCS7Padding.cs deleted file mode 100644 index 5a56002c9..000000000 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/Paddings/PKCS7Padding.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; - -using Renci.SshNet.Common; - -namespace Renci.SshNet.Security.Cryptography.Ciphers.Paddings -{ - /// - /// Implements PKCS7 cipher padding. - /// - public class PKCS7Padding : CipherPadding - { - /// - /// Pads the specified input to match the block size. - /// - /// The size of the block. - /// The input. - /// The zero-based offset in at which the data to pad starts. - /// The number of bytes in to take into account. - /// - /// The padded data array. - /// - public override byte[] Pad(int blockSize, byte[] input, int offset, int length) - { - var numOfPaddedBytes = blockSize - (length % blockSize); - return Pad(input, offset, length, numOfPaddedBytes); - } - - /// - /// Pads the specified input with a given number of bytes. - /// - /// The input. - /// The zero-based offset in at which the data to pad starts. - /// The number of bytes in to take into account. - /// The number of bytes to pad the input with. - /// - /// The padded data array. - /// - 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; - } - - /// - 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; - } - } -} diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/TripleDesCipher.BclImpl.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/TripleDesCipher.BclImpl.cs new file mode 100644 index 000000000..6c0c5f233 --- /dev/null +++ b/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(); + } + } + } +} diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/TripleDesCipher.BlockImpl.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/TripleDesCipher.BlockImpl.cs new file mode 100644 index 000000000..53f9b56fd --- /dev/null +++ b/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 diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/TripleDesCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/TripleDesCipher.cs index eb1c0839d..0a6aec5dc 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/TripleDesCipher.cs +++ b/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 { /// /// Implements 3DES cipher algorithm. /// - 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 /// /// Initializes a new instance of the class. /// /// The key. + /// The IV. /// The mode. - /// The padding. + /// Enable PKCS7 padding. /// is . - 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) { - } - - /// - /// Encrypts the specified region of the input byte array and copies the encrypted data to the specified region of the output byte array. - /// - /// The input data to encrypt. - /// The offset into the input byte array from which to begin using data. - /// The number of bytes in the input byte array to use as data. - /// The output to which to write encrypted data. - /// The offset into the output byte array from which to begin writing data. - /// - /// The number of bytes encrypted. - /// - 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]; + /// + 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); + /// + public override byte[] Decrypt(byte[] input, int offset, int length) + { + return _impl.Decrypt(input, offset, length); + } - return BlockSize; + /// + public override int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) + { + return _impl.EncryptBlock(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset); } - /// - /// Decrypts the specified region of the input byte array and copies the decrypted data to the specified region of the output byte array. - /// - /// The input data to decrypt. - /// The offset into the input byte array from which to begin using data. - /// The number of bytes in the input byte array to use as data. - /// The output to which to write decrypted data. - /// The offset into the output byte array from which to begin writing data. - /// - /// The number of bytes decrypted. - /// + /// 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); } - /// - /// Validates the key. - /// - protected override void ValidateKey() + /// + 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 } } } diff --git a/test/Data/Key.RSA.Encrypted.Des.CBC.12345.pub b/test/Data/Key.RSA.Encrypted.Des.CBC.12345.pub deleted file mode 100644 index 0c98bc9f0..000000000 --- a/test/Data/Key.RSA.Encrypted.Des.CBC.12345.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCs7PAiYUZLgiAKPJpJ62JFBrE1d4lfG0w5vbTkMuwJvYmvWyBPU+Hc7jXxgK1iqsz/s0wDZutENyE9VBilTHAYDBOauXjSfQwlo7zHmK1HZ7h87jcIhltpY0NzBGmd/lQ+yDeXiSFGGoFyjwW6VpOfs0AR+oLA2Hpy4b9lI/QWzGPnSz53LVpALI9ssx15OgwjCNxUW+gjMMNrDN4Gz8EryvY28fwGVgPt6uZeT7bU02aSdcsTvWneGwoNeKIGWuwfIXghiTzIosijMbftWnWVNylM5hQOYlQloxVtCCKe5vnz5PeYfwE38yElu7XV6LqEibFNjor9Mcsc+Rr7d/rN diff --git a/test/Data/Key.RSA.Encrypted.Des.CBC.12345.txt b/test/Data/Key.RSA.Encrypted.Des.CBC.12345.txt deleted file mode 100644 index 9ce0163d3..000000000 --- a/test/Data/Key.RSA.Encrypted.Des.CBC.12345.txt +++ /dev/null @@ -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----- diff --git a/test/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/TripleDesCipherBenchmarks.cs b/test/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/TripleDesCipherBenchmarks.cs new file mode 100644 index 000000000..1d45f4281 --- /dev/null +++ b/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); + } + } +} diff --git a/test/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs b/test/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs index d28ba0a06..3e0935ff6 100644 --- a/test/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs +++ b/test/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs @@ -361,7 +361,6 @@ public void ConstructorWithFileNameAndPassPhraseShouldBeAbleToReadFileThatIsShar [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))] diff --git a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/BlockCipherTest.cs b/test/Renci.SshNet.Tests/Classes/Security/Cryptography/BlockCipherTest.cs index b089cfa9c..04bb15e01 100644 --- a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/BlockCipherTest.cs +++ b/test/Renci.SshNet.Tests/Classes/Security/Cryptography/BlockCipherTest.cs @@ -3,10 +3,11 @@ 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 @@ public void EncryptShouldTakeIntoAccountPaddingForLengthOfInputBufferPassedToEnc 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 @@ public void EncryptShouldTakeIntoAccountPaddingForLengthOfInputBufferPassedToEnc 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 @@ public void DecryptShouldTakeIntoAccountUnPaddingForTheFinalOutput() 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 @@ private class BlockCipherStub : BlockCipher public Func EncryptBlockDelegate; public Func 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) { } diff --git a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/DesCipherTest.cs b/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/DesCipherTest.cs deleted file mode 100644 index 23448c12d..000000000 --- a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/DesCipherTest.cs +++ /dev/null @@ -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 -{ - /// - /// Implements DES cipher algorithm. - /// - [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)); - } - } -} diff --git a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/Paddings/PKCS7PaddingTest.cs b/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/Paddings/PKCS7PaddingTest.cs deleted file mode 100644 index 801cf5e3a..000000000 --- a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/Paddings/PKCS7PaddingTest.cs +++ /dev/null @@ -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)); - } - } -} diff --git a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/TripleDesCipherTest.Gen.cs.txt b/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/TripleDesCipherTest.Gen.cs.txt new file mode 100644 index 000000000..bf0b2749a --- /dev/null +++ b/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 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("};"); +} diff --git a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/TripleDesCipherTest.cs b/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/TripleDesCipherTest.cs index da340cd9d..7bec3ca23 100644 --- a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/TripleDesCipherTest.cs +++ b/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 { /// @@ -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); } } }