2003-07-31 Sebastien Pouliot <spouliot@videotron.ca>
[mono.git] / mcs / class / corlib / System.Security.Cryptography / SymmetricAlgorithm.cs
1 //
2 // System.Security.Cryptography SymmetricAlgorithm Class implementation
3 //
4 // Authors:
5 //   Thomas Neidhart (tome@sbox.tugraz.at)
6 //   Sebastien Pouliot (spouliot@motus.com)
7 //
8 // Portions (C) 2002 Motus Technologies Inc. (http://www.motus.com)
9 //
10
11 using System;
12
13 namespace System.Security.Cryptography {
14
15         // This class implement most of the common code required for symmetric
16         // algorithm transforms, like:
17         // - CipherMode: Builds CBC and CFB on top of (descendant supplied) ECB
18         // - PaddingMode, transform properties, multiple blocks, reuse...
19         //
20         // Descendants MUST:
21         // - intialize themselves (like key expansion, ...)
22         // - override the ECB (Electronic Code Book) method which will only be
23         //   called using BlockSize byte[] array.
24         internal abstract class SymmetricTransform : ICryptoTransform {
25                 protected SymmetricAlgorithm algo;
26                 protected bool encrypt;
27                 private int BlockSizeByte;
28                 private byte[] temp;
29                 private byte[] temp2;
30                 private byte[] workBuff;
31                 private byte[] workout;
32                 private int FeedBackByte;
33                 private int FeedBackIter;
34                 private bool m_disposed = false;
35
36                 public SymmetricTransform (SymmetricAlgorithm symmAlgo, bool encryption, byte[] rgbIV) 
37                 {
38                         algo = symmAlgo;
39                         encrypt = encryption;
40                         BlockSizeByte = (algo.BlockSize >> 3);
41                         // mode buffers
42                         temp = new byte [BlockSizeByte];
43                         Array.Copy (rgbIV, 0, temp, 0, BlockSizeByte);
44                         temp2 = new byte [BlockSizeByte];
45                         FeedBackByte = (algo.FeedbackSize >> 3);
46                         FeedBackIter = (int) BlockSizeByte / FeedBackByte;
47                         // transform buffers
48                         workBuff = new byte [BlockSizeByte];
49                         workout =  new byte [BlockSizeByte];
50                 }
51
52                 ~SymmetricTransform () 
53                 {
54                         Dispose (false);
55                 }
56
57                 void IDisposable.Dispose () 
58                 {
59                         Dispose (true);
60                         GC.SuppressFinalize (this);  // Finalization is now unnecessary
61                 }
62
63                 // MUST be overriden by classes using unmanaged ressources
64                 // the override method must call the base class
65                 protected void Dispose (bool disposing) 
66                 {
67                         if (!m_disposed) {
68                                 if (disposing) {
69                                         // dispose managed object: zeroize and free
70                                         Array.Clear (temp, 0, BlockSizeByte);
71                                         temp = null;
72                                         Array.Clear (temp2, 0, BlockSizeByte);
73                                         temp2 = null;
74                                 }
75                                 m_disposed = true;
76                         }
77                 }
78
79                 public virtual bool CanTransformMultipleBlocks {
80                         get { return true; }
81                 }
82
83                 public bool CanReuseTransform {
84                         get { return false; }
85                 }
86
87                 public virtual int InputBlockSize {
88                         get { return BlockSizeByte; }
89                 }
90
91                 public virtual int OutputBlockSize {
92                         get { return BlockSizeByte; }
93                 }
94
95                 // note: Each block MUST be BlockSizeValue in size!!!
96                 // i.e. Any padding must be done before calling this method
97                 protected void Transform (byte[] input, byte[] output) 
98                 {
99                         switch (algo.Mode) {
100                         case CipherMode.ECB:
101                                 ECB (input, output);
102                                 break;
103                         case CipherMode.CBC:
104                                 CBC (input, output);
105                                 break;
106                         case CipherMode.CFB:
107                                 CFB (input, output);
108                                 break;
109                         case CipherMode.OFB:
110                                 OFB (input, output);
111                                 break;
112                         case CipherMode.CTS:
113                                 CTS (input, output);
114                                 break;
115                         default:
116                                 throw new NotImplementedException ("Unkown CipherMode" + algo.Mode.ToString ());
117                         }
118                 }
119
120                 // Electronic Code Book (ECB)
121                 protected abstract void ECB (byte[] input, byte[] output); 
122
123                 // Cipher-Block-Chaining (CBC)
124                 protected virtual void CBC (byte[] input, byte[] output) 
125                 {
126                         if (encrypt) {
127                                 for (int i = 0; i < BlockSizeByte; i++)
128                                         temp[i] ^= input[i];
129                                 ECB (temp, output);
130                                 Array.Copy (output, 0, temp, 0, BlockSizeByte);
131                         }
132                         else {
133                                 Array.Copy (input, 0, temp2, 0, BlockSizeByte);
134                                 ECB (input, output);
135                                 for (int i = 0; i < BlockSizeByte; i++)
136                                         output[i] ^= temp[i];
137                                 Array.Copy (temp2, 0, temp, 0, BlockSizeByte);
138                         }
139                 }
140
141                 // Cipher-FeedBack (CFB)
142                 protected virtual void CFB (byte[] input, byte[] output) 
143                 {
144                         if (encrypt) {
145                                 for (int x = 0; x < FeedBackIter; x++) {
146                                         // temp is first initialized with the IV
147                                         ECB (temp, temp2);
148
149                                         for (int i = 0; i < FeedBackByte; i++)
150                                                 output[i + x] = (byte)(temp2[i] ^ input[i + x]);
151                                         Array.Copy (temp, FeedBackByte, temp, 0, BlockSizeByte - FeedBackByte);
152                                         Array.Copy (output, x, temp, BlockSizeByte - FeedBackByte, FeedBackByte);
153                                 }
154                         }
155                         else {
156                                 for (int x = 0; x < FeedBackIter; x++) {
157                                         // we do not really decrypt this data!
158                                         encrypt = true;
159                                         // temp is first initialized with the IV
160                                         ECB (temp, temp2);
161                                         encrypt = false;
162
163                                         Array.Copy (temp, FeedBackByte, temp, 0, BlockSizeByte - FeedBackByte);
164                                         Array.Copy (input, x, temp, BlockSizeByte - FeedBackByte, FeedBackByte);
165                                         for (int i = 0; i < FeedBackByte; i++)
166                                                 output[i + x] = (byte)(temp2[i] ^ input[i + x]);
167                                 }
168                         }
169                 }
170
171                 // Output-FeedBack (OFB)
172                 protected virtual void OFB (byte[] input, byte[] output) 
173                 {
174                         throw new NotImplementedException ("OFB not yet supported");
175                 }
176
177                 // Cipher Text Stealing (CTS)
178                 protected virtual void CTS (byte[] input, byte[] output) 
179                 {
180                         throw new NotImplementedException ("CTS not yet supported");
181                 }
182
183                 // this method may get called MANY times so this is the one to optimize
184                 public virtual int TransformBlock (byte [] inputBuffer, int inputOffset, int inputCount, byte [] outputBuffer, int outputOffset) 
185                 {
186                         if (m_disposed)
187                                 throw new ObjectDisposedException ("Object is disposed");
188
189                         if (outputOffset + inputCount > outputBuffer.Length)
190                                 throw new CryptographicException ("Insufficient output buffer size.");
191
192                         int offs = inputOffset;
193                         int full;
194
195                         // this way we don't do a modulo every time we're called
196                         // and we may save a division
197                         if (inputCount != BlockSizeByte) {
198                                 if ((inputCount % BlockSizeByte) != 0)
199                                         throw new CryptographicException ("Invalid input block size.");
200
201                                 full = inputCount / BlockSizeByte;
202                         }
203                         else
204                                 full = 1;
205
206                         int total = 0;
207                         for (int i = 0; i < full; i++) {
208                                 Array.Copy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
209                                 Transform (workBuff, workout);
210                                 Array.Copy (workout, 0, outputBuffer, outputOffset, BlockSizeByte);
211                                 offs += BlockSizeByte;
212                                 outputOffset += BlockSizeByte;
213                                 total += BlockSizeByte;
214                         }
215
216                         return total;
217                 }
218
219                 private byte[] FinalEncrypt (byte [] inputBuffer, int inputOffset, int inputCount) 
220                 {
221                         if (inputCount == 0) return new byte[0];
222
223                         // are there still full block to process ?
224                         int full = (inputCount / BlockSizeByte) * BlockSizeByte;
225                         int rem = inputCount - full;
226                         int total = full;
227
228                         // we need to add an extra block if...
229                         // a. the last block isn't complate (partial);
230                         // b. the last block is complete but we use padding
231                         if ((rem > 0) || (algo.Padding != PaddingMode.None))
232                                 total += BlockSizeByte;
233                         byte[] res = new byte [total];
234
235                         // process all blocks except the last (final) block
236                         while (total > BlockSizeByte) {
237                                 TransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, inputOffset);
238                                 inputOffset += BlockSizeByte;
239                                 total -= BlockSizeByte;
240                         }
241
242                         // now we only have a single last block to encrypt
243                         int padding = BlockSizeByte - rem;
244                         switch (algo.Padding) {
245                                 case PaddingMode.None:
246                                         break;
247                                 case PaddingMode.PKCS7:
248                                         for (int i = BlockSizeByte; --i >= (BlockSizeByte - padding);) 
249                                                 res [i] = (byte) padding;
250                                         break;
251                                 case PaddingMode.Zeros:
252                                         for (int i = BlockSizeByte; --i >= (BlockSizeByte - padding);)
253                                                 res [i] = 0;
254                                         break;
255                         }
256                         Array.Copy (inputBuffer, inputOffset, res, full, rem);
257
258                         // the last padded block will be transformed in-place
259                         TransformBlock (res, full, BlockSizeByte, res, full);
260                         return res;
261                 }
262
263                 private byte[] FinalDecrypt (byte [] inputBuffer, int inputOffset, int inputCount) 
264                 {
265                         if ((inputCount % BlockSizeByte) > 0)
266                                 throw new CryptographicException ("Invalid input block size.");
267
268                         int total = inputCount;
269                         byte[] res = new byte [total];
270                         while (inputCount > 0) {
271                                 TransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, inputOffset);
272                                 inputOffset += BlockSizeByte;
273                                 inputCount -= BlockSizeByte;
274                         }
275
276                         switch (algo.Padding) {
277                                 case PaddingMode.None:
278                                         break;
279                                 case PaddingMode.PKCS7:
280                                         total -= res [total - 1];
281                                         break;
282                                 case PaddingMode.Zeros:
283                                         // TODO
284                                         break;
285                         }
286
287                         // return output without padding
288                         byte[] data = new byte [total];
289                         Array.Copy (res, 0, data, 0, total);
290                         // zeroize decrypted data (copy with padding)
291                         Array.Clear (res, 0, res.Length);
292                         return data;
293                 }
294
295                 public virtual byte [] TransformFinalBlock (byte [] inputBuffer, int inputOffset, int inputCount) 
296                 {
297                         if (m_disposed)
298                                 throw new ObjectDisposedException ("Object is disposed");
299
300                         if (encrypt)
301                                 return FinalEncrypt (inputBuffer, inputOffset, inputCount);
302                         else
303                                 return FinalDecrypt (inputBuffer, inputOffset, inputCount);
304                 }
305         }
306
307         /// <summary>
308         /// Abstract base class for all cryptographic symmetric algorithms.
309         /// Available algorithms include:
310         /// DES, RC2, Rijndael, TripleDES
311         /// </summary>
312         public abstract class SymmetricAlgorithm : IDisposable {
313                 protected int BlockSizeValue; // The block size of the cryptographic operation in bits. 
314                 protected int FeedbackSizeValue; // The feedback size of the cryptographic operation in bits. 
315                 protected byte[] IVValue; // The initialization vector ( IV) for the symmetric algorithm. 
316                 protected int KeySizeValue; // The size of the secret key used by the symmetric algorithm in bits. 
317                 protected byte[] KeyValue; // The secret key for the symmetric algorithm. 
318                 protected KeySizes[] LegalBlockSizesValue; // Specifies the block sizes that are supported by the symmetric algorithm. 
319                 protected KeySizes[] LegalKeySizesValue; // Specifies the key sizes that are supported by the symmetric algorithm. 
320                 protected CipherMode ModeValue; // Represents the cipher mode used in the symmetric algorithm. 
321                 protected PaddingMode PaddingValue; // Represents the padding mode used in the symmetric algorithm. 
322                 private bool m_disposed;
323
324                 /// <summary>
325                 /// Called from constructor of derived class.
326                 /// </summary>
327                 public SymmetricAlgorithm () 
328                 {
329                         ModeValue = CipherMode.CBC;
330                         PaddingValue = PaddingMode.PKCS7;
331                         m_disposed = false;
332                 }
333                 
334                 /// <summary>
335                 /// Called from constructor of derived class.
336                 /// </summary>
337                 ~SymmetricAlgorithm () 
338                 {
339                         Dispose (false);
340                 }
341
342                 public void Clear() 
343                 {
344                         Dispose (true);
345                 }
346
347                 void IDisposable.Dispose () 
348                 {
349                         Dispose (true);
350                         GC.SuppressFinalize (this);  // Finalization is now unnecessary
351                 }
352
353                 protected virtual void Dispose (bool disposing) 
354                 {
355                         if (!m_disposed) {
356                                 // always zeroize keys
357                                 if (KeyValue != null) {
358                                         // Zeroize the secret key and free
359                                         Array.Clear (KeyValue, 0, KeyValue.Length);
360                                         KeyValue = null;
361                                 }
362                                 // dispose unmanaged managed objects
363                                 if (disposing) {
364                                         // dispose managed objects
365                                 }
366                                 m_disposed = true;
367                         }
368                 }
369
370                 /// <summary>
371                 /// Gets or sets the actual BlockSize
372                 /// </summary>
373                 public virtual int BlockSize {
374                         get { return this.BlockSizeValue; }
375                         set {
376                                 if (KeySizes.IsLegalKeySize (this.LegalBlockSizesValue, value))
377                                         this.BlockSizeValue = value;
378                                 else
379                                         throw new CryptographicException("block size not supported by algorithm");
380                         }
381                 }
382
383                 /// <summary>
384                 /// Gets or sets the actual FeedbackSize
385                 /// </summary>
386                 public virtual int FeedbackSize {
387                         get { return this.FeedbackSizeValue; }
388                         set {
389                                 if (value > this.BlockSizeValue)
390                                         throw new CryptographicException("feedback size larger than block size");
391                                 else
392                                         this.FeedbackSizeValue = value;
393                         }
394                 }
395                 
396                 /// <summary>
397                 /// Gets or sets the actual Initial Vector
398                 /// </summary>
399                 public virtual byte[] IV {
400                         get {
401                                 if (this.IVValue == null)
402                                         GenerateIV();
403
404                                 return this.IVValue;
405                         }
406                         set {
407                                 if (value == null)
408                                         throw new ArgumentNullException ("tried setting initial vector to null");
409                                         
410                                 if (value.Length * 8 != this.BlockSizeValue)
411                                         throw new CryptographicException ("IV length must match block size");
412                                 
413                                 this.IVValue = new byte [value.Length];
414                                 Array.Copy (value, 0, this.IVValue, 0, value.Length);
415                         }
416                 }
417
418                 /// <summary>
419                 /// Gets or sets the actual key
420                 /// </summary>
421                 public virtual byte[] Key {
422                         get {
423                                 if (this.KeyValue == null)
424                                         GenerateKey();
425
426                                 return this.KeyValue;
427                         }
428                         set {
429                                 if (value == null)
430                                         throw new ArgumentNullException ("tried setting key to null");
431
432                                 if (!KeySizes.IsLegalKeySize (this.LegalKeySizesValue, value.Length * 8))
433                                         throw new CryptographicException ("key size not supported by algorithm");
434
435                                 this.KeySizeValue = value.Length * 8;
436                                 this.KeyValue = new byte [value.Length];
437                                 Array.Copy (value, 0, this.KeyValue, 0, value.Length);
438                         }
439                 }
440                 
441                 /// <summary>
442                 /// Gets or sets the actual key size in bits
443                 /// </summary>
444                 public virtual int KeySize {
445                         get { return this.KeySizeValue; }
446                         set {
447                                 if (!KeySizes.IsLegalKeySize (this.LegalKeySizesValue, value))
448                                         throw new CryptographicException ("key size not supported by algorithm");
449                                 
450                                 this.KeyValue = null;
451                                 this.KeySizeValue = value;
452                         }
453                 }
454
455                 /// <summary>
456                 /// Gets all legal block sizes
457                 /// </summary>
458                 public virtual KeySizes[] LegalBlockSizes {
459                         get { return this.LegalBlockSizesValue; }
460                 }
461
462                 /// <summary>
463                 /// Gets all legal key sizes
464                 /// </summary>
465                 public virtual KeySizes[] LegalKeySizes {
466                         get { return this.LegalKeySizesValue; }
467                 }
468
469                 /// <summary>
470                 /// Gets or sets the actual cipher mode
471                 /// </summary>
472                 public virtual CipherMode Mode {
473                         get { return this.ModeValue; }
474                         set {
475                                 if (Enum.IsDefined( ModeValue.GetType (), value))
476                                         this.ModeValue = value;
477                                 else
478                                         throw new CryptographicException ("padding mode not available");
479                         }
480                 }
481
482                 /// <summary>
483                 /// Gets or sets the actual padding
484                 /// </summary>
485                 public virtual PaddingMode Padding {
486                         get { return this.PaddingValue; }
487                         set {
488                                 if (Enum.IsDefined (PaddingValue.GetType (), value))
489                                         this.PaddingValue = value;
490                                 else
491                                         throw new CryptographicException ("padding mode not available");
492                         }
493                 }
494
495                 /// <summary>
496                 /// Gets an Decryptor transform object to work with a CryptoStream
497                 /// </summary>
498                 public virtual ICryptoTransform CreateDecryptor () 
499                 {
500                         return CreateDecryptor (Key, IV);
501                 }
502
503                 /// <summary>
504                 /// Gets an Decryptor transform object to work with a CryptoStream
505                 /// </summary>
506                 public abstract ICryptoTransform CreateDecryptor (byte[] rgbKey, byte[] rgbIV);
507
508                 /// <summary>
509                 /// Gets an Encryptor transform object to work with a CryptoStream
510                 /// </summary>
511                 public virtual ICryptoTransform CreateEncryptor() 
512                 {
513                         return CreateEncryptor (Key, IV);
514                 }
515
516                 /// <summary>
517                 /// Gets an Encryptor transform object to work with a CryptoStream
518                 /// </summary>
519                 public abstract ICryptoTransform CreateEncryptor (byte[] rgbKey, byte[] rgbIV);
520
521                 /// <summary>
522                 /// used to generate an inital vector if none is specified
523                 /// </summary>
524                 public abstract void GenerateIV ();
525
526                 /// </summary>
527                 /// used to generate a random key if none is specified
528                 /// </summary>
529                 public abstract void GenerateKey ();
530
531                 /// <summary>
532                 /// Checks wether the given keyLength is valid for the current algorithm
533                 /// </summary>
534                 /// <param name="bitLength">the given keyLength</param>
535                 public bool ValidKeySize (int bitLength) 
536                 {
537                         return KeySizes.IsLegalKeySize (LegalKeySizesValue, bitLength);
538                 }
539                 
540                 /// <summary>
541                 /// Creates the default implementation of the default symmetric algorithm (Rijndael).
542                 /// </summary>
543                 // LAMESPEC: Default is Rijndael - not TripleDES
544                 public static SymmetricAlgorithm Create () 
545                 {
546                         return Create ("System.Security.Cryptography.SymmetricAlgorithm");
547                 }
548
549                 /// <summary>
550                 /// Creates a specific implementation of the given symmetric algorithm.
551                 /// </summary>
552                 /// <param name="algName">Specifies which derived class to create</param>
553                 public static SymmetricAlgorithm Create (string algName) 
554                 {
555                         return (SymmetricAlgorithm) CryptoConfig.CreateFromName (algName);
556                 }
557         }
558 }
559