Skip to content

Commit

Permalink
Use BCL CipherMode enum for AesCipher class just like TripleDesCipher…
Browse files Browse the repository at this point in the history
… class; Create a dedicated AesCtrCipher class just like AesGcmCipher class.
  • Loading branch information
scott-xu committed Dec 28, 2024
1 parent 14c652c commit 166cd93
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 297 deletions.
12 changes: 6 additions & 6 deletions src/Renci.SshNet/ConnectionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -364,15 +364,15 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy

Encryptions = new Dictionary<string, CipherInfo>
{
{ "aes128-ctr", new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)) },
{ "aes192-ctr", new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)) },
{ "aes256-ctr", new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)) },
{ "aes128-ctr", new CipherInfo(128, (key, iv) => new AesCtrCipher(key, iv)) },
{ "aes192-ctr", new CipherInfo(192, (key, iv) => new AesCtrCipher(key, iv)) },
{ "aes256-ctr", new CipherInfo(256, (key, iv) => new AesCtrCipher(key, iv)) },
{ "[email protected]", new CipherInfo(128, (key, iv) => new AesGcmCipher(key, iv, aadLength: 4), isAead: true) },
{ "[email protected]", new CipherInfo(256, (key, iv) => new AesGcmCipher(key, iv, aadLength: 4), isAead: true) },
{ "[email protected]", new CipherInfo(512, (key, iv) => new ChaCha20Poly1305Cipher(key, aadLength: 4), isAead: true) },
{ "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)) },
{ "aes128-cbc", new CipherInfo(128, (key, iv) => new AesCipher(key, iv, CipherMode.CBC, pkcs7Padding: false)) },
{ "aes192-cbc", new CipherInfo(192, (key, iv) => new AesCipher(key, iv, CipherMode.CBC, pkcs7Padding: false)) },
{ "aes256-cbc", new CipherInfo(256, (key, iv) => new AesCipher(key, iv, CipherMode.CBC, pkcs7Padding: false)) },
{ "3des-cbc", new CipherInfo(192, (key, iv) => new TripleDesCipher(key, iv, CipherMode.CBC, pkcs7Padding: false)) },
};

Expand Down
12 changes: 6 additions & 6 deletions src/Renci.SshNet/PrivateKeyFile.OpenSSH.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,22 +95,22 @@ public Key Parse()
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));
cipherInfo = new CipherInfo(128, (key, iv) => new AesCipher(key, iv, CipherMode.CBC, pkcs7Padding: false));
break;
case "aes192-cbc":
cipherInfo = new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false));
cipherInfo = new CipherInfo(192, (key, iv) => new AesCipher(key, iv, CipherMode.CBC, pkcs7Padding: false));
break;
case "aes256-cbc":
cipherInfo = new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false));
cipherInfo = new CipherInfo(256, (key, iv) => new AesCipher(key, iv, CipherMode.CBC, pkcs7Padding: false));
break;
case "aes128-ctr":
cipherInfo = new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false));
cipherInfo = new CipherInfo(128, (key, iv) => new AesCtrCipher(key, iv));
break;
case "aes192-ctr":
cipherInfo = new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false));
cipherInfo = new CipherInfo(192, (key, iv) => new AesCtrCipher(key, iv));
break;
case "aes256-ctr":
cipherInfo = new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false));
cipherInfo = new CipherInfo(256, (key, iv) => new AesCtrCipher(key, iv));
break;
case "[email protected]":
cipherInfo = new CipherInfo(128, (key, iv) => new AesGcmCipher(key, iv, aadLength: 0), isAead: true);
Expand Down
6 changes: 3 additions & 3 deletions src/Renci.SshNet/PrivateKeyFile.PKCS1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ public Key Parse()
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));
cipher = new CipherInfo(128, (key, iv) => new AesCipher(key, iv, CipherMode.CBC, pkcs7Padding: true));
break;
case "AES-192-CBC":
cipher = new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: true));
cipher = new CipherInfo(192, (key, iv) => new AesCipher(key, iv, CipherMode.CBC, pkcs7Padding: true));
break;
case "AES-256-CBC":
cipher = new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: true));
cipher = new CipherInfo(256, (key, iv) => new AesCipher(key, iv, CipherMode.CBC, pkcs7Padding: true));
break;
default:
throw new SshException(string.Format(CultureInfo.InvariantCulture, "Private key cipher \"{0}\" is not supported.", _cipherName));
Expand Down
4 changes: 3 additions & 1 deletion src/Renci.SshNet/PrivateKeyFile.PuTTY.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
using Renci.SshNet.Security;
using Renci.SshNet.Security.Cryptography.Ciphers;

using CipherMode = System.Security.Cryptography.CipherMode;

namespace Renci.SshNet
{
public partial class PrivateKeyFile
Expand Down Expand Up @@ -111,7 +113,7 @@ public Key Parse()
throw new SshException("PuTTY key file version " + _version + " is not supported");
}

