Skip to content

Commit b7d6ea8

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 f072c5f commit b7d6ea8

File tree

8 files changed

+279
-100
lines changed

8 files changed

+279
-100
lines changed

src/Renci.SshNet/Renci.SshNet.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@
3636
<DefineConstants>FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_POLL;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII;FEATURE_ECDSA</DefineConstants>
3737
</PropertyGroup>
3838
<PropertyGroup Condition=" '$(TargetFramework)' == 'net40' ">
39-
<DefineConstants>FEATURE_STRINGBUILDER_CLEAR;FEATURE_HASHALGORITHM_DISPOSE;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_SOCKET_DISPOSE;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_WAITHANDLE_DISPOSE;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII;FEATURE_ECDSA</DefineConstants>
39+
<DefineConstants>FEATURE_STRINGBUILDER_CLEAR;FEATURE_HASHALGORITHM_DISPOSE;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_SOCKET_DISPOSE;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_WAITHANDLE_DISPOSE;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII;FEATURE_ECDSA;FEATURE_AES_CSP</DefineConstants>
4040
</PropertyGroup>
4141
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
4242
<DefineConstants>FEATURE_STRINGBUILDER_CLEAR;FEATURE_HASHALGORITHM_DISPOSE;FEATURE_ENCODING_ASCII;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_DIRECTORYINFO_ENUMERATEFILES;FEATURE_MEMORYSTREAM_TRYGETBUFFER;FEATURE_REFLECTION_TYPEINFO;FEATURE_RNG_CREATE;FEATURE_SOCKET_TAP;FEATURE_SOCKET_EAP;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_SOCKET_DISPOSE;FEATURE_DNS_TAP;FEATURE_STREAM_TAP;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_TAP;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_WAITHANDLE_DISPOSE;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512</DefineConstants>
4343
</PropertyGroup>
4444
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netstandard2.1' ">
45-
<DefineConstants>FEATURE_STRINGBUILDER_CLEAR;FEATURE_HASHALGORITHM_DISPOSE;FEATURE_ENCODING_ASCII;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_DIRECTORYINFO_ENUMERATEFILES;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_MEMORYSTREAM_TRYGETBUFFER;FEATURE_RNG_CREATE;FEATURE_SOCKET_TAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_EAP;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_SOCKET_DISPOSE;FEATURE_DNS_SYNC;FEATURE_DNS_APM;FEATURE_DNS_TAP;FEATURE_STREAM_APM;FEATURE_STREAM_TAP;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_TAP;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_WAITHANDLE_DISPOSE;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_ECDSA</DefineConstants>
45+
<DefineConstants>FEATURE_STRINGBUILDER_CLEAR;FEATURE_HASHALGORITHM_DISPOSE;FEATURE_ENCODING_ASCII;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_DIRECTORYINFO_ENUMERATEFILES;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_MEMORYSTREAM_TRYGETBUFFER;FEATURE_RNG_CREATE;FEATURE_SOCKET_TAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_EAP;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_SOCKET_DISPOSE;FEATURE_DNS_SYNC;FEATURE_DNS_APM;FEATURE_DNS_TAP;FEATURE_STREAM_APM;FEATURE_STREAM_TAP;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_TAP;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_WAITHANDLE_DISPOSE;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_ECDSA;FEATURE_AES_CSP</DefineConstants>
4646
</PropertyGroup>
4747
</Project>

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

