Skip to content

Commit

Permalink
Use System.Security.Cryptography for TripleDesCipher (#1546)
Browse files Browse the repository at this point in the history
* Use System.Security.Cryptography in DesCipher and TripleDesCipher; Fall back to use BouncyCastle if BCL doesn't support

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

* Restore `CbcCipherMode`

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

* Restore the xml doc comment
  • Loading branch information
scott-xu authored Dec 27, 2024
1 parent 29997ae commit 14c652c
Show file tree
Hide file tree
Showing 26 changed files with 751 additions and 988 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions src/Renci.SshNet/ConnectionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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<string, HashInfo>
Expand Down
5 changes: 3 additions & 2 deletions src/Renci.SshNet/PrivateKeyFile.OpenSSH.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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));
Expand Down
11 changes: 4 additions & 7 deletions src/Renci.SshNet/PrivateKeyFile.PKCS1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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));
Expand Down
5 changes: 3 additions & 2 deletions src/Renci.SshNet/PrivateKeyFile.SSHCOM.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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
Expand Down
3 changes: 0 additions & 3 deletions src/Renci.SshNet/PrivateKeyFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ namespace Renci.SshNet
/// <description>DES-EDE3-CFB</description>
/// </item>
/// <item>
/// <description>DES-CBC</description>
/// </item>
/// <item>
/// <description>AES-128-CBC</description>
/// </item>
/// <item>
Expand Down
10 changes: 7 additions & 3 deletions src/Renci.SshNet/Security/Cryptography/BlockCipher.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,7 +15,7 @@ public abstract class BlockCipher : SymmetricCipher
{
private readonly CipherMode _mode;

private readonly CipherPadding _padding;
private readonly IBlockCipherPadding _padding;

/// <summary>
/// Gets the size of the block in bytes.
Expand Down Expand Up @@ -56,7 +58,7 @@ public byte BlockSize
/// <param name="mode">Cipher mode.</param>
/// <param name="padding">Cipher padding.</param>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
protected BlockCipher(byte[] key, byte blockSize, CipherMode mode, CipherPadding padding)
protected BlockCipher(byte[] key, byte blockSize, CipherMode mode, IBlockCipherPadding padding)
: base(key)
{
_blockSize = blockSize;
Expand All @@ -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;
}
Expand Down
29 changes: 12 additions & 17 deletions src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.BclImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}

Expand All @@ -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;
}
}
Expand All @@ -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);
}

Expand All @@ -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();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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();
Expand All @@ -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();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}
Expand Down
26 changes: 8 additions & 18 deletions src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -17,8 +18,8 @@ public sealed partial class AesCipher : BlockCipher, IDisposable
/// Initializes a new instance of the <see cref="AesCipher"/> class.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="mode">The mode.</param>
/// <param name="iv">The IV.</param>
/// <param name="mode">The mode.</param>
/// <param name="pkcs7Padding">Enable PKCS7 padding.</param>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">Keysize is not valid for this algorithm.</exception>
Expand All @@ -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)
Expand Down Expand Up @@ -76,24 +77,13 @@ public override byte[] Decrypt(byte[] input, int offset, int length)
return _impl.Decrypt(input, offset, length);
}

/// <summary>
/// Dispose the instance.
/// </summary>
/// <param name="disposing">Set to True to dispose of resouces.</param>
public void Dispose(bool disposing)
/// <inheritdoc/>
public void Dispose()
{
if (disposing && _impl is IDisposable disposableImpl)
if (_impl is IDisposable disposableImpl)
{
disposableImpl.Dispose();
}
}

/// <inheritdoc/>
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}
Loading

0 comments on commit 14c652c

Please sign in to comment.