Skip to content

Commit af5b858

Browse files
author
Pedro Fonseca
committed
Add FEATURE_AES_CSP to use hardware-accelerated AesCryptoServiceProvider
Reduces CPU usage dramatically, allowing more performance on slower machines
1 parent 508fc87 commit af5b858

File tree

8 files changed

+293
-42
lines changed

8 files changed

+293
-42
lines changed

src/Renci.SshNet/Renci.SshNet.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@
1818
</ItemGroup>
1919

2020
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netstandard2.1' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' ">
21-
<DefineConstants>FEATURE_SOCKET_TAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_EAP;FEATURE_DNS_SYNC;FEATURE_DNS_APM;FEATURE_DNS_TAP</DefineConstants>
21+
<DefineConstants>FEATURE_SOCKET_TAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_EAP;FEATURE_DNS_SYNC;FEATURE_DNS_APM;FEATURE_DNS_TAP;FEATURE_AES_CSP</DefineConstants>
2222
</PropertyGroup>
2323
</Project>

src/Renci.SshNet/Security/Cryptography/BlockCipher.cs

Lines changed: 64 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using csp = System.Security.Cryptography;
23
using Renci.SshNet.Security.Cryptography.Ciphers;
34

45
namespace Renci.SshNet.Security.Cryptography
@@ -60,7 +61,20 @@ protected BlockCipher(byte[] key, byte blockSize, CipherMode mode, CipherPadding
6061
_mode = mode;
6162
_padding = padding;
6263

64+
#if FEATURE_AES_CSP
65+
// use AesCryptoServiceProvider which uses AES-NI (faster, less CPU usage)
66+
csp.AesCryptoServiceProvider aesProvider = new csp.AesCryptoServiceProvider()
67+
{
68+
BlockSize = blockSize * 8,
69+
KeySize = Key.Length * 8,
70+
Mode = _mode.cspMode,
71+
Padding = padding == null ? csp.PaddingMode.None : csp.PaddingMode.PKCS7,
72+
Key = key
73+
};
74+
_mode?.Init(this, aesProvider);
75+
#else
6376
_mode?.Init(this);
77+
#endif
6478
}
6579

