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