Skip to content

Commit e02f749

Browse files
committed
Use BCL CipherMode enum for AesCipher class just like TripleDesCipher class; Create a dedicated AesCtrCipher class just like AesGcmCipher class.
1 parent 14c652c commit e02f749

File tree

11 files changed

+290
-305
lines changed

11 files changed

+290
-305
lines changed

src/Renci.SshNet/ConnectionInfo.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -364,15 +364,15 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy
364364

365365
Encryptions = new Dictionary<string, CipherInfo>
366366
{
367-
{ "aes128-ctr", new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)) },
368-
{ "aes192-ctr", new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)) },
369-
{ "aes256-ctr", new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)) },
367+
{ "aes128-ctr", new CipherInfo(128, (key, iv) => new AesCtrCipher(key, iv)) },
368+
{ "aes192-ctr", new CipherInfo(192, (key, iv) => new AesCtrCipher(key, iv)) },
369+
{ "aes256-ctr", new CipherInfo(256, (key, iv) => new AesCtrCipher(key, iv)) },
370370
{ "[email protected]", new CipherInfo(128, (key, iv) => new AesGcmCipher(key, iv, aadLength: 4), isAead: true) },
371371
{ "[email protected]", new CipherInfo(256, (key, iv) => new AesGcmCipher(key, iv, aadLength: 4), isAead: true) },
372372
{ "[email protected]", new CipherInfo(512, (key, iv) => new ChaCha20Poly1305Cipher(key, aadLength: 4), isAead: true) },
373-
{ "aes128-cbc", new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)) },
374-
{ "aes192-cbc", new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)) },
375-
{ "aes256-cbc", new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)) },
373+
{ "aes128-cbc", new CipherInfo(128, (key, iv) => new AesCipher(key, iv, CipherMode.CBC, pkcs7Padding: false)) },
374+
{ "aes192-cbc", new CipherInfo(192, (key, iv) => new AesCipher(key, iv, CipherMode.CBC, pkcs7Padding: false)) },
375+
{ "aes256-cbc", new CipherInfo(256, (key, iv) => new AesCipher(key, iv, CipherMode.CBC, pkcs7Padding: false)) },
376376
{ "3des-cbc", new CipherInfo(192, (key, iv) => new TripleDesCipher(key, iv, CipherMode.CBC, pkcs7Padding: false)) },
377377
};
378378

src/Renci.SshNet/PrivateKeyFile.OpenSSH.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -95,22 +95,22 @@ public Key Parse()
9595
cipherInfo = new CipherInfo(192, (key, iv) => new TripleDesCipher(key, iv, CipherMode.CBC, pkcs7Padding: false));
9696
break;
9797
case "aes128-cbc":
98-
cipherInfo = new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false));
98+
cipherInfo = new CipherInfo(128, (key, iv) => new AesCipher(key, iv, CipherMode.CBC, pkcs7Padding: false));
9999
break;
100100
case "aes192-cbc":
101-
cipherInfo = new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false));
101+
cipherInfo = new CipherInfo(192, (key, iv) => new AesCipher(key, iv, CipherMode.CBC, pkcs7Padding: false));
102102
break;
103103
case "aes256-cbc":
104-
cipherInfo = new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false));
104+
cipherInfo = new CipherInfo(256, (key, iv) => new AesCipher(key, iv, CipherMode.CBC, pkcs7Padding: false));
105105
break;
106106
case "aes128-ctr":
107-
cipherInfo = new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false));
107+
cipherInfo = new CipherInfo(128, (key, iv) => new AesCtrCipher(key, iv));
108108
break;
109109
case "aes192-ctr":
110-
cipherInfo = new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false));
110+
cipherInfo = new CipherInfo(192, (key, iv) => new AesCtrCipher(key, iv));
111111
break;
112112
case "aes256-ctr":
113-
cipherInfo = new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false));
113+
cipherInfo = new CipherInfo(256, (key, iv) => new AesCtrCipher(key, iv));
114114
break;
115115
116116
cipherInfo = new CipherInfo(128, (key, iv) => new AesGcmCipher(key, iv, aadLength: 0), isAead: true);

