瀏覽代碼

Add padding when encrypt and remove padding when decrypt (#1545)

* Tighten private key checking to reveal padding issue

* `Encrypt` should take into account padding for length of `inputBuffer` passed to `EncryptBlock` if padding is specified, no matter input is divisible or not.

* `Decrypt` should take into account unpadding for the final output if padding is specified.

* `Decrypt` should take into account *manual* padding for length of `inputBuffer` passed to `DecryptBlock` and unpadding for the final output if padding is not specified and mode is CFB or OFB.

* `Encrypt` should take into account *manual* padding for length of `inputBuffer` passed to `EncryptBlock` and unpadding for the final output if padding is not specified and mode is CFB or OFB.

* Rectify DES cipher tests. There's no padding in the data.

* Borrow `PadCount` method from BouncyCastle

* Manually pad input in CTR mode as well. Update AesCipherTest.

Co-Authored-By: Rob Hague <5132141+Rob-Hague@users.noreply.github.com>

* Manually pad/unpad for Aes CFB/OFB mode

* Update test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/AesCipherTest.Gen.cs.txt

Co-authored-by: Rob Hague <rob.hague00@gmail.com>

* Re-generate AES cipher tests

---------

Co-authored-by: Rob Hague <5132141+Rob-Hague@users.noreply.github.com>
Co-authored-by: Rob Hague <rob.hague00@gmail.com>
Scott Xu 10 月之前
父節點
當前提交
29997aebe1

+ 1 - 1
src/Renci.SshNet/PrivateKeyFile.PKCS1.cs

@@ -54,7 +54,7 @@ namespace Renci.SshNet
                             cipher = new CipherInfo(192, (key, iv) => new TripleDesCipher(key, new CbcCipherMode(iv), new PKCS7Padding()));
                             break;
                         case "DES-EDE3-CFB":
-                            cipher = new CipherInfo(192, (key, iv) => new TripleDesCipher(key, new CfbCipherMode(iv), new PKCS7Padding()));
+                            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()));

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

@@ -8,7 +8,6 @@ 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;
 
 namespace Renci.SshNet
 {
@@ -52,7 +51,7 @@ namespace Renci.SshNet
                     }
 
                     var key = GetCipherKey(_passPhrase, 192 / 8);
-                    var ssh2Сipher = new TripleDesCipher(key, new CbcCipherMode(new byte[8]), new PKCS7Padding());
+                    var ssh2Сipher = new TripleDesCipher(key, new CbcCipherMode(new byte[8]), padding: null);
                     keyData = ssh2Сipher.Decrypt(reader.ReadBytes(blobSize));
                 }
                 else

+ 45 - 12
src/Renci.SshNet/Security/Cryptography/BlockCipher.cs

@@ -1,6 +1,8 @@
 using System;
 
+using Renci.SshNet.Common;
 using Renci.SshNet.Security.Cryptography.Ciphers;
+using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
 
 namespace Renci.SshNet.Security.Cryptography
 {
@@ -75,18 +77,29 @@ namespace Renci.SshNet.Security.Cryptography
         /// </returns>
         public override byte[] Encrypt(byte[] input, int offset, int length)
         {
-            if (length % _blockSize > 0)
+            var paddingLength = 0;
+            if (_padding is not null)
             {
-                if (_padding is null)
-                {
-                    throw new ArgumentException(string.Format("The data block size is incorrect for {0}.", GetType().Name), "data");
-                }
-
-                var paddingLength = _blockSize - (length % _blockSize);
+                paddingLength = _blockSize - (length % _blockSize);
                 input = _padding.Pad(input, offset, length, paddingLength);
                 length += paddingLength;
                 offset = 0;
             }
+            else if (length % _blockSize > 0)
+            {
+                if (_mode is CfbCipherMode or OfbCipherMode or CtrCipherMode)
+                {
+                    paddingLength = _blockSize - (length % _blockSize);
+                    input = input.Take(offset, length);
+                    length += paddingLength;
+                    Array.Resize(ref input, length);
+                    offset = 0;
+                }
+                else
+                {
+                    throw new ArgumentException(string.Format("The data block size is incorrect for {0}.", GetType().Name), "data");
+                }
+            }
 
             var output = new byte[length];
             var writtenBytes = 0;
@@ -108,6 +121,11 @@ namespace Renci.SshNet.Security.Cryptography
                 throw new InvalidOperationException("Encryption error.");
             }
 
+            if (_padding is null && paddingLength > 0)
+            {
+                Array.Resize(ref output, output.Length - paddingLength);
+            }
+
             return output;
         }
 
