| | | 1 | | using System; |
| | | 2 | | #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER |
| | | 3 | | using System.Buffers.Binary; |
| | | 4 | | using System.Numerics; |
| | | 5 | | #endif |
| | | 6 | | using System.Security.Cryptography; |
| | | 7 | | |
| | | 8 | | namespace Renci.SshNet.Security.Cryptography.Ciphers |
| | | 9 | | { |
| | | 10 | | public partial class AesCipher |
| | | 11 | | { |
| | | 12 | | private sealed class CtrImpl : BlockCipher, IDisposable |
| | | 13 | | { |
| | | 14 | | private readonly Aes _aes; |
| | | 15 | | |
| | | 16 | | private readonly ICryptoTransform _encryptor; |
| | | 17 | | |
| | | 18 | | #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER |
| | | 19 | | private ulong _ivUpper; // The upper 64 bits of the IV |
| | | 20 | | private ulong _ivLower; // The lower 64 bits of the IV |
| | | 21 | | #else |
| | | 22 | | // The same on netfx |
| | | 23 | | private readonly uint[] _packedIV; |
| | | 24 | | #endif |
| | | 25 | | |
| | | 26 | | public CtrImpl( |
| | | 27 | | byte[] key, |
| | | 28 | | byte[] iv) |
| | 2465 | 29 | | : base(key, 16, mode: null, padding: null) |
| | 2465 | 30 | | { |
| | 2465 | 31 | | var aes = Aes.Create(); |
| | 2465 | 32 | | aes.Key = key; |
| | 2465 | 33 | | aes.Mode = System.Security.Cryptography.CipherMode.ECB; |
| | 2465 | 34 | | aes.Padding = PaddingMode.None; |
| | 2465 | 35 | | _aes = aes; |
| | 2465 | 36 | | _encryptor = aes.CreateEncryptor(); |
| | | 37 | | |
| | | 38 | | #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER |
| | 2435 | 39 | | _ivLower = BinaryPrimitives.ReadUInt64BigEndian(iv.AsSpan(8)); |
| | 2435 | 40 | | _ivUpper = BinaryPrimitives.ReadUInt64BigEndian(iv); |
| | | 41 | | #else |
| | 30 | 42 | | _packedIV = GetPackedIV(iv); |
| | | 43 | | #endif |
| | 2465 | 44 | | } |
| | | 45 | | |
| | | 46 | | public override byte[] Encrypt(byte[] input, int offset, int length) |
| | 48498 | 47 | | { |
| | 48498 | 48 | | return CTREncryptDecrypt(input, offset, length); |
| | 48498 | 49 | | } |
| | | 50 | | |
| | | 51 | | public override byte[] Decrypt(byte[] input, int offset, int length) |
| | 99529 | 52 | | { |
| | 99529 | 53 | | return CTREncryptDecrypt(input, offset, length); |
| | 99529 | 54 | | } |
| | | 55 | | |
| | | 56 | | public override int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, i |
| | 0 | 57 | | { |
| | 0 | 58 | | throw new NotImplementedException($"Invalid usage of {nameof(DecryptBlock)}."); |
| | | 59 | | } |
| | | 60 | | |
| | | 61 | | public override int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, i |
| | 0 | 62 | | { |
| | 0 | 63 | | throw new NotImplementedException($"Invalid usage of {nameof(EncryptBlock)}."); |
| | | 64 | | } |
| | | 65 | | |
| | | 66 | | private byte[] CTREncryptDecrypt(byte[] data, int offset, int length) |
| | 148027 | 67 | | { |
| | 148027 | 68 | | var count = length / BlockSize; |
| | 148027 | 69 | | if (length % BlockSize != 0) |
| | 0 | 70 | | { |
| | 0 | 71 | | count++; |
| | 0 | 72 | | } |
| | | 73 | | |
| | 148027 | 74 | | var buffer = new byte[count * BlockSize]; |
| | 148027 | 75 | | CTRCreateCounterArray(buffer); |
| | 148027 | 76 | | _ = _encryptor.TransformBlock(buffer, 0, buffer.Length, buffer, 0); |
| | 148027 | 77 | | ArrayXOR(buffer, data, offset, length); |
| | | 78 | | |
| | | 79 | | // adjust output for non-blocksized lengths |
| | 148027 | 80 | | if (buffer.Length > length) |
| | 0 | 81 | | { |
| | 0 | 82 | | Array.Resize(ref buffer, length); |
| | 0 | 83 | | } |
| | | 84 | | |
| | 148027 | 85 | | return buffer; |
| | 148027 | 86 | | } |
| | | 87 | | |
| | | 88 | | #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER |
| | | 89 | | |
| | | 90 | | // creates the Counter array filled with incrementing copies of IV |
| | | 91 | | private void CTRCreateCounterArray(byte[] buffer) |
| | 147995 | 92 | | { |
| | 46453302 | 93 | | for (var i = 0; i < buffer.Length; i += 16) |
| | 23078656 | 94 | | { |
| | 23078656 | 95 | | BinaryPrimitives.WriteUInt64BigEndian(buffer.AsSpan(i + 8), _ivLower); |
| | 23078656 | 96 | | BinaryPrimitives.WriteUInt64BigEndian(buffer.AsSpan(i), _ivUpper); |
| | | 97 | | |
| | 23078656 | 98 | | _ivLower += 1; |
| | 23078656 | 99 | | _ivUpper += (_ivLower == 0) ? 1UL : 0UL; |
| | 23078656 | 100 | | } |
| | 147995 | 101 | | } |
| | | 102 | | |
| | | 103 | | // XOR 2 arrays using Vector<byte> |
| | | 104 | | private static void ArrayXOR(byte[] buffer, byte[] data, int offset, int length) |
| | 147995 | 105 | | { |
| | 147995 | 106 | | var i = 0; |
| | | 107 | | |
| | 147995 | 108 | | var oneVectorFromEnd = length - Vector<byte>.Count; |
| | 23118317 | 109 | | for (; i <= oneVectorFromEnd; i += Vector<byte>.Count) |
| | 11485161 | 110 | | { |
| | 11485161 | 111 | | var v = new Vector<byte>(buffer, i) ^ new Vector<byte>(data, offset + i); |
| | 11485161 | 112 | | v.CopyTo(buffer, i); |
| | 11485161 | 113 | | } |
| | | 114 | | |
| | 3614683 | 115 | | for (; i < length; i++) |
| | 1733344 | 116 | | { |
| | 1733344 | 117 | | buffer[i] ^= data[offset + i]; |
| | 1733344 | 118 | | } |
| | 147995 | 119 | | } |
| | | 120 | | |
| | | 121 | | #else |
| | | 122 | | // creates the Counter array filled with incrementing copies of IV |
| | | 123 | | private void CTRCreateCounterArray(byte[] buffer) |
| | 32 | 124 | | { |
| | | 125 | | // fill array with IV, increment by 1 for each copy |
| | 32 | 126 | | var words = buffer.Length / 4; |
| | 32 | 127 | | var counter = new uint[words]; |
| | 432 | 128 | | for (var i = 0; i < words; i += 4) |
| | 184 | 129 | | { |
| | | 130 | | // write IV to buffer (big endian) |
| | 184 | 131 | | counter[i] = _packedIV[0]; |
| | 184 | 132 | | counter[i + 1] = _packedIV[1]; |
| | 184 | 133 | | counter[i + 2] = _packedIV[2]; |
| | 184 | 134 | | counter[i + 3] = _packedIV[3]; |
| | | 135 | | |
| | | 136 | | // increment IV (little endian) |
| | 184 | 137 | | if (_packedIV[3] < 0xFF000000u) |
| | 182 | 138 | | { |
| | 182 | 139 | | _packedIV[3] += 0x01000000u; |
| | 182 | 140 | | } |
| | | 141 | | else |
| | 2 | 142 | | { |
| | 2 | 143 | | var j = 3; |
| | | 144 | | do |
| | 8 | 145 | | { |
| | 8 | 146 | | _packedIV[j] = SwapEndianness(SwapEndianness(_packedIV[j]) + 1); |
| | 8 | 147 | | } |
| | 8 | 148 | | while (_packedIV[j] == 0 && --j >= 0); |
| | 2 | 149 | | } |
| | 184 | 150 | | } |
| | | 151 | | |
| | | 152 | | // copy uint[] to byte[] |
| | 32 | 153 | | Buffer.BlockCopy(counter, 0, buffer, 0, buffer.Length); |
| | 32 | 154 | | } |
| | | 155 | | |
| | | 156 | | // XOR 2 arrays using Uint[] and blockcopy |
| | | 157 | | private static void ArrayXOR(byte[] buffer, byte[] data, int offset, int length) |
| | 32 | 158 | | { |
| | 32 | 159 | | var words = length / 4; |
| | 32 | 160 | | if (length % 4 != 0) |
| | 0 | 161 | | { |
| | 0 | 162 | | words++; |
| | 0 | 163 | | } |
| | | 164 | | |
| | | 165 | | // convert original data to words |
| | 32 | 166 | | var datawords = new uint[words]; |
| | 32 | 167 | | Buffer.BlockCopy(data, offset, datawords, 0, length); |
| | | 168 | | |
| | | 169 | | // convert encrypted IV counter to words |
| | 32 | 170 | | var bufferwords = new uint[words]; |
| | 32 | 171 | | Buffer.BlockCopy(buffer, 0, bufferwords, 0, length); |
| | | 172 | | |
| | | 173 | | // XOR encrypted Counter with input data |
| | 1536 | 174 | | for (var i = 0; i < words; i++) |
| | 736 | 175 | | { |
| | 736 | 176 | | bufferwords[i] = bufferwords[i] ^ datawords[i]; |
| | 736 | 177 | | } |
| | | 178 | | |
| | | 179 | | // copy uint[] to byte[] |
| | 32 | 180 | | Buffer.BlockCopy(bufferwords, 0, buffer, 0, length); |
| | 32 | 181 | | } |
| | | 182 | | |
| | | 183 | | // pack the IV into an array of uint[4] |
| | | 184 | | private static uint[] GetPackedIV(byte[] iv) |
| | 30 | 185 | | { |
| | 30 | 186 | | var packedIV = new uint[4]; |
| | 30 | 187 | | packedIV[0] = BitConverter.ToUInt32(iv, 0); |
| | 30 | 188 | | packedIV[1] = BitConverter.ToUInt32(iv, 4); |
| | 30 | 189 | | packedIV[2] = BitConverter.ToUInt32(iv, 8); |
| | 30 | 190 | | packedIV[3] = BitConverter.ToUInt32(iv, 12); |
| | | 191 | | |
| | 30 | 192 | | return packedIV; |
| | 30 | 193 | | } |
| | | 194 | | |
| | | 195 | | private static uint SwapEndianness(uint x) |
| | 16 | 196 | | { |
| | 16 | 197 | | x = (x >> 16) | (x << 16); |
| | 16 | 198 | | return ((x & 0xFF00FF00) >> 8) | ((x & 0x00FF00FF) << 8); |
| | 16 | 199 | | } |
| | | 200 | | #endif |
| | | 201 | | |
| | | 202 | | private void Dispose(bool disposing) |
| | 2390 | 203 | | { |
| | 2390 | 204 | | if (disposing) |
| | 2390 | 205 | | { |
| | 2390 | 206 | | _aes.Dispose(); |
| | 2390 | 207 | | _encryptor.Dispose(); |
| | 2390 | 208 | | } |
| | 2390 | 209 | | } |
| | | 210 | | |
| | | 211 | | public void Dispose() |
| | 2390 | 212 | | { |
| | | 213 | | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method |
| | 2390 | 214 | | Dispose(disposing: true); |
| | 2390 | 215 | | GC.SuppressFinalize(this); |
| | 2390 | 216 | | } |
| | | 217 | | } |
| | | 218 | | } |
| | | 219 | | } |