src/Renci.SshNet/PrivateKeyFile.PKCS1.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ public Key Parse()
5757
cipher = new CipherInfo(192, (key, iv) => new TripleDesCipher(key, iv, CipherMode.CFB, pkcs7Padding: false));
5858
break;
5959
case "AES-128-CBC":
60-
cipher = new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: true));
60+
cipher = new CipherInfo(128, (key, iv) => new AesCipher(key, iv, CipherMode.CBC, pkcs7Padding: true));
6161
break;
6262
case "AES-192-CBC":
63-
cipher = new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: true));
63+
cipher = new CipherInfo(192, (key, iv) => new AesCipher(key, iv, CipherMode.CBC, pkcs7Padding: true));
6464
break;
6565
case "AES-256-CBC":
66-
cipher = new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: true));
66+
cipher = new CipherInfo(256, (key, iv) => new AesCipher(key, iv, CipherMode.CBC, pkcs7Padding: true));
6767
break;
6868
default:
6969
throw new SshException(string.Format(CultureInfo.InvariantCulture, "Private key cipher \"{0}\" is not supported.", _cipherName));

src/Renci.SshNet/PrivateKeyFile.PuTTY.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
using Renci.SshNet.Security;
1515
using Renci.SshNet.Security.Cryptography.Ciphers;
1616

17+
using CipherMode = System.Security.Cryptography.CipherMode;
18+
1719
namespace Renci.SshNet
1820
{
1921
public partial class PrivateKeyFile
@@ -111,7 +113,7 @@ public Key Parse()
111113
throw new SshException("PuTTY key file version " + _version + " is not supported");
112114
}
113115

114-
using (var cipher = new AesCipher(cipherKey, cipherIV, AesCipherMode.CBC, pkcs7Padding: false))
116+
using (var cipher = new AesCipher(cipherKey, cipherIV, CipherMode.CBC, pkcs7Padding: false))
115117
{
116118
privateKey = cipher.Decrypt(_data);
117119
}

src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.CtrImpl.cs

-115
This file was deleted.

src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.cs

+4-9
Original file line numberDiff line numberDiff line change
@@ -23,32 +23,27 @@ public sealed partial class AesCipher : BlockCipher, IDisposable
2323
/// <param name="pkcs7Padding">Enable PKCS7 padding.</param>
2424
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
2525
/// <exception cref="ArgumentException">Keysize is not valid for this algorithm.</exception>
26-
public AesCipher(byte[] key, byte[] iv, AesCipherMode mode, bool pkcs7Padding = false)
26+
public AesCipher(byte[] key, byte[] iv, System.Security.Cryptography.CipherMode mode, bool pkcs7Padding = false)
2727
: base(key, 16, mode: null, padding: null)
2828
{
29-
if (mode == AesCipherMode.OFB)
29+
if (mode == System.Security.Cryptography.CipherMode.OFB)
3030
{
3131
// OFB is not supported on modern .NET
3232
_impl = new BlockImpl(key, new OfbCipherMode(iv), pkcs7Padding ? new Pkcs7Padding() : null);
3333
}
3434
#if !NET6_0_OR_GREATER
35-
else if (mode == AesCipherMode.CFB)
35+
else if (mode == System.Security.Cryptography.CipherMode.CFB)
3636
{
3737
// CFB not supported on NetStandard 2.1
3838
_impl = new BlockImpl(key, new CfbCipherMode(iv), pkcs7Padding ? new Pkcs7Padding() : null);
3939
}
4040
#endif
41-
else if (mode == AesCipherMode.CTR)
42-
{
43-
// CTR not supported by the BCL, use an optimized implementation
44-
_impl = new CtrImpl(key, iv);
45-
}
4641
else
4742
{
4843
_impl = new BclImpl(
4944
key,
5045
iv,
51-
(System.Security.Cryptography.CipherMode)mode,
46+
mode,
5247
pkcs7Padding ? PaddingMode.PKCS7 : PaddingMode.None);
5348
}
5449
}