using (var cipher = new AesCipher(cipherKey, cipherIV, AesCipherMode.CBC, pkcs7Padding: false))
using (var cipher = new AesCipher(cipherKey, cipherIV, CipherMode.CBC, pkcs7Padding: false))
{
privateKey = cipher.Decrypt(_data);
}
Expand Down
115 changes: 0 additions & 115 deletions src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.CtrImpl.cs

This file was deleted.

13 changes: 4 additions & 9 deletions src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,32 +23,27 @@ public sealed partial class AesCipher : BlockCipher, IDisposable
/// <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>
public AesCipher(byte[] key, byte[] iv, AesCipherMode mode, bool pkcs7Padding = false)
public AesCipher(byte[] key, byte[] iv, System.Security.Cryptography.CipherMode mode, bool pkcs7Padding = false)
: base(key, 16, mode: null, padding: null)
{
if (mode == AesCipherMode.OFB)
if (mode == System.Security.Cryptography.CipherMode.OFB)
{
// OFB is not supported on modern .NET
_impl = new BlockImpl(key, new OfbCipherMode(iv), pkcs7Padding ? new Pkcs7Padding() : null);
}
#if !NET6_0_OR_GREATER
else if (mode == AesCipherMode.CFB)
else if (mode == System.Security.Cryptography.CipherMode.CFB)
{
// CFB not supported on NetStandard 2.1
_impl = new BlockImpl(key, new CfbCipherMode(iv), pkcs7Padding ? new Pkcs7Padding() : null);
}
#endif
else if (mode == AesCipherMode.CTR)
{
// CTR not supported by the BCL, use an optimized implementation
_impl = new CtrImpl(key, iv);
}
else
{
_impl = new BclImpl(
key,
iv,
(System.Security.Cryptography.CipherMode)mode,
mode,
pkcs7Padding ? PaddingMode.PKCS7 : PaddingMode.None);
}
}
Expand Down
26 changes: 0 additions & 26 deletions src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipherMode.cs

This file was deleted.

109 changes: 109 additions & 0 deletions src/Renci.SshNet/Security/Cryptography/Ciphers/AesCtrCipher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using System;
using System.Buffers.Binary;
using System.Numerics;
using System.Security.Cryptography;

namespace Renci.SshNet.Security.Cryptography.Ciphers
{
internal sealed class AesCtrCipher : BlockCipher, IDisposable
{
private readonly Aes _aes;
private readonly ICryptoTransform _encryptor;

private ulong _ivUpper; // The upper 64 bits of the IV
private ulong _ivLower; // The lower 64 bits of the IV

public AesCtrCipher(byte[] key, byte[] iv)
: base(key, 16, mode: null, padding: null)
{
var aes = Aes.Create();
aes.Key = key;
aes.Mode = System.Security.Cryptography.CipherMode.ECB;
aes.Padding = PaddingMode.None;
_aes = aes;
_encryptor = aes.CreateEncryptor();

_ivLower = BinaryPrimitives.ReadUInt64BigEndian(iv.AsSpan(8));
_ivUpper = BinaryPrimitives.ReadUInt64BigEndian(iv);
}

public override byte[] Encrypt(byte[] input, int offset, int length)
{
return CTREncryptDecrypt(input, offset, length);
}

public override byte[] Decrypt(byte[] input, int offset, int length)
{
return CTREncryptDecrypt(input, offset, length);
}

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)}.");
}

private byte[] CTREncryptDecrypt(byte[] data, int offset, int length)
{
var count = length / BlockSize;
if (length % BlockSize != 0)
{
count++;
}

var buffer = new byte[count * BlockSize];
CTRCreateCounterArray(buffer);
_ = _encryptor.TransformBlock(buffer, 0, buffer.Length, buffer, 0);
ArrayXOR(buffer, data, offset, length);

// adjust output for non-blocksized lengths
if (buffer.Length > length)
{
Array.Resize(ref buffer, length);
}

return buffer;
}

// creates the Counter array filled with incrementing copies of IV
private void CTRCreateCounterArray(byte[] buffer)
{
for (var i = 0; i < buffer.Length; i += 16)
{
BinaryPrimitives.WriteUInt64BigEndian(buffer.AsSpan(i + 8), _ivLower);
BinaryPrimitives.WriteUInt64BigEndian(buffer.AsSpan(i), _ivUpper);

_ivLower += 1;
_ivUpper += (_ivLower == 0) ? 1UL : 0UL;
}
}

// XOR 2 arrays using Vector<byte>
private static void ArrayXOR(byte[] buffer, byte[] data, int offset, int length)
{
var i = 0;

var oneVectorFromEnd = length - Vector<byte>.Count;
for (; i <= oneVectorFromEnd; i += Vector<byte>.Count)
{
var v = new Vector<byte>(buffer, i) ^ new Vector<byte>(data, offset + i);
v.CopyTo(buffer, i);
}

for (; i < length; i++)
{
buffer[i] ^= data[offset + i];
}
}

public void Dispose()
{
_aes.Dispose();
_encryptor.Dispose();
}
}
}
Loading

0 comments on commit 166cd93

Please sign in to comment.