mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-09-10 09:15:47 +00:00
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>
This commit is contained in:
@@ -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()));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -161,19 +161,22 @@ 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();
|
||||
|
||||
Modulus = der.ReadInteger();
|
||||
Exponent = der.ReadInteger();
|
||||
D = der.ReadInteger();
|
||||
P = der.ReadInteger();
|
||||
Q = der.ReadInteger();
|
||||
DP = der.ReadInteger();
|
||||
DQ = der.ReadInteger();
|
||||
InverseQ = der.ReadInteger();
|
||||
_ = sequenceReader.ReadInteger(); // skip version
|
||||
|
||||
der.ThrowIfNotEmpty();
|
||||
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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+109
-88
@@ -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);
|
||||
|
||||
byte[] key = new byte[keySize / 8];
|
||||
random.NextBytes(key);
|
||||
|
||||
byte[] iv = new byte[16];
|
||||
random.NextBytes(iv);
|
||||
|
||||
StringBuilder openSslCmd = new();
|
||||
|
||||
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")
|
||||
foreach (bool pad in new bool[] { false, true })
|
||||
{
|
||||
openSslCmd.Append($" -iv {Convert.ToHexString(iv)}");
|
||||
}
|
||||
openSslCmd.Append(" -nopad");
|
||||
// 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;
|
||||
}
|
||||
|
||||
ProcessStartInfo pi = new("wsl", openSslCmd.ToString())
|
||||
{
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
};
|
||||
// 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[] expected;
|
||||
string error;
|
||||
byte[] input = new byte[inputLength];
|
||||
random.NextBytes(input);
|
||||
|
||||
using (MemoryStream ms = new())
|
||||
{
|
||||
var p = Process.Start(pi);
|
||||
p.StandardOutput.BaseStream.CopyTo(ms);
|
||||
error = p.StandardError.ReadToEnd();
|
||||
byte[] key = new byte[keySize / 8];
|
||||
random.NextBytes(key);
|
||||
|
||||
p.WaitForExit();
|
||||
byte[] iv = new byte[16];
|
||||
random.NextBytes(iv);
|
||||
|
||||
expected = ms.ToArray();
|
||||
}
|
||||
StringBuilder openSslCmd = new();
|
||||
|
||||
tw.WriteLine("[TestMethod]");
|
||||
tw.WriteLine($"public void AES_{mode.ToUpper()}_{keySize}_Length{inputLength}()");
|
||||
tw.WriteLine("{");
|
||||
tw.Indent++;
|
||||
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)}");
|
||||
}
|
||||
|
||||
WriteBytes(input);
|
||||
WriteBytes(key);
|
||||
if (mode != "ecb")
|
||||
{
|
||||
WriteBytes(iv);
|
||||
}
|
||||
tw.WriteLine();
|
||||
if (!pad)
|
||||
{
|
||||
openSslCmd.Append(" -nopad");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(error))
|
||||
{
|
||||
tw.WriteLine($"// {openSslCmd}");
|
||||
tw.WriteLine($"Assert.Fail(@\"{error}\");");
|
||||
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 AES_{mode.ToUpper()}_{keySize}_Length{inputLength}_{(pad ? "Pad" : "NoPad")}()");
|
||||
tw.WriteLine("{");
|
||||
tw.Indent++;
|
||||
|
||||
WriteBytes(input);
|
||||
WriteBytes(key);
|
||||
if (mode != "ecb")
|
||||
{
|
||||
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 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.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();
|
||||
continue;
|
||||
}
|
||||
|
||||
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);");
|
||||
|
||||
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))
|
||||
{
|
||||
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 AesCipher(key, {modeCode}, pkcs7Padding: false).Decrypt(actual);");
|
||||
tw.WriteLine();
|
||||
tw.WriteLine($"CollectionAssert.AreEqual(input, decrypted);");
|
||||
|
||||
tw.Indent--;
|
||||
tw.WriteLine("}");
|
||||
tw.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user