src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipherMode.cs

-26
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
using System;
2+
using System.Buffers.Binary;
3+
using System.Numerics;
4+
using System.Security.Cryptography;
5+
6+
namespace Renci.SshNet.Security.Cryptography.Ciphers
7+
{
8+
internal sealed class AesCtrCipher : BlockCipher, IDisposable
9+
{
10+
private readonly Aes _aes;
11+
private readonly ICryptoTransform _encryptor;
12+
13+
private ulong _ivUpper; // The upper 64 bits of the IV
14+
private ulong _ivLower; // The lower 64 bits of the IV
15+
16+
public AesCtrCipher(byte[] key, byte[] iv)
17+
: base(key, 16, mode: null, padding: null)
18+
{
19+
var aes = Aes.Create();
20+
aes.Key = key;
21+
aes.Mode = System.Security.Cryptography.CipherMode.ECB;
22+
aes.Padding = PaddingMode.None;
23+
_aes = aes;
24+
_encryptor = aes.CreateEncryptor();
25+
26+
_ivLower = BinaryPrimitives.ReadUInt64BigEndian(iv.AsSpan(8));
27+
_ivUpper = BinaryPrimitives.ReadUInt64BigEndian(iv);
28+
}
29+
30+
public override byte[] Encrypt(byte[] input, int offset, int length)
31+
{
32+
return CTREncryptDecrypt(input, offset, length);
33+
}
34+
35+
public override byte[] Decrypt(byte[] input, int offset, int length)
36+
{
37+
return CTREncryptDecrypt(input, offset, length);
38+
}
39+
40+
public override int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
41+
{
42+
throw new NotImplementedException($"Invalid usage of {nameof(EncryptBlock)}.");
43+
}
44+
45+
public override int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
46+
{
47+
throw new NotImplementedException($"Invalid usage of {nameof(DecryptBlock)}.");
48+
}
49+
50+
private byte[] CTREncryptDecrypt(byte[] data, int offset, int length)
51+
{
52+
var count = length / BlockSize;
53+
if (length % BlockSize != 0)
54+
{
55+
count++;
56+
}
57+
58+
var buffer = new byte[count * BlockSize];
59+
CTRCreateCounterArray(buffer);
60+
_ = _encryptor.TransformBlock(buffer, 0, buffer.Length, buffer, 0);
61+
ArrayXOR(buffer, data, offset, length);
62+
63+
// adjust output for non-blocksized lengths
64+
if (buffer.Length > length)
65+
{
66+
Array.Resize(ref buffer, length);
67+
}
68+
69+
return buffer;
70+
}
71+
72+
// creates the Counter array filled with incrementing copies of IV
73+
private void CTRCreateCounterArray(byte[] buffer)
74+
{
75+
for (var i = 0; i < buffer.Length; i += 16)
76+
{
77+
BinaryPrimitives.WriteUInt64BigEndian(buffer.AsSpan(i + 8), _ivLower);
78+
BinaryPrimitives.WriteUInt64BigEndian(buffer.AsSpan(i), _ivUpper);
79+
80+
_ivLower += 1;
81+
_ivUpper += (_ivLower == 0) ? 1UL : 0UL;
82+
}
83+
}
84+
85+
// XOR 2 arrays using Vector<byte>
86+
private static void ArrayXOR(byte[] buffer, byte[] data, int offset, int length)
87+
{
88+
var i = 0;
89+
90+
var oneVectorFromEnd = length - Vector<byte>.Count;
91+
for (; i <= oneVectorFromEnd; i += Vector<byte>.Count)
92+
{
93+
var v = new Vector<byte>(buffer, i) ^ new Vector<byte>(data, offset + i);
94+
v.CopyTo(buffer, i);
95+
}
96+
97+
for (; i < length; i++)
98+
{
99+
buffer[i] ^= data[offset + i];
100+
}
101+
}
102+
103+
public void Dispose()
104+
{
105+
_aes.Dispose();
106+
_encryptor.Dispose();
107+
}
108+
}
109+
}

0 commit comments

Comments
 (0)