Skip to content

Commit 9622631

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 af5b858 commit 9622631

File tree

8 files changed

+252
-296
lines changed

8 files changed

+252
-296
lines changed

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

Lines changed: 35 additions & 62 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
@@ -61,20 +60,7 @@ protected BlockCipher(byte[] key, byte blockSize, CipherMode mode, CipherPadding
6160
_mode = mode;
6261
_padding = padding;
6362

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
7663
_mode?.Init(this);
77-
#endif
7864
}
7965

8066
/// <summary>
@@ -88,42 +74,34 @@ protected BlockCipher(byte[] key, byte blockSize, CipherMode mode, CipherPadding
8874
/// </returns>
8975
public override byte[] Encrypt(byte[] input, int offset, int length)
9076
{
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+
9190
var output = new byte[length];
9291
var writtenBytes = 0;
9392

94-
#if FEATURE_AES_CSP
95-
if (_mode is not null && _mode.isCspAvailable)
96-
writtenBytes = _mode.EncryptWithCSP(input, offset, output);
97-
else
93+
for (var i = 0; i < length / _blockSize; i++)
9894
{
99-
#endif
100-
if (length % _blockSize > 0)
95+
if (_mode is null)
10196
{
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;
97+
writtenBytes += EncryptBlock(input, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
11198
}
112-
113-
for (var i = 0; i < length / _blockSize; i++)
99+
else
114100
{
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-
}
101+
writtenBytes += _mode.EncryptBlock(input, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
123102
}
124-
#if FEATURE_AES_CSP
125103
}
126-
#endif
104+
127105
if (writtenBytes < length)
128106
{
129107
throw new InvalidOperationException("Encryption error.");
@@ -155,37 +133,32 @@ public override byte[] Decrypt(byte[] input)
155133
/// </returns>
156134
public override byte[] Decrypt(byte[] input, int offset, int length)
157135
{
158-
var output = new byte[length];
159-
var writtenBytes = 0;
160-
161-
#if FEATURE_AES_CSP
162-
if (_mode is not null && _mode.isCspAvailable)
163-
writtenBytes = _mode.DecryptWithCSP(input, offset, output);
164-
else
136+
if (length % _blockSize > 0)
165137
{
166-
#endif
167-
if (length % _blockSize > 0)
168-
{
169138
if (_padding is null)
170-
{
171-
throw new ArgumentException("data");
172-
}
139+
{
140+
throw new ArgumentException("data");
141+
}
173142

174143
input = _padding.Pad(_blockSize, input, offset, length);
175144
offset = 0;
176145
length = input.Length;
177-
}
146+
}
178147

179-
for (var i = 0; i < length / _blockSize; i++)
148+
var output = new byte[length];
149+
var writtenBytes = 0;
150+
for (var i = 0; i < length / _blockSize; i++)
151+
{
152+
if (_mode is null)
180153
{
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);
154+
writtenBytes += DecryptBlock(input, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
155+
}
156+
else
157+
{
158+
writtenBytes += _mode.DecryptBlock(input, offset + (i * _blockSize), _blockSize, output, i * _blockSize);
185159
}
186-
#if FEATURE_AES_CSP
187160
}
188-
#endif
161+
189162
if (writtenBytes < length)
190163
{
191164
throw new InvalidOperationException("Encryption error.");

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

Lines changed: 213 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System;
22
using System.Globalization;
3-
43
using Renci.SshNet.Common;
4+
using csp = System.Security.Cryptography;
55

66
namespace Renci.SshNet.Security.Cryptography.Ciphers
77
{
@@ -17,10 +17,15 @@ public sealed class AesCipher : BlockCipher
1717
private int _rounds;
1818
private uint[] _encryptionKey;
1919
private uint[] _decryptionKey;
20-
private uint _c0;
21-
private uint _c1;
22-
private uint _c2;
23-
private uint _c3;
20+
private uint C0, C1, C2, C3;
21+
22+
#if FEATURE_AES_CSP
23+
private csp.ICryptoTransform aesDecryptor;
24+
private csp.ICryptoTransform aesEncryptor;
25+
private bool useCSP; // set to false when CSP is not available for a given mode; falls back to legacy code
26+
private bool isCTRMode;
27+
private uint[] _ctrIV;
28+
#endif
2429

2530
#region Static Definition Tables
2631

@@ -572,6 +577,11 @@ public AesCipher(byte[] key, CipherMode mode, CipherPadding padding)
572577
{
573578
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "KeySize '{0}' is not valid for this algorithm.", keySize));
574579
}
580+
581+
#if FEATURE_AES_CSP
582+
// initialize AesCryptoServiceProvider which uses AES-NI (faster, less CPU usage)
583+
useCSP = initCryptoServiceProvider(mode, padding);
584+
#endif
575585
}
576586

577587
/// <summary>
@@ -666,6 +676,204 @@ public override int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputC
666676
return BlockSize;
667677
}
668678

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

0 commit comments

Comments
 (0)