@@ -122,16 +140,21 @@ namespace Renci.SshNet.Security.Cryptography
         /// </returns>
         public override byte[] Decrypt(byte[] input, int offset, int length)
         {
+            var paddingLength = 0;
             if (length % _blockSize > 0)
             {
-                if (_padding is null)
+                if (_padding is null && _mode is CfbCipherMode or OfbCipherMode or CtrCipherMode)
+                {
+                    paddingLength = _blockSize - (length % _blockSize);
+                    input = input.Take(offset, length);
+                    length += paddingLength;
+                    Array.Resize(ref input, length);
+                    offset = 0;
+                }
+                else
                 {
                     throw new ArgumentException(string.Format("The data block size is incorrect for {0}.", GetType().Name), "data");
                 }
-
-                input = _padding.Pad(_blockSize, input, offset, length);
-                offset = 0;
-                length = input.Length;
             }
 
             var output = new byte[length];
@@ -154,6 +177,16 @@ namespace Renci.SshNet.Security.Cryptography
                 throw new InvalidOperationException("Encryption error.");
             }
 
+            if (_padding is not null)
+            {
+                paddingLength = _padding.PadCount(output);
+            }
+
+            if (paddingLength > 0)
+            {
+                Array.Resize(ref output, output.Length - paddingLength);
+            }
+
             return output;
         }
 

+ 39 - 0
src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.BclImpl.cs

@@ -47,12 +47,31 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers
                     return _encryptor.TransformFinalBlock(input, offset, length);
                 }
 
+                var paddingLength = 0;
+                if (length % BlockSize > 0)
+                {
+                    if (_aes.Mode is System.Security.Cryptography.CipherMode.CFB or System.Security.Cryptography.CipherMode.OFB)
+                    {
+                        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)
+                {
+                    Array.Resize(ref output, output.Length - paddingLength);
+                }
+
                 return output;
             }
 
@@ -65,12 +84,32 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers
                     return _decryptor.TransformFinalBlock(input, offset, length);
                 }
 
+                var paddingLength = 0;
+                if (length % BlockSize > 0)
+                {
+                    if (_aes.Mode is System.Security.Cryptography.CipherMode.CFB or System.Security.Cryptography.CipherMode.OFB)
+                    {
+                        paddingLength = BlockSize - (length % BlockSize);
+                        var newInput = new byte[input.Length + paddingLength];
+                        Buffer.BlockCopy(input, offset, newInput, 0, length);
+                        input = newInput;
+                        length = 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 Decrypt.
                 var output = new byte[length];
                 _ = _decryptor.TransformBlock(input, offset, length, output, 0);
+
+                if (paddingLength > 0)
+                {
+                    Array.Resize(ref output, output.Length - paddingLength);
+                }
+
                 return output;
             }
 

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

@@ -54,5 +54,12 @@
         /// 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);
     }
 }

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

@@ -1,5 +1,7 @@
 using System;
 
+using Renci.SshNet.Common;
+
 namespace Renci.SshNet.Security.Cryptography.Ciphers.Paddings
 {
     /// <summary>
@@ -45,5 +47,26 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers.Paddings
 
             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;
+        }
     }
 }

+ 11 - 8
src/Renci.SshNet/Security/Cryptography/DsaKey.cs

@@ -113,16 +113,19 @@ namespace Renci.SshNet.Security
         {
             ThrowHelper.ThrowIfNull(privateKeyData);
 
-            var der = new AsnReader(privateKeyData, AsnEncodingRules.DER).ReadSequence();
-            _ = der.ReadInteger(); // skip version
+            var keyReader = new AsnReader(privateKeyData, AsnEncodingRules.DER);
+            var sequenceReader = keyReader.ReadSequence();
+            keyReader.ThrowIfNotEmpty();
 
-            P = der.ReadInteger();
-            Q = der.ReadInteger();
-            G = der.ReadInteger();
-            Y = der.ReadInteger();
-            X = der.ReadInteger();
+            _ = sequenceReader.ReadInteger(); // skip version
 
-            der.ThrowIfNotEmpty();
+            P = sequenceReader.ReadInteger();
+            Q = sequenceReader.ReadInteger();
+            G = sequenceReader.ReadInteger();
+            Y = sequenceReader.ReadInteger();
+            X = sequenceReader.ReadInteger();
+
+            sequenceReader.ThrowIfNotEmpty();
 
             DSA = LoadDSA();
         }

