* FileSystemInfo.cs: corrected COM visibility of UTC properties
[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 using System;
13 using System.Security.Cryptography;
14
15 namespace Mono.Security.Cryptography {
16
17         // This class implement most of the common code required for symmetric
18         // algorithm transforms, like:
19         // - CipherMode: Builds CBC and CFB on top of (descendant supplied) ECB
20         // - PaddingMode, transform properties, multiple blocks, reuse...
21         //
22         // Descendants MUST:
23         // - intialize themselves (like key expansion, ...)
24         // - override the ECB (Electronic Code Book) method which will only be
25         //   called using BlockSize byte[] array.
26         internal abstract class SymmetricTransform : ICryptoTransform {
27                 protected SymmetricAlgorithm algo;
28                 protected bool encrypt;
29                 private int BlockSizeByte;
30                 private byte[] temp;
31                 private byte[] temp2;
32                 private byte[] workBuff;
33                 private byte[] workout;
34                 private int FeedBackByte;
35                 private int FeedBackIter;
36                 private bool m_disposed = false;
37
38                 public SymmetricTransform (SymmetricAlgorithm symmAlgo, bool encryption, byte[] rgbIV) 
39                 {
40                         algo = symmAlgo;
41                         encrypt = encryption;
42                         BlockSizeByte = (algo.BlockSize >> 3);
43                         // mode buffers
44                         temp = new byte [BlockSizeByte];
45                         Array.Copy (rgbIV, 0, temp, 0, BlockSizeByte);
46                         temp2 = new byte [BlockSizeByte];
47                         FeedBackByte = (algo.FeedbackSize >> 3);
48                         FeedBackIter = (int) BlockSizeByte / FeedBackByte;
49                         // transform buffers
50                         workBuff = new byte [BlockSizeByte];
51                         workout =  new byte [BlockSizeByte];
52                 }
53
54                 ~SymmetricTransform () 
55                 {
56                         Dispose (false);
57                 }
58
59                 void IDisposable.Dispose () 
60                 {
61                         Dispose (true);
62                         GC.SuppressFinalize (this);  // Finalization is now unnecessary
63                 }
64
65                 // MUST be overriden by classes using unmanaged ressources
66                 // the override method must call the base class
67                 protected void Dispose (bool disposing) 
68                 {
69                         if (!m_disposed) {
70                                 if (disposing) {
71                                         // dispose managed object: zeroize and free
72                                         Array.Clear (temp, 0, BlockSizeByte);
73                                         temp = null;
74                                         Array.Clear (temp2, 0, BlockSizeByte);
75                                         temp2 = null;
76                                 }
77                                 m_disposed = true;
78                         }
79                 }
80
81                 public virtual bool CanTransformMultipleBlocks {
82                         get { return true; }
83                 }
84
85                 public bool CanReuseTransform {
86                         get { return false; }
87                 }
88
89                 public virtual int InputBlockSize {
90                         get { return BlockSizeByte; }
91                 }
92
93                 public virtual int OutputBlockSize {
94                         get { return BlockSizeByte; }
95                 }
96
97                 // note: Each block MUST be BlockSizeValue in size!!!
98                 // i.e. Any padding must be done before calling this method
99                 protected void Transform (byte[] input, byte[] output) 
100                 {
101                         switch (algo.Mode) {
102                         case CipherMode.ECB:
103                                 ECB (input, output);
104                                 break;
105                         case CipherMode.CBC:
106                                 CBC (input, output);
107                                 break;
108                         case CipherMode.CFB:
109                                 CFB (input, output);
110                                 break;
111                         case CipherMode.OFB:
112                                 OFB (input, output);
113                                 break;
114                         case CipherMode.CTS:
115                                 CTS (input, output);
116                                 break;
117                         default:
118                                 throw new NotImplementedException ("Unkown CipherMode" + algo.Mode.ToString ());
119                         }
120                 }
121
122                 // Electronic Code Book (ECB)
123                 protected abstract void ECB (byte[] input, byte[] output); 
124
125                 // Cipher-Block-Chaining (CBC)
126                 protected virtual void CBC (byte[] input, byte[] output) 
127                 {
128                         if (encrypt) {
129                                 for (int i = 0; i < BlockSizeByte; i++)
130                                         temp[i] ^= input[i];
131                                 ECB (temp, output);
132                                 Array.Copy (output, 0, temp, 0, BlockSizeByte);
133                         }
134                         else {
135                                 Array.Copy (input, 0, temp2, 0, BlockSizeByte);
136                                 ECB (input, output);
137                                 for (int i = 0; i < BlockSizeByte; i++)
138                                         output[i] ^= temp[i];
139                                 Array.Copy (temp2, 0, temp, 0, BlockSizeByte);
140                         }
141                 }
142
143                 // Cipher-FeedBack (CFB)
144                 protected virtual void CFB (byte[] input, byte[] output) 
145                 {
146                         if (encrypt) {
147                                 for (int x = 0; x < FeedBackIter; x++) {
148                                         // temp is first initialized with the IV
149                                         ECB (temp, temp2);
150
151                                         for (int i = 0; i < FeedBackByte; i++)
152                                                 output[i + x] = (byte)(temp2[i] ^ input[i + x]);
153                                         Array.Copy (temp, FeedBackByte, temp, 0, BlockSizeByte - FeedBackByte);
154                                         Array.Copy (output, x, temp, BlockSizeByte - FeedBackByte, FeedBackByte);
155                                 }
156                         }
157                         else {
158                                 for (int x = 0; x < FeedBackIter; x++) {
159                                         // we do not really decrypt this data!
160                                         encrypt = true;
161                                         // temp is first initialized with the IV
162                                         ECB (temp, temp2);
163                                         encrypt = false;
164
165                                         Array.Copy (temp, FeedBackByte, temp, 0, BlockSizeByte - FeedBackByte);
166                                         Array.Copy (input, x, temp, BlockSizeByte - FeedBackByte, FeedBackByte);
167                                         for (int i = 0; i < FeedBackByte; i++)
168                                                 output[i + x] = (byte)(temp2[i] ^ input[i + x]);
169                                 }
170                         }
171                 }
172
173                 // Output-FeedBack (OFB)
174                 protected virtual void OFB (byte[] input, byte[] output) 
175                 {
176                         throw new NotImplementedException ("OFB not yet supported");
177                 }
178
179                 // Cipher Text Stealing (CTS)
180                 protected virtual void CTS (byte[] input, byte[] output) 
181                 {
182                         throw new NotImplementedException ("CTS not yet supported");
183                 }
184
185                 // this method may get called MANY times so this is the one to optimize
186                 public virtual int TransformBlock (byte [] inputBuffer, int inputOffset, int inputCount, byte [] outputBuffer, int outputOffset) 
187                 {
188                         if (m_disposed)
189                                 throw new ObjectDisposedException ("Object is disposed");
190
191                         if (outputOffset + inputCount > outputBuffer.Length)
192                                 throw new CryptographicException ("Insufficient output buffer size.");
193
194                         int offs = inputOffset;
195                         int full;
196
197                         // this way we don't do a modulo every time we're called
198                         // and we may save a division
199                         if (inputCount != BlockSizeByte) {
200                                 if ((inputCount % BlockSizeByte) != 0)
201                                         throw new CryptographicException ("Invalid input block size.");
202
203                                 full = inputCount / BlockSizeByte;
204                         }
205                         else
206                                 full = 1;
207
208                         int total = 0;
209                         for (int i = 0; i < full; i++) {
210                                 Array.Copy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
211                                 Transform (workBuff, workout);
212                                 Array.Copy (workout, 0, outputBuffer, outputOffset, BlockSizeByte);
213                                 offs += BlockSizeByte;
214                                 outputOffset += BlockSizeByte;
215                                 total += BlockSizeByte;
216                         }
217
218                         return total;
219                 }
220
221                 private byte[] FinalEncrypt (byte[] inputBuffer, int inputOffset, int inputCount) 
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                         if (algo.Padding != PaddingMode.PKCS7) {
229                                 if (inputCount == 0)
230                                         return new byte [0];
231                                 if (rem != 0) {
232                                         if (algo.Padding == PaddingMode.None)
233                                                 throw new CryptographicException ("invalid block length");
234                                         // zero padding the input (by adding a block for the partial data)
235                                         byte[] paddedInput = new byte [full + BlockSizeByte];
236                                         Buffer.BlockCopy (inputBuffer, inputOffset, paddedInput, 0, inputCount);
237                                         inputBuffer = paddedInput;
238                                         inputOffset = 0;
239                                         inputCount = paddedInput.Length;
240                                         total = inputCount;
241                                 }
242                         }
243                         else {
244                                 // we need to add an extra block for padding
245                                 total += BlockSizeByte;
246                         }
247
248                         byte[] res = new byte [total];
249
250                         // process all blocks except the last (final) block
251                         while (total > BlockSizeByte) {
252                                 TransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, inputOffset);
253                                 inputOffset += BlockSizeByte;
254                                 total -= BlockSizeByte;
255                         }
256
257                         // now we only have a single last block to encrypt
258                         if (algo.Padding == PaddingMode.PKCS7) {
259                                 byte padding = (byte) (BlockSizeByte - rem);
260                                 for (int i = res.Length; --i >= (res.Length - padding);) 
261                                         res [i] = padding;
262                                 Array.Copy (inputBuffer, inputOffset, res, full, rem);
263                                 // the last padded block will be transformed in-place
264                                 TransformBlock (res, full, BlockSizeByte, res, full);
265                         }
266                         else
267                                 TransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, inputOffset);
268
269                         return res;
270                 }
271
272                 private byte[] FinalDecrypt (byte [] inputBuffer, int inputOffset, int inputCount) 
273                 {
274                         if ((inputCount % BlockSizeByte) > 0)
275                                 throw new CryptographicException ("Invalid input block size.");
276
277                         int total = inputCount;
278                         byte[] res = new byte [total];
279                         while (inputCount > 0) {
280                                 TransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, inputOffset);
281                                 inputOffset += BlockSizeByte;
282                                 inputCount -= BlockSizeByte;
283                         }
284
285                         switch (algo.Padding) {
286                                 case PaddingMode.None:  // nothing to do - it's a multiple of block size
287                                 case PaddingMode.Zeros: // nothing to do - user must unpad himself
288                                         break;
289                                 case PaddingMode.PKCS7:
290                                         total -= res [total - 1];
291                                         break;
292                         }
293
294                         // return output without padding
295                         if (total > 0) {
296                                 byte[] data = new byte [total];
297                                 Array.Copy (res, 0, data, 0, total);
298                                 // zeroize decrypted data (copy with padding)
299                                 Array.Clear (res, 0, res.Length);
300                                 return data;
301                         }
302                         else
303                                 return new byte [0];
304                 }
305
306                 public virtual byte [] TransformFinalBlock (byte [] inputBuffer, int inputOffset, int inputCount) 
307                 {
308                         if (m_disposed)
309                                 throw new ObjectDisposedException ("Object is disposed");
310
311                         if (encrypt)
312                                 return FinalEncrypt (inputBuffer, inputOffset, inputCount);
313                         else
314                                 return FinalDecrypt (inputBuffer, inputOffset, inputCount);
315                 }
316         }
317 }