Skip to content

Commit 5c8f564

Browse files
author
Pedro Fonseca
committed
Restructure, move most of the feature code to AesCipher.cs
Fix padding for non-AES blockciphers Fix IV exception for non-AES blockciphers
1 parent b7d6ea8 commit 5c8f564

File tree

8 files changed

+315
-277
lines changed

8 files changed

+315
-277
lines changed

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

Lines changed: 36 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using csp = System.Security.Cryptography;
32
using Renci.SshNet.Security.Cryptography.Ciphers;
43

54
namespace Renci.SshNet.Security.Cryptography
@@ -62,22 +61,7 @@ protected BlockCipher(byte[] key, byte blockSize, CipherMode mode, CipherPadding
6261
_padding = padding;
6362

6463
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
7864
_mode.Init(this);
79-
#endif
80-
}
8165
}
8266

8367
/// <summary>
@@ -89,37 +73,33 @@ protected BlockCipher(byte[] key, byte blockSize, CipherMode mode, CipherPadding
8973
/// <returns>Encrypted data</returns>
9074
public override byte[] Encrypt(byte[] data, int offset, int length)
9175
{
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+
9288
var output = new byte[length];
9389
var writtenBytes = 0;
9490

95-
#if FEATURE_AES_CSP
96-
if (_mode != null && _mode.isCspAvailable)
97-
writtenBytes = _mode.EncryptWithCSP(data, offset, output);
98-
else
91+
for (var i = 0; i < length / _blockSize; i++)
9992
{
100-
#endif
101-
if (length % _blockSize > 0)
93+
if (_mode == null)
10294
{
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;
95+
writtenBytes += EncryptBlock(data, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
11196
}
112-
113-
for (var i = 0; i < length / _blockSize; i++)
97+
else
11498
{
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);
99+
writtenBytes += _mode.EncryptBlock(data, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
119100
}
120-
#if FEATURE_AES_CSP
121101
}
122-
#endif
102+
123103
if (writtenBytes < length)
124104
{
125105
throw new InvalidOperationException("Encryption error.");
@@ -149,36 +129,32 @@ public override byte[] Decrypt(byte[] data)
149129
/// </returns>
150130
public override byte[] Decrypt(byte[] data, int offset, int length)
151131
{
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+
152143
var output = new byte[length];
153-
var writtenBytes = 0;
154144

155-
#if FEATURE_AES_CSP
156-
if (_mode != null && _mode.isCspAvailable)
157-
writtenBytes = _mode.DecryptWithCSP(data, offset, output);
158-
else
145+
var writtenBytes = 0;
146+
for (var i = 0; i < length / _blockSize; i++)
159147
{
160-
#endif
161-
if (length % _blockSize > 0)
148+
if (_mode == null)
162149
{
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;
150+
writtenBytes += DecryptBlock(data, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
170151
}
171-
172-
for (var i = 0; i < length / _blockSize; i++)
152+
else
173153
{
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);
154+
writtenBytes += _mode.DecryptBlock(data, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
178155
}
179-
#if FEATURE_AES_CSP
180156
}
181-
#endif
157+
182158
if (writtenBytes < length)
183159
{
184160
throw new InvalidOperationException("Encryption error.");

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

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Globalization;
33
using Renci.SshNet.Common;
4+
using csp = System.Security.Cryptography;
45

56
namespace Renci.SshNet.Security.Cryptography.Ciphers
67
{
@@ -23,6 +24,19 @@ public sealed class AesCipher : BlockCipher
2324

2425
private uint C0, C1, C2, C3;
2526

27+
#if FEATURE_AES_CSP
28+
29+
private csp.ICryptoTransform aesDecryptor;
30+
31+
private csp.ICryptoTransform aesEncryptor;
32+
33+
private bool useCSP; // set to false when CSP is not available for a given mode; falls back to legacy code
34+
35+
private bool isCTRMode;
36+
37+
private uint[] _ctrIV;
38+
#endif
39+
2640
#region Static Definition Tables
2741

2842
private static readonly byte[] S =
@@ -571,6 +585,11 @@ public AesCipher(byte[] key, CipherMode mode, CipherPadding padding)
571585

572586
if (!(keySize == 256 || keySize == 192 || keySize == 128))
573587
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "KeySize '{0}' is not valid for this algorithm.", keySize));
588+
589+
#if FEATURE_AES_CSP
590+
// initialize AesCryptoServiceProvider which uses AES-NI (faster, less CPU usage)
591+
useCSP = initCryptoServiceProvider(mode, padding);
592+
#endif
574593
}
575594

576595
/// <summary>
@@ -663,6 +682,204 @@ public override int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputC
663682
return BlockSize;
664683
}
665684

685+
#if FEATURE_AES_CSP
686+
687+
/// <summary>
688+
/// Encrypts the specified data using AesCryptoServiceProvider
689+
/// </summary>
690+
/// <param name="data">The data.</param>
691+
/// <param name="offset">The zero-based offset in <paramref name="data"/> at which to begin encrypting.</param>
692+
/// <param name="length">The number of bytes to encrypt from <paramref name="data"/>.</param>
693+
/// <returns>Encrypted data</returns>
694+
public override byte[] Encrypt(byte[] data, int offset, int length)
695+
{
696+
if (useCSP)
697+
{
698+
if (isCTRMode)
699+
return CTREncryptDecrypt(data, offset, length);
700+
else
701+
{
702+
if (length % BlockSize == 0)
703+
{
704+
byte[] output = new byte[length];
705+
aesEncryptor.TransformBlock(data, offset, length, output, 0);
706+
return output;
707+
}
708+
else
709+
{
710+
// adds padding
711+
byte[] output = aesEncryptor.TransformFinalBlock(data, offset, length);
712+
return output;
713+
}
714+
}
715+
}
716+
else
717+
return base.Encrypt(data, offset, length);
718+
}
719+
720+
/// <summary>
721+
/// Decrypts the specified input using AesCryptoServiceProvider
722+
/// </summary>
723+
/// <param name="data">The input.</param>
724+
/// <param name="offset">The zero-based offset in <paramref name="data"/> at which to begin decrypting.</param>
725+
/// <param name="length">The number of bytes to decrypt from <paramref name="data"/>.</param>
726+
/// <returns>
727+
/// The decrypted data.
728+
/// </returns>
729+
public override byte[] Decrypt(byte[] data, int offset, int length)
730+
{
731+
if (useCSP)
732+
{
733+
if (isCTRMode)
734+
return CTREncryptDecrypt(data, offset, length);
735+
else
736+
{
737+
if (length % BlockSize == 0)
738+
{
739+
byte[] output = new byte[length];
740+
aesDecryptor.TransformBlock(data, offset, length, output, 0);
741+
return output;
742+
}
743+
else
744+
{
745+
// handles padding
746+
byte[] output = aesDecryptor.TransformFinalBlock(data, offset, length);
747+
return output;
748+
}
749+
750+
751+
//byte[] ok = base.Decrypt(data, offset, length);
752+
//for (int i = 0; i < a1.Length; i++)
753+
// if (a1[i] != ok[i] || a1.Length != ok.Length)
754+
// return null;
755+
756+
//for (int i = 0; i < a1.Length; i++)
757+
// if (a2[i] != ok[i] || a1.Length != ok.Length)
758+
// return null;
759+
760+
//return a1;
761+
}
762+
}
763+
else
764+
return base.Encrypt(data, offset, length);
765+
}
766+
767+
// initialize AesCryptoServiceProvider
768+
private bool initCryptoServiceProvider(CipherMode mode, CipherPadding padding)
769+
{
770+
try
771+
{
772+
csp.PaddingMode cspPadding = padding == null ? csp.PaddingMode.None : csp.PaddingMode.PKCS7; // PKCS5 is same as PKCS7
773+
csp.CipherMode cspMode = 0;
774+
isCTRMode = mode is Modes.CtrCipherMode;
775+
776+
if (mode is Modes.CbcCipherMode)
777+
cspMode = csp.CipherMode.CBC;
778+
else if (isCTRMode)
779+
cspMode = csp.CipherMode.ECB; // CTR uses ECB
780+
else
781+
return false; // OFB and CFB not supported, fallback to managed code
782+
783+
// prepare IV array for CTR mode
784+
if (isCTRMode)
785+
_ctrIV = GetPackedIV(mode.IV);
786+
787+
// create ICryptoTransform instances
788+
var aesProvider = new csp.AesCryptoServiceProvider()
789+
{
790+
BlockSize = BlockSize * 8,
791+
KeySize = Key.Length * 8,
792+
Mode = cspMode,
793+
Padding = cspPadding,
794+
Key = Key,
795+
IV = mode.IV,
796+
};
797+
aesEncryptor = aesProvider.CreateEncryptor(Key, mode.IV);
798+
aesDecryptor = aesProvider.CreateDecryptor(Key, mode.IV);
799+
return true;
800+
}
801+
catch { } // fallback for unsupported key/iv/blocksize combinations
802+
return false;
803+
}
804+
805+
// convert the IV into an array of uint[4]
806+
private uint[] GetPackedIV(byte[] iv)
807+
{
808+
uint[] packedIV = new uint[4];
809+
packedIV[0] = (uint)((iv[0] << 24) | (iv[1] << 16) | (iv[2] << 8) | (iv[3]));
810+
packedIV[1] = (uint)((iv[4] << 24) | (iv[5] << 16) | (iv[6] << 8) | (iv[7]));
811+
packedIV[2] = (uint)((iv[8] << 24) | (iv[9] << 16) | (iv[10] << 8) | (iv[11]));
812+
packedIV[3] = (uint)((iv[12] << 24) | (iv[13] << 16) | (iv[14] << 8) | (iv[15]));
813+
return packedIV;
814+
}
815+
816+
// Perform AES-CTR encryption/decryption
817+
private byte[] CTREncryptDecrypt(byte[] data, int offset, int length)
818+
{
819+
int count = length / BlockSize;
820+
if (length % BlockSize != 0) count++;
821+
822+
byte[] counter = CTRCreateCounterArray(count);
823+
byte[] aesCounter = aesEncryptor.TransformFinalBlock(counter, 0, counter.Length);
824+
byte[] output = CTRArrayXOR(aesCounter, data, offset, length);
825+
826+
return output;
827+
}
828+
829+
// creates the Counter array filled with incrementing copies of IV
830+
private byte[] CTRCreateCounterArray(int blocks)
831+
{
832+
// fill an array with IV, increment by 1 for each copy
833+
uint[] counter = new uint[blocks * 4];
834+
for (int i = 0; i < counter.Length; i += 4)
835+
{
836+
// write IV to buffer (big endian)
837+
counter[i] = (_ctrIV[0] << 24) | ((_ctrIV[0] << 8) & 0x00FF0000) | ((_ctrIV[0] >> 8) & 0x0000FF00) | (_ctrIV[0] >> 24);
838+
counter[i + 1] = (_ctrIV[1] << 24) | ((_ctrIV[1] << 8) & 0x00FF0000) | ((_ctrIV[1] >> 8) & 0x0000FF00) | (_ctrIV[1] >> 24);
839+
counter[i + 2] = (_ctrIV[2] << 24) | ((_ctrIV[2] << 8) & 0x00FF0000) | ((_ctrIV[2] >> 8) & 0x0000FF00) | (_ctrIV[2] >> 24);
840+
counter[i + 3] = (_ctrIV[3] << 24) | ((_ctrIV[3] << 8) & 0x00FF0000) | ((_ctrIV[3] >> 8) & 0x0000FF00) | (_ctrIV[3] >> 24);
841+
842+
// increment IV (little endian)
843+
for (int j = 3; j >= 0 && ++_ctrIV[j] == 0; j--) ;
844+
}
845+
846+
// copy uint[] to byte[]
847+
byte[] counterBytes = new byte[blocks * 16];
848+
System.Buffer.BlockCopy(counter, 0, counterBytes, 0, counterBytes.Length);
849+
return counterBytes;
850+
}
851+
852+
// XORs the input data with the encrypted Counter array to produce the final output
853+
// uses uint arrays for speed
854+
private byte[] CTRArrayXOR(byte[] counter, byte[] data, int offset, int length)
855+
{
856+
int words = length / 4;
857+
if (length % 4 != 0) words++;
858+
859+
// convert original data to words
860+
uint[] datawords = new uint[words];
861+
System.Buffer.BlockCopy(data, offset, datawords, 0, length);
862+
863+
// convert encrypted IV counter to words
864+
uint[] counterwords = new uint[words];
865+
System.Buffer.BlockCopy(counter, 0, counterwords, 0, length);
866+
867+
// XOR encrypted Counter with input data
868+
for (int i = 0; i < words; i++)
869+
counterwords[i] = counterwords[i] ^ datawords[i];
870+
871+
// copy uint[] to byte[]
872+
byte[] output = counter;
873+
System.Buffer.BlockCopy(counterwords, 0, output, 0, length);
874+
875+
// adjust output for non-aligned lengths
876+
if (output.Length > length)
877+
Array.Resize(ref output, length);
878+
879+
return output;
880+
}
881+
#endif
882+
666883
private uint[] GenerateWorkingKey(bool isEncryption, byte[] key)
667884
{
668885
int KC = key.Length / 4; // key length in words

0 commit comments

Comments
 (0)