+ 9 - 6
src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs

@@ -218,18 +218,21 @@ namespace Renci.SshNet.Security
         /// <param name="data">DER encoded private key data.</param>
         public EcdsaKey(byte[] data)
         {
-            var der = new AsnReader(data, AsnEncodingRules.DER).ReadSequence();
-            _ = der.ReadInteger(); // skip version
+            var keyReader = new AsnReader(data, AsnEncodingRules.DER);
+            var sequenceReader = keyReader.ReadSequence();
+            keyReader.ThrowIfNotEmpty();
 
-            var privatekey = der.ReadOctetString().TrimLeadingZeros();
+            _ = sequenceReader.ReadInteger(); // skip version
 
-            var s0 = der.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 0, isConstructed: true));
+            var privatekey = sequenceReader.ReadOctetString().TrimLeadingZeros();
+
+            var s0 = sequenceReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 0, isConstructed: true));
             var curve = s0.ReadObjectIdentifier();
 
-            var s1 = der.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 1, isConstructed: true));
+            var s1 = sequenceReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 1, isConstructed: true));
             var pubkey = s1.ReadBitString(out _);
 
-            der.ThrowIfNotEmpty();
+            sequenceReader.ThrowIfNotEmpty();
 
             _impl = Import(curve, pubkey, privatekey);
         }

+ 16 - 13
src/Renci.SshNet/Security/Cryptography/RsaKey.cs

@@ -161,19 +161,22 @@ namespace Renci.SshNet.Security
         {
             ThrowHelper.ThrowIfNull(privateKeyData);
 
-            var der = new AsnReader(privateKeyData, AsnEncodingRules.DER).ReadSequence();
-            _ = der.ReadInteger(); // skip version
-
-            Modulus = der.ReadInteger();
-            Exponent = der.ReadInteger();
-            D = der.ReadInteger();
-            P = der.ReadInteger();
-            Q = der.ReadInteger();
-            DP = der.ReadInteger();
-            DQ = der.ReadInteger();
-            InverseQ = der.ReadInteger();
-
-            der.ThrowIfNotEmpty();
+            var keyReader = new AsnReader(privateKeyData, AsnEncodingRules.DER);
+            var sequenceReader = keyReader.ReadSequence();
+            keyReader.ThrowIfNotEmpty();
+
+            _ = sequenceReader.ReadInteger(); // skip version
+
+            Modulus = sequenceReader.ReadInteger();
+            Exponent = sequenceReader.ReadInteger();
+            D = sequenceReader.ReadInteger();
+            P = sequenceReader.ReadInteger();
+            Q = sequenceReader.ReadInteger();
+            DP = sequenceReader.ReadInteger();
+            DQ = sequenceReader.ReadInteger();
+            InverseQ = sequenceReader.ReadInteger();
+
+            sequenceReader.ThrowIfNotEmpty();
 
             RSA = RSA.Create();
             RSA.ImportParameters(GetRSAParameters());

+ 88 - 4
test/Renci.SshNet.Tests/Classes/Security/Cryptography/BlockCipherTest.cs

@@ -5,6 +5,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
 
 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;
 
@@ -14,7 +15,7 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography
     public class BlockCipherTest : TestBase
     {
         [TestMethod]
-        public void EncryptShouldTakeIntoAccountPaddingForLengthOfOutputBufferPassedToEncryptBlock()
+        public void EncryptShouldTakeIntoAccountPaddingForLengthOfInputBufferPassedToEncryptBlock_InputNotDivisible()
         {
             var input = new byte[] { 0x2c, 0x1a, 0x05, 0x00, 0x68 };
             var output = new byte[] { 0x0a, 0x00, 0x03, 0x02, 0x06, 0x08, 0x07, 0x05 };
@@ -23,7 +24,7 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography
             {
                 EncryptBlockDelegate = (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset) =>
                     {
-                        Assert.AreEqual(8, outputBuffer.Length);
+                        Assert.AreEqual(8, inputBuffer.Length);
                         Buffer.BlockCopy(output, 0, outputBuffer, 0, output.Length);
                         return inputBuffer.Length;
                     }
@@ -35,17 +36,61 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography
         }
 
         [TestMethod]
-        public void DecryptShouldTakeIntoAccountPaddingForLengthOfOutputBufferPassedToDecryptBlock()
+        public void EncryptShouldTakeIntoAccountPaddingForLengthOfInputBufferPassedToEncryptBlock_InputDivisible()
         {
-            var input = new byte[] { 0x2c, 0x1a, 0x05, 0x00, 0x68 };
+            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())
+            {
+                EncryptBlockDelegate = (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset) =>
+                {
+                    Assert.AreEqual(8, inputBuffer.Length);
+                    Buffer.BlockCopy(output, 0, outputBuffer, 0, output.Length);
+                    return inputBuffer.Length;
+                }
+            };
+
+            var actual = blockCipher.Encrypt(input);
+
+            Assert.IsTrue(output.SequenceEqual(actual));
+        }
+
+        [TestMethod]
+        public void EncryptShouldTakeIntoAccountManualPaddingForLengthOfInputBufferPassedToDecryptBlockAndUnPaddingForTheFinalOutput_CFB()
+        {
+            var input = new byte[] { 0x0a, 0x00, 0x03, 0x02, 0x06 };
+            var output = new byte[] { 0x2c, 0x1a, 0x05, 0x00, 0x68 };
+            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, new CfbCipherModeStub(new byte[8]), null)
+            {
+                EncryptBlockDelegate = (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset) =>
+                {
+                    Assert.AreEqual(8, inputBuffer.Length);
+                    Buffer.BlockCopy(output, 0, outputBuffer, 0, output.Length);
+                    return inputBuffer.Length;
+                }
+            };
+
+            var actual = blockCipher.Encrypt(input);
+
+            Assert.IsTrue(output.SequenceEqual(actual));
+        }
+
+        [TestMethod]
+        public void DecryptShouldTakeIntoAccountUnPaddingForTheFinalOutput()
+        {
+            var input = new byte[] { 0x0a, 0x00, 0x03, 0x02, 0x06, 0x08, 0x07, 0x05 };
+            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())
             {
                 DecryptBlockDelegate = (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset) =>
                     {
                         Assert.AreEqual(8, outputBuffer.Length);
                         Buffer.BlockCopy(output, 0, outputBuffer, 0, output.Length);
+                        Buffer.BlockCopy(padding, 0, outputBuffer, output.Length, padding.Length);
                         return inputBuffer.Length;
                     }
             };
@@ -55,6 +100,27 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography
             Assert.IsTrue(output.SequenceEqual(actual));
         }
 