Lines changed: 60 additions & 36 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
@@ -61,7 +62,22 @@ protected BlockCipher(byte[] key, byte blockSize, CipherMode mode, CipherPadding
6162
_padding = padding;
6263

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

6783
/// <summary>
@@ -73,33 +89,37 @@ protected BlockCipher(byte[] key, byte blockSize, CipherMode mode, CipherPadding
7389
/// <returns>Encrypted data</returns>
7490
public override byte[] Encrypt(byte[] data, int offset, int length)
7591
{
76-
if (length % _blockSize > 0)
77-
{
78-
if (_padding == null)
79-
{
80-
throw new ArgumentException("data");
81-
}
82-
var paddingLength = _blockSize - (length % _blockSize);
83-
data = _padding.Pad(data, offset, length, paddingLength);
84-
length += paddingLength;
85-
offset = 0;
86-
}
87-
8892
var output = new byte[length];
8993
var writtenBytes = 0;
9094

91-
for (var i = 0; i < length / _blockSize; i++)
95+
#if FEATURE_AES_CSP
96+
if (_mode != null && _mode.isCspAvailable)
97+
writtenBytes = _mode.EncryptWithCSP(data, offset, output);
98+
else
9299
{
93-
if (_mode == null)
100+
#endif
101+
if (length % _blockSize > 0)
94102
{
95-
writtenBytes += EncryptBlock(data, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
103+
if (_padding == null)
104+
{
105+
throw new ArgumentException("data");
106+
}
107+
var paddingLength = _blockSize - (length % _blockSize);
108+
data = _padding.Pad(data, offset, length, paddingLength);
109+
length += paddingLength;
110+
offset = 0;
96111
}
97-
else
112+
113+
for (var i = 0; i < length / _blockSize; i++)
98114
{
99-
writtenBytes += _mode.EncryptBlock(data, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
115+
if (_mode == null)
116+
writtenBytes += EncryptBlock(data, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
117+
else
118+
writtenBytes += _mode.EncryptBlock(data, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
100119
}
120+
#if FEATURE_AES_CSP
101121
}
102-
122+
#endif
103123
if (writtenBytes < length)
104124
{
105125
throw new InvalidOperationException("Encryption error.");
@@ -129,32 +149,36 @@ public override byte[] Decrypt(byte[] data)
129149
/// </returns>
130150
public override byte[] Decrypt(byte[] data, int offset, int length)
131151
{
132-
if (length % _blockSize > 0)
133-
{
134-
if (_padding == null)
135-
{
136-
throw new ArgumentException("data");
137-
}
138-
data = _padding.Pad(_blockSize, data, offset, length);
139-
offset = 0;
140-
length = data.Length;
141-
}
142-
143152
var output = new byte[length];
144-
145153
var writtenBytes = 0;
146-
for (var i = 0; i < length / _blockSize; i++)
154+
155+
#if FEATURE_AES_CSP
156+
if (_mode != null && _mode.isCspAvailable)
157+
writtenBytes = _mode.DecryptWithCSP(data, offset, output);
158+
else
147159
{
148-
if (_mode == null)
160+
#endif
161+
if (length % _blockSize > 0)
149162
{
150-
writtenBytes += DecryptBlock(data, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
163+
if (_padding == null)
164+
{
165+
throw new ArgumentException("data");
166+
}
167+
data = _padding.Pad(_blockSize, data, offset, length);
168+
offset = 0;
169+
length = data.Length;
151170
}
152-
else
171+
172+
for (var i = 0; i < length / _blockSize; i++)
153173
{
154-
writtenBytes += _mode.DecryptBlock(data, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
174+
if (_mode == null)
175+
writtenBytes += DecryptBlock(data, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
176+
else
177+
writtenBytes += _mode.DecryptBlock(data, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
155178
}
179+
#if FEATURE_AES_CSP
156180
}
157-
181+
#endif
158182
if (writtenBytes < length)
159183
{
160184
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
{
@@ -15,7 +17,7 @@ public abstract class CipherMode
1517
/// <summary>
1618
/// Gets the IV vector.
1719
/// </summary>
18-
protected byte[] IV;
20+
internal byte[] IV;
1921

2022
/// <summary>
2123
/// Holds block size of the cipher.
@@ -28,6 +30,9 @@ public abstract class CipherMode
2830
/// <param name="iv">The iv.</param>
2931
protected CipherMode(byte[] iv)
3032
{
33+
if (iv.Length < 16)
34+
throw new ArgumentException("Invalid AES IV length");
35+
3136
IV = iv;
3237
}
3338

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

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>

0 commit comments

Comments
 (0)