2004-06-23 Sebastien Pouliot <sebastien@ximian.com>
[mono.git] / mcs / class / Mono.Security / Mono.Security.Cryptography / SymmetricTransform.cs
1 //
2 // Mono.Security.Cryptography.SymmetricTransform implementation
3 //
4 // Authors:
5 //      Thomas Neidhart (tome@sbox.tugraz.at)
6 //      Sebastien Pouliot <sebastien@ximian.com>
7 //
8 // Portions (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
9 // (C) 2004 Novell (http://www.novell.com)
10 //
11
12 //
13 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
14 //
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
22 // 
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 // 
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 //
34
35 using System;
36 using System.Security.Cryptography;
37
38 namespace Mono.Security.Cryptography {
39
40         // This class implement most of the common code required for symmetric
41         // algorithm transforms, like:
42         // - CipherMode: Builds CBC and CFB on top of (descendant supplied) ECB
43         // - PaddingMode, transform properties, multiple blocks, reuse...
44         //
45         // Descendants MUST:
46         // - intialize themselves (like key expansion, ...)
47         // - override the ECB (Electronic Code Book) method which will only be
48         //   called using BlockSize byte[] array.
49         internal abstract class SymmetricTransform : ICryptoTransform {
50                 protected SymmetricAlgorithm algo;
51                 protected bool encrypt;
52                 private int BlockSizeByte;
53                 private byte[] temp;
54                 private byte[] temp2;
55                 private byte[] workBuff;
56                 private byte[] workout;
57                 private int FeedBackByte;
58                 private int FeedBackIter;
59                 private bool m_disposed = false;
60                 private bool lastBlock;
61
62                 public SymmetricTransform (SymmetricAlgorithm symmAlgo, bool encryption, byte[] rgbIV) 
63                 {
64                         algo = symmAlgo;
65                         encrypt = encryption;
66                         BlockSizeByte = (algo.BlockSize >> 3);
67                         // mode buffers
68                         temp = new byte [BlockSizeByte];
69                         Buffer.BlockCopy (rgbIV, 0, temp, 0, BlockSizeByte);
70                         temp2 = new byte [BlockSizeByte];
71                         FeedBackByte = (algo.FeedbackSize >> 3);
72                         FeedBackIter = (int) BlockSizeByte / FeedBackByte;
73                         // transform buffers
74                         workBuff = new byte [BlockSizeByte];
75                         workout =  new byte [BlockSizeByte];
76                 }
77
78                 ~SymmetricTransform () 
79                 {
80                         Dispose (false);
81                 }
82
83                 void IDisposable.Dispose () 
84                 {
85                         Dispose (true);
86                         GC.SuppressFinalize (this);  // Finalization is now unnecessary
87                 }
88
89                 // MUST be overriden by classes using unmanaged ressources
90                 // the override method must call the base class
91                 protected void Dispose (bool disposing) 
92                 {
93                         if (!m_disposed) {
94                                 if (disposing) {
95                                         // dispose managed object: zeroize and free
96                                         Array.Clear (temp, 0, BlockSizeByte);
97                                         temp = null;
98                                         Array.Clear (temp2, 0, BlockSizeByte);
99                                         temp2 = null;
100                                 }
101                                 m_disposed = true;
102                         }
103                 }
104
105                 public virtual bool CanTransformMultipleBlocks {
106                         get { return true; }
107                 }
108
109                 public bool CanReuseTransform {
110                         get { return false; }
111                 }
112
113                 public virtual int InputBlockSize {
114                         get { return BlockSizeByte; }
115                 }
116
117                 public virtual int OutputBlockSize {
118                         get { return BlockSizeByte; }
119                 }
120
121                 // note: Each block MUST be BlockSizeValue in size!!!
122                 // i.e. Any padding must be done before calling this method
123                 protected void Transform (byte[] input, byte[] output) 
124                 {
125                         switch (algo.Mode) {
126                         case CipherMode.ECB:
127                                 ECB (input, output);
128                                 break;
129                         case CipherMode.CBC:
130                                 CBC (input, output);
131                                 break;
132                         case CipherMode.CFB:
133                                 CFB (input, output);
134                                 break;
135                         case CipherMode.OFB:
136                                 OFB (input, output);
137                                 break;
138                         case CipherMode.CTS:
139                                 CTS (input, output);
140                                 break;
141                         default:
142                                 throw new NotImplementedException ("Unkown CipherMode" + algo.Mode.ToString ());
143                         }
144                 }
145
146                 // Electronic Code Book (ECB)
147                 protected abstract void ECB (byte[] input, byte[] output); 
148
149                 // Cipher-Block-Chaining (CBC)
150                 protected virtual void CBC (byte[] input, byte[] output) 
151                 {
152                         if (encrypt) {
153                                 for (int i = 0; i < BlockSizeByte; i++)
154                                         temp[i] ^= input[i];
155                                 ECB (temp, output);
156                                 Buffer.BlockCopy (output, 0, temp, 0, BlockSizeByte);
157                         }
158                         else {
159                                 Buffer.BlockCopy (input, 0, temp2, 0, BlockSizeByte);
160                                 ECB (input, output);
161                                 for (int i = 0; i < BlockSizeByte; i++)
162                                         output[i] ^= temp[i];
163                                 Buffer.BlockCopy (temp2, 0, temp, 0, BlockSizeByte);
164                         }
165                 }
166
167                 // Cipher-FeedBack (CFB)
168                 protected virtual void CFB (byte[] input, byte[] output) 
169                 {
170                         if (encrypt) {
171                                 for (int x = 0; x < FeedBackIter; x++) {
172                                         // temp is first initialized with the IV
173                                         ECB (temp, temp2);
174
175                                         for (int i = 0; i < FeedBackByte; i++)
176                                                 output[i + x] = (byte)(temp2[i] ^ input[i + x]);
177                                         Buffer.BlockCopy (temp, FeedBackByte, temp, 0, BlockSizeByte - FeedBackByte);
178                                         Buffer.BlockCopy (output, x, temp, BlockSizeByte - FeedBackByte, FeedBackByte);
179                                 }
180                         }
181                         else {
182                                 for (int x = 0; x < FeedBackIter; x++) {
183                                         // we do not really decrypt this data!
184                                         encrypt = true;
185                                         // temp is first initialized with the IV
186                                         ECB (temp, temp2);
187                                         encrypt = false;
188
189                                         Buffer.BlockCopy (temp, FeedBackByte, temp, 0, BlockSizeByte - FeedBackByte);
190                                         Buffer.BlockCopy (input, x, temp, BlockSizeByte - FeedBackByte, FeedBackByte);
191                                         for (int i = 0; i < FeedBackByte; i++)
192                                                 output[i + x] = (byte)(temp2[i] ^ input[i + x]);
193                                 }
194                         }
195                 }
196
197                 // Output-FeedBack (OFB)
198                 protected virtual void OFB (byte[] input, byte[] output) 
199                 {
200                         throw new NotImplementedException ("OFB not yet supported");
201                 }
202
203                 // Cipher Text Stealing (CTS)
204                 protected virtual void CTS (byte[] input, byte[] output) 
205                 {
206                         throw new NotImplementedException ("CTS not yet supported");
207                 }
208
209                 private void CheckInput (byte[] inputBuffer, int inputOffset, int inputCount)
210                 {
211                         if (inputBuffer == null)
212                                 throw new ArgumentNullException ("inputBuffer");
213                         if (inputOffset < 0)
214                                 throw new ArgumentOutOfRangeException ("inputOffset", "< 0");
215                         if (inputCount < 0)
216                                 throw new ArgumentOutOfRangeException ("inputCount", "< 0");
217                         // ordered to avoid possible integer overflow
218                         if (inputOffset > inputBuffer.Length - inputCount)
219                                 throw new ArgumentException ("inputBuffer", Locale.GetText ("Overflow"));
220                 }
221
222                 // this method may get called MANY times so this is the one to optimize
223                 public virtual int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) 
224                 {
225                         if (m_disposed)
226                                 throw new ObjectDisposedException ("Object is disposed");
227                         CheckInput (inputBuffer, inputOffset, inputCount);
228                         // check output parameters
229                         if (outputBuffer == null)
230                                 throw new ArgumentNullException ("outputBuffer");
231                         if (outputOffset < 0)
232                                 throw new ArgumentOutOfRangeException ("outputOffset", "< 0");
233                         // ordered to avoid possible integer overflow
234                         if (outputOffset > outputBuffer.Length - inputCount)
235                                 throw new ArgumentException ("outputBuffer", Locale.GetText ("Overflow"));
236
237                         return InternalTransformBlock (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
238                 }
239
240                 private bool KeepLastBlock {
241                         get {
242                                 return ((!encrypt) && (algo.Mode != CipherMode.ECB) && (algo.Padding != PaddingMode.None));
243                         }
244                 }
245
246                 private int InternalTransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) 
247                 {
248                         int offs = inputOffset;
249                         int full;
250
251                         // this way we don't do a modulo every time we're called
252                         // and we may save a division
253                         if (inputCount != BlockSizeByte) {
254                                 if ((inputCount % BlockSizeByte) != 0)
255                                         throw new CryptographicException ("Invalid input block size.");
256
257                                 full = inputCount / BlockSizeByte;
258                         }
259                         else
260                                 full = 1;
261
262                         if (KeepLastBlock)
263                                 full--;
264
265                         int total = 0;
266
267                         if (lastBlock) {
268                                 Transform (workBuff, workout);
269                                 Buffer.BlockCopy (workout, 0, outputBuffer, outputOffset, BlockSizeByte);
270                                 outputOffset += BlockSizeByte;
271                                 total += BlockSizeByte;
272                                 lastBlock = false;
273                         }
274
275                         for (int i = 0; i < full; i++) {
276                                 Buffer.BlockCopy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
277                                 Transform (workBuff, workout);
278                                 Buffer.BlockCopy (workout, 0, outputBuffer, outputOffset, BlockSizeByte);
279                                 offs += BlockSizeByte;
280                                 outputOffset += BlockSizeByte;
281                                 total += BlockSizeByte;
282                         }
283
284                         if (KeepLastBlock) {
285                                 Buffer.BlockCopy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
286                                 lastBlock = true;
287                         }
288
289                         return total;
290                 }
291
292                 private byte[] FinalEncrypt (byte[] inputBuffer, int inputOffset, int inputCount) 
293                 {
294                         // are there still full block to process ?
295                         int full = (inputCount / BlockSizeByte) * BlockSizeByte;
296                         int rem = inputCount - full;
297                         int total = full;
298
299                         if (algo.Padding != PaddingMode.PKCS7) {
300                                 if (inputCount == 0)
301                                         return new byte [0];
302                                 if (rem != 0) {
303                                         if (algo.Padding == PaddingMode.None)
304                                                 throw new CryptographicException ("invalid block length");
305                                         // zero padding the input (by adding a block for the partial data)
306                                         byte[] paddedInput = new byte [full + BlockSizeByte];
307                                         Buffer.BlockCopy (inputBuffer, inputOffset, paddedInput, 0, inputCount);
308                                         inputBuffer = paddedInput;
309                                         inputOffset = 0;
310                                         inputCount = paddedInput.Length;
311                                         total = inputCount;
312                                 }
313                         }
314                         else {
315                                 // we need to add an extra block for padding
316                                 total += BlockSizeByte;
317                         }
318
319                         byte[] res = new byte [total];
320                         int outputOffset = 0;
321
322                         // process all blocks except the last (final) block
323                         while (total > BlockSizeByte) {
324                                 InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
325                                 inputOffset += BlockSizeByte;
326                                 outputOffset += BlockSizeByte;
327                                 total -= BlockSizeByte;
328                         }
329
330                         // now we only have a single last block to encrypt
331                         if (algo.Padding == PaddingMode.PKCS7) {
332                                 byte padding = (byte) (BlockSizeByte - rem);
333                                 for (int i = res.Length; --i >= (res.Length - padding);) 
334                                         res [i] = padding;
335                                 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
336                                 // the last padded block will be transformed in-place
337                                 InternalTransformBlock (res, full, BlockSizeByte, res, full);
338                         }
339                         else
340                                 InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
341
342                         return res;
343                 }
344
345                 private byte[] FinalDecrypt (byte[] inputBuffer, int inputOffset, int inputCount) 
346                 {
347                         if ((inputCount % BlockSizeByte) > 0)
348                                 throw new CryptographicException ("Invalid input block size.");
349
350                         int total = inputCount;
351                         if (lastBlock)
352                                 total += BlockSizeByte;
353
354                         byte[] res = new byte [total];
355                         int outputOffset = 0;
356
357                         while (inputCount > 0) {
358                                 int len = InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
359                                 inputOffset += BlockSizeByte;
360                                 outputOffset += len;
361                                 inputCount -= BlockSizeByte;
362                         }
363
364                         if (lastBlock) {
365                                 Transform (workBuff, workout);
366                                 Buffer.BlockCopy (workout, 0, res, outputOffset, BlockSizeByte);
367                                 outputOffset += BlockSizeByte;
368                                 lastBlock = false;
369                         }
370
371                         switch (algo.Padding) {
372                                 case PaddingMode.None:  // nothing to do - it's a multiple of block size
373                                 case PaddingMode.Zeros: // nothing to do - user must unpad himself
374                                         break;
375                                 case PaddingMode.PKCS7:
376                                         total -= res [total - 1];
377                                         break;
378                         }
379
380                         // return output without padding
381                         if (total > 0) {
382                                 byte[] data = new byte [total];
383                                 Buffer.BlockCopy (res, 0, data, 0, total);
384                                 // zeroize decrypted data (copy with padding)
385                                 Array.Clear (res, 0, res.Length);
386                                 return data;
387                         }
388                         else
389                                 return new byte [0];
390                 }
391
392                 public virtual byte[] TransformFinalBlock (byte[] inputBuffer, int inputOffset, int inputCount) 
393                 {
394                         if (m_disposed)
395                                 throw new ObjectDisposedException ("Object is disposed");
396                         CheckInput (inputBuffer, inputOffset, inputCount);
397
398                         if (encrypt)
399                                 return FinalEncrypt (inputBuffer, inputOffset, inputCount);
400                         else
401                                 return FinalDecrypt (inputBuffer, inputOffset, inputCount);
402                 }
403         }
404 }