+        [TestMethod]
+        public void DecryptShouldTakeIntoAccountManualPaddingForLengthOfInputBufferPassedToDecryptBlockAndUnPaddingForTheFinalOutput_CFB()
+        {
+            var input = new byte[] { 0x0a, 0x00, 0x03, 0x02, 0x06 };
+            var output = new byte[] { 0x2c, 0x1a, 0x05, 0x00, 0x68 };
+            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, new CfbCipherModeStub(new byte[8]), null)
+            {
+                DecryptBlockDelegate = (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset) =>
+                {
+                    Assert.AreEqual(8, inputBuffer.Length);
+                    Buffer.BlockCopy(output, 0, outputBuffer, 0, output.Length);
+                    return inputBuffer.Length;
+                }
+            };
+
+            var actual = blockCipher.Decrypt(input);
+
+            Assert.IsTrue(output.SequenceEqual(actual));
+        }
+
 
         private class BlockCipherStub : BlockCipher
         {
@@ -75,5 +141,23 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography
                 return DecryptBlockDelegate(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
             }
         }
+
+        private class CfbCipherModeStub : CfbCipherMode
+        {
+            public CfbCipherModeStub(byte[] iv)
+                : base(iv)
+            {
+            }
+
+            public override int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
+            {
+                return Cipher.EncryptBlock(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
+            }
+
+            public override int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
+            {
+                return Cipher.DecryptBlock(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
+            }
+        }
     }
 }

+ 103 - 82
test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/AesCipherTest.Gen.cs.txt