6680
/// <summary>
@@ -74,34 +88,42 @@ protected BlockCipher(byte[] key, byte blockSize, CipherMode mode, CipherPadding
7488
/// </returns>
7589
public override byte[] Encrypt(byte[] input, int offset, int length)
7690
{
77-
if (length % _blockSize > 0)
78-
{
79-
if (_padding is null)
80-
{
81-
throw new ArgumentException("data");
82-
}
83-
84-
var paddingLength = _blockSize - (length % _blockSize);
85-
input = _padding.Pad(input, offset, length, paddingLength);
86-
length += paddingLength;
87-
offset = 0;
88-
}
89-
9091
var output = new byte[length];
9192
var writtenBytes = 0;
9293

93-
for (var i = 0; i < length / _blockSize; i++)
94+
#if FEATURE_AES_CSP
95+
if (_mode is not null && _mode.isCspAvailable)
96+
writtenBytes = _mode.EncryptWithCSP(input, offset, output);
97+
else
9498
{
95-
if (_mode is null)
99+
#endif
100+
if (length % _blockSize > 0)
96101
{
97-
writtenBytes += EncryptBlock(input, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
102+
if (_padding is null)
103+
{
104+
throw new ArgumentException("data");
105+
}
106+
107+
var paddingLength = _blockSize - (length % _blockSize);
108+
input = _padding.Pad(input, offset, length, paddingLength);
109+
length += paddingLength;
110+
offset = 0;
98111
}
99-
else
112+
113+
for (var i = 0; i < length / _blockSize; i++)
100114
{
101-
writtenBytes += _mode.EncryptBlock(input, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
115+
if (_mode is null)
116+
{
117+
writtenBytes += EncryptBlock(input, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
118+
}
119+
else
120+
{
121+
writtenBytes += _mode.EncryptBlock(input, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
122+
}
102123
}
124+
#if FEATURE_AES_CSP
103125
}
104-
126+
#endif
105127
if (writtenBytes < length)
106128
{
107129
throw new InvalidOperationException("Encryption error.");
@@ -133,33 +155,37 @@ public override byte[] Decrypt(byte[] input)
133155
/// </returns>
134156
public override byte[] Decrypt(byte[] input, int offset, int length)
135157
{
136-
if (length % _blockSize > 0)
137-
{
138-
if (_padding is null)
139-
{
140-
throw new ArgumentException("data");
141-
}
142-
143-
input = _padding.Pad(_blockSize, input, offset, length);
144-
offset = 0;
145-
length = input.Length;
146-
}
147-
148158
var output = new byte[length];
149-
150159
var writtenBytes = 0;
151-
for (var i = 0; i < length / _blockSize; i++)
160+
161+
#if FEATURE_AES_CSP
162+
if (_mode is not null && _mode.isCspAvailable)
163+
writtenBytes = _mode.DecryptWithCSP(input, offset, output);
164+
else
152165
{
153-
if (_mode is null)
166+
#endif
167+
if (length % _blockSize > 0)
154168
{
155-
writtenBytes += DecryptBlock(input, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
169+
if (_padding is null)
170+
{
171+
throw new ArgumentException("data");
172+
}
173+
174+
input = _padding.Pad(_blockSize, input, offset, length);
175+
offset = 0;
176+
length = input.Length;
156177
}
157-
else
178+
179+
for (var i = 0; i < length / _blockSize; i++)
158180
{
159-
writtenBytes += _mode.DecryptBlock(input, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
181+
if (_mode is null)
182+
writtenBytes += DecryptBlock(input, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
183+
else
184+
writtenBytes += _mode.DecryptBlock(input, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
160185
}
186+
#if FEATURE_AES_CSP
161187
}
162-
188+
#endif
163189
if (writtenBytes < length)
164190
{
165191
throw new InvalidOperationException("Encryption error.");

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

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using Renci.SshNet.Common;
1+
using System;
2+
using Renci.SshNet.Common;
3+
using csp = System.Security.Cryptography;
24

35
namespace Renci.SshNet.Security.Cryptography.Ciphers
46
{
@@ -17,7 +19,7 @@ public abstract class CipherMode
1719
/// <summary>
1820
/// Gets the IV vector.
1921
/// </summary>
20-
protected byte[] IV;
22+
internal byte[] IV;
2123

2224
/// <summary>
2325
/// Holds block size of the cipher.
@@ -32,6 +34,9 @@ public abstract class CipherMode
3234
/// <param name="iv">The iv.</param>
3335
protected CipherMode(byte[] iv)
3436
{
37+
if (iv.Length < 16)
38+
throw new ArgumentException("Invalid AES IV length");
39+
3540
IV = iv;
3641
}
3742

@@ -70,6 +75,75 @@ internal void Init(BlockCipher cipher)
7075
/// <returns>
7176
/// The number of bytes decrypted.
7277
/// </returns>
73-
public abstract int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset);
78+
public virtual int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
79+
{
80+
// By default, use the same EncryptBlock() function
81+
// Modes that require a different implementation (non-symmetric) can override this function (CBC/CFB)
82+
return EncryptBlock(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
83+
}
84+
85+
#if FEATURE_AES_CSP
86+
// CryptoServiceProvider acceleration using AES-NI if supported
87+
internal csp.ICryptoTransform aesDecryptor;
88+
internal csp.ICryptoTransform aesEncryptor;
89+
90+
// set to false when CSP is not available; falls back to legacy code
91+
internal bool isCspAvailable = true;
92+
93+
// corresponding CSP cipher mode
94+
internal csp.CipherMode cspMode = csp.CipherMode.ECB;
95+
96+
/// <summary>
97+
/// If true, performs decryption using aesEncryptor
98+
/// </summary>
99+
protected bool cspDecryptAsEncrypt = true;
100+
101+
/// <summary>
102+
/// Initializes the specified cipher mode using CryptoServiceProvider acceleration
103+
/// </summary>
104+
/// <param name="cipher">The cipher.</param>
105+
/// <param name="csp">The cryptoServiceProvider instance</param>
106+
internal void Init(BlockCipher cipher, csp.AesCryptoServiceProvider csp)
107+
{
108+
Init(cipher);
109+
try
110+
{
111+
aesDecryptor = csp.CreateDecryptor(csp.Key, IV);
112+
aesEncryptor = csp.CreateEncryptor(csp.Key, IV);
113+
}
114+
catch
115+
{
116+
// OFB/CFB might not be available on some versions of Windows - fallback to legacy code
117+
isCspAvailable = false;
118+
}
119+
}
120+
121+
/// <summary>
122+
/// Encrypts the specified region of the input byte array using AesCryptoServiceProvider
123+
/// </summary>
124+
/// <param name="data">The input data to encrypt.</param>
125+
/// <param name="offset">The offset into the input byte array from which to begin using data.</param>
126+
/// <param name="output">The output to which to write encrypted data.</param>
127+
/// <returns>The number of bytes encrypted</returns>
128+
public virtual int EncryptWithCSP(byte[] data, int offset, byte[] output)
129+
{
130+
return aesEncryptor.TransformBlock(data, offset, output.Length, output, 0);
131+
}
132+
133+
/// <summary>
134+
/// Decrypts the specified region of the input byte array using AesCryptoServiceProvider
135+
/// </summary>
136+
/// <param name="data">The input data to decrypt.</param>
137+
/// <param name="offset">The offset into the input byte array from which to begin using data.</param>
138+
/// <param name="output">The output to which to write decrypted data.</param>
139+
/// <returns>The number of bytes decrypted</returns>
140+
public virtual int DecryptWithCSP(byte[] data, int offset, byte[] output)
141+
{
142+
if (cspDecryptAsEncrypt)
143+
return EncryptWithCSP(data, offset, output);
144+
145+
return aesDecryptor.TransformBlock(data, offset, output.Length, output, 0);
146+
}
147+
#endif
74148
}
75149
}

src/Renci.SshNet/Security/Cryptography/Ciphers/Modes/CbcCipherMode.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ public class CbcCipherMode : CipherMode
1515
public CbcCipherMode(byte[] iv)
1616
: base(iv)
1717
{
18+
#if FEATURE_AES_CSP
19+
cspMode = System.Security.Cryptography.CipherMode.CBC;
20+
cspDecryptAsEncrypt = false;
21+
#endif
1822
}
1923

2024
/// <summary>

src/Renci.SshNet/Security/Cryptography/Ciphers/Modes/CfbCipherMode.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ public CfbCipherMode(byte[] iv)
1818
: base(iv)
1919
{
2020
_ivOutput = new byte[iv.Length];
21+
22+
#if FEATURE_AES_CSP
23+
cspMode = System.Security.Cryptography.CipherMode.CFB;
24+
// note: the legacy code also uses EncryptBlock() on the DecryptBlock() function
25+
cspDecryptAsEncrypt = true;
26+
#endif
2127
}
2228

2329
/// <summary>

src/Renci.SshNet/Security/Cryptography/Ciphers/Modes/CtrCipherMode.cs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ public class CtrCipherMode : CipherMode
1010
{
1111
private readonly byte[] _ivOutput;
1212

13+
#if FEATURE_AES_CSP
14+
// IV as uint[] for usage with CryptoServiceProvider
15+
private uint[] _ivCSP;
16+
#endif
17+
1318
/// <summary>
1419
/// Initializes a new instance of the <see cref="CtrCipherMode"/> class.
1520
/// </summary>
@@ -18,6 +23,19 @@ public CtrCipherMode(byte[] iv)
1823
: base(iv)
1924
{
2025
_ivOutput = new byte[iv.Length];
26+
27+
#if FEATURE_AES_CSP
28+
// CTR uses ECB plus an incrementing IV
29+
cspMode = System.Security.Cryptography.CipherMode.ECB;
30+
cspDecryptAsEncrypt = true;
31+
32+
// convert the IV into an array of uint[4] for speed
33+
_ivCSP = new uint[4];
34+
_ivCSP[0] = (uint)((iv[0] << 24) | (iv[1] << 16) | (iv[2] << 8) | (iv[3]));
35+
_ivCSP[1] = (uint)((iv[4] << 24) | (iv[5] << 16) | (iv[6] << 8) | (iv[7]));
36+
_ivCSP[2] = (uint)((iv[8] << 24) | (iv[9] << 16) | (iv[10] << 8) | (iv[11]));
37+
_ivCSP[3] = (uint)((iv[12] << 24) | (iv[13] << 16) | (iv[14] << 8) | (iv[15]));
38+
#endif
2139
}
2240

2341
/// <summary>
@@ -64,6 +82,67 @@ public override int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputC
6482
return _blockSize;
6583
}
6684

85+
#if FEATURE_AES_CSP
86+
/// <summary>
87+
/// Encrypts the specified region of the input byte array using AesCryptoServiceProvider
88+
/// </summary>
89+
/// <param name="data">The input data to encrypt.</param>
90+
/// <param name="offset">The offset into the input byte array from which to begin using data.</param>
91+
/// <param name="output">The output to which to write encrypted data.</param>
92+
/// <returns>The number of bytes encrypted</returns>
93+
public override int EncryptWithCSP(byte[] data, int offset, byte[] output)
94+
{
95+
byte[] counter = CreateIVCounter(data, offset, output.Length);
96+
int bytes = aesEncryptor.TransformBlock(counter, 0, output.Length, output, 0);
97+
DataXorCounter(data, offset, output);
98+
return bytes;
99+
}
100+
101+
// creates the Counter array filled with incrementing copies of IV
102+
private byte[] CreateIVCounter(byte[] inputBuffer, int offset, int length)
103+
{
104+
// fill an array with IV, increment by 1 for each copy
105+
uint[] counter = new uint[length / 4];
106+
for (int i = 0; i < counter.Length; i += 4)
107+
{
108+
// write IV to buffer (big endian)
109+
counter[i] = (_ivCSP[0] << 24) | ((_ivCSP[0] << 8) & 0x00FF0000) | ((_ivCSP[0] >> 8) & 0x0000FF00) | (_ivCSP[0] >> 24);
110+
counter[i + 1] = (_ivCSP[1] << 24) | ((_ivCSP[1] << 8) & 0x00FF0000) | ((_ivCSP[1] >> 8) & 0x0000FF00) | (_ivCSP[1] >> 24);
111+
counter[i + 2] = (_ivCSP[2] << 24) | ((_ivCSP[2] << 8) & 0x00FF0000) | ((_ivCSP[2] >> 8) & 0x0000FF00) | (_ivCSP[2] >> 24);
112+
counter[i + 3] = (_ivCSP[3] << 24) | ((_ivCSP[3] << 8) & 0x00FF0000) | ((_ivCSP[3] >> 8) & 0x0000FF00) | (_ivCSP[3] >> 24);
113+
114+
// increment IV (little endian)
115+
for (int j = 3; j >= 0 && ++_ivCSP[j] == 0; j--) ;
116+
}
117+
118+
// copy uint[] to byte[]
119+
byte[] counterBytes = new byte[length];
120+
System.Buffer.BlockCopy(counter, 0, counterBytes, 0, length);
121+
return counterBytes;
122+
}
123+
124+
// XORs the input data with the encrypted Counter array to produce the final output
125+
private void DataXorCounter(byte[] data, int offset, byte[] output)
126+
{
127+
int length = output.Length;
128+
129+
// original data
130+
uint[] inwords = new uint[length / 4];
131+
System.Buffer.BlockCopy(data, offset, inwords, 0, length);
132+
133+
// encrypted IV counter data
134+
uint[] outwords = new uint[length / 4];
135+
System.Buffer.BlockCopy(output, 0, outwords, 0, length);
136+
137+
// XOR encrypted Counter with input data (use uint arrays for speed)
138+
for (int i = 0; i < outwords.Length; i++)
139+
outwords[i] = outwords[i] ^ inwords[i];
140+
141+
// copy final data to output byte[]
142+
System.Buffer.BlockCopy(outwords, 0, output, 0, length);
143+
}
144+
#endif
145+
67146
/// <summary>
68147
/// Decrypts the specified region of the input byte array and copies the decrypted data to the specified region of the output byte array.
69148
/// </summary>

0 commit comments

Comments
 (0)