@@ -21,109 +21,130 @@ foreach ((string mode, (string modeCode, CipherMode? bclMode)) in modes)
 {
     foreach (int keySize in new int[] { 128, 192, 256 })
     {
-        foreach (int inputLength in new int[] { 16, 32, 64 })
+        foreach (int inputLength in new int[] { 16, 35, 64 })
         {
-            byte[] input = new byte[inputLength];
-            random.NextBytes(input);
+            foreach (bool pad in new bool[] { false, true })
+            {
+                // It is not allowed to use no padding on non-block lengths
+                // It makes sense in cfb, ctr and ofb modes
+                if (!pad && inputLength % 16 != 0 && mode is not ("cfb" or "ctr" or "ofb"))
+                {
+                    continue;
+                }
 
-            byte[] key = new byte[keySize / 8];
-            random.NextBytes(key);
+                // It does not make sense to test padding for stream cipher modes
+                // (and the OpenSSL, BCL implementations differ)
+                if (pad && mode is "cfb" or "ctr" or "ofb")
+                {
+                    continue;
+                }
 
-            byte[] iv = new byte[16];
-            random.NextBytes(iv);
+                byte[] input = new byte[inputLength];
+                random.NextBytes(input);
 
-            StringBuilder openSslCmd = new();
+                byte[] key = new byte[keySize / 8];
+                random.NextBytes(key);
 
-            openSslCmd.Append($"echo -n -e '{string.Join("", input.Select(b => $"\\x{b:x2}"))}' |");
-            openSslCmd.Append($" openssl enc -e -aes-{keySize}-{mode}");
-            openSslCmd.Append($" -K {Convert.ToHexString(key)}");
-            if (mode != "ecb")
-            {
-                openSslCmd.Append($" -iv {Convert.ToHexString(iv)}");
-            }
-            openSslCmd.Append(" -nopad");
+                byte[] iv = new byte[16];
+                random.NextBytes(iv);
 
-            ProcessStartInfo pi = new("wsl", openSslCmd.ToString())
-            {
-                RedirectStandardOutput = true,
-                RedirectStandardError = true,
-            };
+                StringBuilder openSslCmd = new();
 
-            byte[] expected;
-            string error;
+                openSslCmd.Append($"echo -n -e '{string.Join("", input.Select(b => $"\\x{b:x2}"))}' |");
+                openSslCmd.Append($" openssl enc -e -aes-{keySize}-{mode}");
+                openSslCmd.Append($" -K {Convert.ToHexString(key)}");
+                if (mode != "ecb")
+                {
+                    openSslCmd.Append($" -iv {Convert.ToHexString(iv)}");
+                }
 
-            using (MemoryStream ms = new())
-            {
-                var p = Process.Start(pi);
-                p.StandardOutput.BaseStream.CopyTo(ms);
-                error = p.StandardError.ReadToEnd();
+                if (!pad)
+                {
+                    openSslCmd.Append(" -nopad");
+                }
 
-                p.WaitForExit();
+                ProcessStartInfo pi = new("wsl", openSslCmd.ToString())
+                {
+                    RedirectStandardOutput = true,
+                    RedirectStandardError = true,
+                };
 
-                expected = ms.ToArray();
-            }
+                byte[] expected;
+                string error;
 
-            tw.WriteLine("[TestMethod]");
-            tw.WriteLine($"public void AES_{mode.ToUpper()}_{keySize}_Length{inputLength}()");
-            tw.WriteLine("{");
-            tw.Indent++;
+                using (MemoryStream ms = new())
+                {
+                    var p = Process.Start(pi);
+                    p.StandardOutput.BaseStream.CopyTo(ms);
+                    error = p.StandardError.ReadToEnd();
 
-            WriteBytes(input);
-            WriteBytes(key);
-            if (mode != "ecb")
-            {
-                WriteBytes(iv);
-            }
-            tw.WriteLine();
+                    p.WaitForExit();
 
-            if (!string.IsNullOrWhiteSpace(error))
-            {
-                tw.WriteLine($"// {openSslCmd}");
-                tw.WriteLine($"Assert.Fail(@\"{error}\");");
+                    expected = ms.ToArray();
+                }
 
-                tw.Indent--;
-                tw.WriteLine("}");
-                tw.WriteLine();
-                continue;
-            }
+                tw.WriteLine("[TestMethod]");
+                tw.WriteLine($"public void AES_{mode.ToUpper()}_{keySize}_Length{inputLength}_{(pad ? "Pad" : "NoPad")}()");
+                tw.WriteLine("{");
+                tw.Indent++;
 
-            tw.WriteLine($"// {openSslCmd} | hd"); // pipe to hexdump
-            WriteBytes(expected);
-            tw.WriteLine();
-            tw.WriteLine($"var actual = new AesCipher(key, {modeCode}, pkcs7Padding: false).Encrypt(input);");
-            tw.WriteLine();
-            tw.WriteLine($"CollectionAssert.AreEqual(expected, actual);");
+                WriteBytes(input);
+                WriteBytes(key);
+                if (mode != "ecb")
+                {
+                    WriteBytes(iv);
+                }
+                tw.WriteLine();
 
-            if (bclMode is not null and not CipherMode.OFB)
-            {
-                // Verify the OpenSSL result is the same as the .NET BCL, just to be sure
-                Aes bcl = Aes.Create();
-                bcl.Key = key;
-                bcl.IV = iv;
-                bcl.Mode = bclMode.Value;
-                bcl.Padding = PaddingMode.None;
-                bcl.FeedbackSize = 128; // .NET is CFB8 by default, OpenSSL is CFB128
-                byte[] bclBytes = bcl.CreateEncryptor().TransformFinalBlock(input, 0, input.Length);
-
-                if (!bclBytes.AsSpan().SequenceEqual(expected))
+                if (!string.IsNullOrWhiteSpace(error))
                 {
-                    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.WriteLine($"// {openSslCmd}");
+                    tw.WriteLine($"Assert.Fail(@\"{error}\");");
+
                     tw.Indent--;
+                    tw.WriteLine("}");
+                    tw.WriteLine();
+                    continue;
                 }
-            }
 
-            tw.WriteLine();
-            tw.WriteLine($"var decrypted = new AesCipher(key, {modeCode}, pkcs7Padding: false).Decrypt(actual);");
-            tw.WriteLine();
-            tw.WriteLine($"CollectionAssert.AreEqual(input, decrypted);");
+                tw.WriteLine($"// {openSslCmd} | hd"); // pipe to hexdump
+                WriteBytes(expected);
+                tw.WriteLine();
+                tw.WriteLine($"var actual = new AesCipher(key, {modeCode}, pkcs7Padding: {(pad ? "true" : "false")}).Encrypt(input);");
+                tw.WriteLine();
+                tw.WriteLine($"CollectionAssert.AreEqual(expected, actual);");
+
+                if (bclMode is not null and not CipherMode.OFB and not CipherMode.CFB)
+                {
+                    // Verify the OpenSSL result is the same as the .NET BCL, just to be sure
+                    Aes bcl = Aes.Create();
+                    bcl.Key = key;
+                    bcl.IV = iv;
+                    bcl.Mode = bclMode.Value;
+                    bcl.Padding = pad ? PaddingMode.PKCS7 : PaddingMode.None;
+                    bcl.FeedbackSize = 128; // .NET is CFB8 by default, OpenSSL is CFB128
+                    byte[] bclBytes = bcl.CreateEncryptor().TransformFinalBlock(input, 0, input.Length);
+
+                    if (!bclBytes.AsSpan().SequenceEqual(expected))
+                    {
+                        tw.WriteLine();
+                        tw.WriteLine(@"Assert.Inconclusive(@""OpenSSL does not match the .NET BCL");
+                        tw.Indent++;
+                        tw.WriteLine($@"OpenSSL: {Convert.ToHexString(expected)}");
+                        tw.WriteLine($@"BCL:     {Convert.ToHexString(bclBytes)}"");");
+                        tw.Indent--;
+                    }
+                }
 
-            tw.Indent--;
-            tw.WriteLine("}");
-            tw.WriteLine();
+                tw.WriteLine();
+                tw.WriteLine($"var decrypted = new AesCipher(key, {modeCode}, pkcs7Padding: {(pad ? "true" : "false")}).Decrypt(actual);");
+                tw.WriteLine();
+                tw.WriteLine($"CollectionAssert.AreEqual(input, decrypted);");
+
+                tw.Indent--;
+                tw.WriteLine("}");
+                tw.WriteLine();
+            }
         }
     }
 }

文件差異過大導致無法顯示
+ 542 - 166
test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/AesCipherTest.cs


+ 2 - 3
test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/DesCipherTest.cs

@@ -5,7 +5,6 @@ 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.Security.Cryptography.Ciphers.Paddings;
 using Renci.SshNet.Tests.Common;
 
 namespace Renci.SshNet.Tests.Classes.Security.Cryptography.Ciphers
@@ -30,7 +29,7 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography.Ciphers
             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), new PKCS7Padding());
+            var des = new DesCipher(key, new CbcCipherMode(iv), padding: null);
             var actualCypher = des.Encrypt(input);
 
             Assert.IsTrue((expectedCypher.IsEqualTo(actualCypher)));
@@ -50,7 +49,7 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography.Ciphers
                     0xe3, 0xbd
                 };
 
-            var des = new DesCipher(key, new CbcCipherMode(iv), new PKCS7Padding());
+            var des = new DesCipher(key, new CbcCipherMode(iv), padding: null);
             var plain = des.Decrypt(cypher);
 
             Assert.IsTrue(expectedPlain.IsEqualTo(plain));

部分文件因文件數量過多而無法顯示