2010-06-21 Marek Habersack <mhabersack@novell.com>
[mcs.git] / class / corlib / Mono.Security.Cryptography / SymmetricTransform.cs
blobbda13d875c517bb0d079b9f005f7b311c332d528
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-2008 Novell, Inc (http://www.novell.com)
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.
31 using System;
32 using System.Security.Cryptography;
34 namespace Mono.Security.Cryptography {
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...
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 #if !MOONLIGHT
54 // Silverlight 2.0 does not support any feedback mode
55 private int FeedBackByte;
56 private int FeedBackIter;
57 #endif
58 private bool m_disposed = false;
59 private bool lastBlock;
61 public SymmetricTransform (SymmetricAlgorithm symmAlgo, bool encryption, byte[] rgbIV)
63 algo = symmAlgo;
64 encrypt = encryption;
65 BlockSizeByte = (algo.BlockSize >> 3);
67 if (rgbIV == null) {
68 rgbIV = KeyBuilder.IV (BlockSizeByte);
69 } else {
70 rgbIV = (byte[]) rgbIV.Clone ();
72 // compare the IV length with the "currently selected" block size and *ignore* IV that are too big
73 if (rgbIV.Length < BlockSizeByte) {
74 string msg = Locale.GetText ("IV is too small ({0} bytes), it should be {1} bytes long.",
75 rgbIV.Length, BlockSizeByte);
76 throw new CryptographicException (msg);
78 // mode buffers
79 temp = new byte [BlockSizeByte];
80 Buffer.BlockCopy (rgbIV, 0, temp, 0, System.Math.Min (BlockSizeByte, rgbIV.Length));
81 temp2 = new byte [BlockSizeByte];
82 #if !MOONLIGHT
83 FeedBackByte = (algo.FeedbackSize >> 3);
84 if (FeedBackByte != 0)
85 FeedBackIter = (int) BlockSizeByte / FeedBackByte;
86 #endif
87 // transform buffers
88 workBuff = new byte [BlockSizeByte];
89 workout = new byte [BlockSizeByte];
92 ~SymmetricTransform ()
94 Dispose (false);
97 void IDisposable.Dispose ()
99 Dispose (true);
100 GC.SuppressFinalize (this); // Finalization is now unnecessary
103 // MUST be overriden by classes using unmanaged ressources
104 // the override method must call the base class
105 protected virtual void Dispose (bool disposing)
107 if (!m_disposed) {
108 if (disposing) {
109 // dispose managed object: zeroize and free
110 Array.Clear (temp, 0, BlockSizeByte);
111 temp = null;
112 Array.Clear (temp2, 0, BlockSizeByte);
113 temp2 = null;
115 m_disposed = true;
119 public virtual bool CanTransformMultipleBlocks {
120 get { return true; }
123 public virtual bool CanReuseTransform {
124 get { return false; }
127 public virtual int InputBlockSize {
128 get { return BlockSizeByte; }
131 public virtual int OutputBlockSize {
132 get { return BlockSizeByte; }
135 // note: Each block MUST be BlockSizeValue in size!!!
136 // i.e. Any padding must be done before calling this method
137 protected virtual void Transform (byte[] input, byte[] output)
139 #if MOONLIGHT
140 // Silverlight 2.0 only supports CBC
141 CBC (input, output);
142 #else
143 switch (algo.Mode) {
144 case CipherMode.ECB:
145 ECB (input, output);
146 break;
147 case CipherMode.CBC:
148 CBC (input, output);
149 break;
150 case CipherMode.CFB:
151 CFB (input, output);
152 break;
153 case CipherMode.OFB:
154 OFB (input, output);
155 break;
156 case CipherMode.CTS:
157 CTS (input, output);
158 break;
159 default:
160 throw new NotImplementedException ("Unkown CipherMode" + algo.Mode.ToString ());
162 #endif
165 // Electronic Code Book (ECB)
166 protected abstract void ECB (byte[] input, byte[] output);
168 // Cipher-Block-Chaining (CBC)
169 protected virtual void CBC (byte[] input, byte[] output)
171 if (encrypt) {
172 for (int i = 0; i < BlockSizeByte; i++)
173 temp[i] ^= input[i];
174 ECB (temp, output);
175 Buffer.BlockCopy (output, 0, temp, 0, BlockSizeByte);
177 else {
178 Buffer.BlockCopy (input, 0, temp2, 0, BlockSizeByte);
179 ECB (input, output);
180 for (int i = 0; i < BlockSizeByte; i++)
181 output[i] ^= temp[i];
182 Buffer.BlockCopy (temp2, 0, temp, 0, BlockSizeByte);
186 #if !MOONLIGHT
187 // Cipher-FeedBack (CFB)
188 protected virtual void CFB (byte[] input, byte[] output)
190 if (encrypt) {
191 for (int x = 0; x < FeedBackIter; x++) {
192 // temp is first initialized with the IV
193 ECB (temp, temp2);
195 for (int i = 0; i < FeedBackByte; i++)
196 output[i + x] = (byte)(temp2[i] ^ input[i + x]);
197 Buffer.BlockCopy (temp, FeedBackByte, temp, 0, BlockSizeByte - FeedBackByte);
198 Buffer.BlockCopy (output, x, temp, BlockSizeByte - FeedBackByte, FeedBackByte);
201 else {
202 for (int x = 0; x < FeedBackIter; x++) {
203 // we do not really decrypt this data!
204 encrypt = true;
205 // temp is first initialized with the IV
206 ECB (temp, temp2);
207 encrypt = false;
209 Buffer.BlockCopy (temp, FeedBackByte, temp, 0, BlockSizeByte - FeedBackByte);
210 Buffer.BlockCopy (input, x, temp, BlockSizeByte - FeedBackByte, FeedBackByte);
211 for (int i = 0; i < FeedBackByte; i++)
212 output[i + x] = (byte)(temp2[i] ^ input[i + x]);
217 // Output-FeedBack (OFB)
218 protected virtual void OFB (byte[] input, byte[] output)
220 throw new CryptographicException ("OFB isn't supported by the framework");
223 // Cipher Text Stealing (CTS)
224 protected virtual void CTS (byte[] input, byte[] output)
226 throw new CryptographicException ("CTS isn't supported by the framework");
228 #endif
230 private void CheckInput (byte[] inputBuffer, int inputOffset, int inputCount)
232 if (inputBuffer == null)
233 throw new ArgumentNullException ("inputBuffer");
234 if (inputOffset < 0)
235 throw new ArgumentOutOfRangeException ("inputOffset", "< 0");
236 if (inputCount < 0)
237 throw new ArgumentOutOfRangeException ("inputCount", "< 0");
238 // ordered to avoid possible integer overflow
239 if (inputOffset > inputBuffer.Length - inputCount)
240 throw new ArgumentException ("inputBuffer", Locale.GetText ("Overflow"));
243 // this method may get called MANY times so this is the one to optimize
244 public virtual int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
246 if (m_disposed)
247 throw new ObjectDisposedException ("Object is disposed");
248 CheckInput (inputBuffer, inputOffset, inputCount);
249 // check output parameters
250 if (outputBuffer == null)
251 throw new ArgumentNullException ("outputBuffer");
252 if (outputOffset < 0)
253 throw new ArgumentOutOfRangeException ("outputOffset", "< 0");
255 // ordered to avoid possible integer overflow
256 int len = outputBuffer.Length - inputCount - outputOffset;
257 #if MOONLIGHT
258 // only PKCS7 is supported Silverlight 2.0
259 if (KeepLastBlock) {
260 #else
261 if (!encrypt && (0 > len) && ((algo.Padding == PaddingMode.None) || (algo.Padding == PaddingMode.Zeros))) {
262 throw new CryptographicException ("outputBuffer", Locale.GetText ("Overflow"));
263 } else if (KeepLastBlock) {
264 #endif
265 if (0 > len + BlockSizeByte) {
266 throw new CryptographicException ("outputBuffer", Locale.GetText ("Overflow"));
268 } else {
269 if (0 > len) {
270 // there's a special case if this is the end of the decryption process
271 if (inputBuffer.Length - inputOffset - outputBuffer.Length == BlockSizeByte)
272 inputCount = outputBuffer.Length - outputOffset;
273 else
274 throw new CryptographicException ("outputBuffer", Locale.GetText ("Overflow"));
277 return InternalTransformBlock (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
280 private bool KeepLastBlock {
281 get {
282 #if MOONLIGHT
283 // only PKCS7 is supported Silverlight 2.0
284 return !encrypt;
285 #else
286 return ((!encrypt) && (algo.Padding != PaddingMode.None) && (algo.Padding != PaddingMode.Zeros));
287 #endif
291 private int InternalTransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
293 int offs = inputOffset;
294 int full;
296 // this way we don't do a modulo every time we're called
297 // and we may save a division
298 if (inputCount != BlockSizeByte) {
299 if ((inputCount % BlockSizeByte) != 0)
300 throw new CryptographicException ("Invalid input block size.");
302 full = inputCount / BlockSizeByte;
304 else
305 full = 1;
307 if (KeepLastBlock)
308 full--;
310 int total = 0;
312 if (lastBlock) {
313 Transform (workBuff, workout);
314 Buffer.BlockCopy (workout, 0, outputBuffer, outputOffset, BlockSizeByte);
315 outputOffset += BlockSizeByte;
316 total += BlockSizeByte;
317 lastBlock = false;
320 for (int i = 0; i < full; i++) {
321 Buffer.BlockCopy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
322 Transform (workBuff, workout);
323 Buffer.BlockCopy (workout, 0, outputBuffer, outputOffset, BlockSizeByte);
324 offs += BlockSizeByte;
325 outputOffset += BlockSizeByte;
326 total += BlockSizeByte;
329 if (KeepLastBlock) {
330 Buffer.BlockCopy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
331 lastBlock = true;
334 return total;
337 #if !MOONLIGHT
338 RandomNumberGenerator _rng;
340 private void Random (byte[] buffer, int start, int length)
342 if (_rng == null) {
343 _rng = RandomNumberGenerator.Create ();
345 byte[] random = new byte [length];
346 _rng.GetBytes (random);
347 Buffer.BlockCopy (random, 0, buffer, start, length);
350 private void ThrowBadPaddingException (PaddingMode padding, int length, int position)
352 string msg = String.Format (Locale.GetText ("Bad {0} padding."), padding);
353 if (length >= 0)
354 msg += String.Format (Locale.GetText (" Invalid length {0}."), length);
355 if (position >= 0)
356 msg += String.Format (Locale.GetText (" Error found at position {0}."), position);
357 throw new CryptographicException (msg);
359 #endif
361 private byte[] FinalEncrypt (byte[] inputBuffer, int inputOffset, int inputCount)
363 // are there still full block to process ?
364 int full = (inputCount / BlockSizeByte) * BlockSizeByte;
365 int rem = inputCount - full;
366 int total = full;
368 #if MOONLIGHT
369 // only PKCS7 is supported Silverlight 2.0
370 total += BlockSizeByte;
371 #else
372 switch (algo.Padding) {
373 case PaddingMode.ANSIX923:
374 case PaddingMode.ISO10126:
375 case PaddingMode.PKCS7:
376 // we need to add an extra block for padding
377 total += BlockSizeByte;
378 break;
379 default:
380 if (inputCount == 0)
381 return new byte [0];
382 if (rem != 0) {
383 if (algo.Padding == PaddingMode.None)
384 throw new CryptographicException ("invalid block length");
385 // zero padding the input (by adding a block for the partial data)
386 byte[] paddedInput = new byte [full + BlockSizeByte];
387 Buffer.BlockCopy (inputBuffer, inputOffset, paddedInput, 0, inputCount);
388 inputBuffer = paddedInput;
389 inputOffset = 0;
390 inputCount = paddedInput.Length;
391 total = inputCount;
393 break;
395 #endif // NET_2_1
397 byte[] res = new byte [total];
398 int outputOffset = 0;
400 // process all blocks except the last (final) block
401 while (total > BlockSizeByte) {
402 InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
403 inputOffset += BlockSizeByte;
404 outputOffset += BlockSizeByte;
405 total -= BlockSizeByte;
408 // now we only have a single last block to encrypt
409 byte padding = (byte) (BlockSizeByte - rem);
410 #if MOONLIGHT
411 // only PKCS7 is supported Silverlight 2.0
412 for (int i = res.Length; --i >= (res.Length - padding);)
413 res [i] = padding;
414 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
415 InternalTransformBlock (res, full, BlockSizeByte, res, full);
416 #else
417 switch (algo.Padding) {
418 case PaddingMode.ANSIX923:
419 // XX 00 00 00 00 00 00 07 (zero + padding length)
420 res [res.Length - 1] = padding;
421 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
422 // the last padded block will be transformed in-place
423 InternalTransformBlock (res, full, BlockSizeByte, res, full);
424 break;
425 case PaddingMode.ISO10126:
426 // XX 3F 52 2A 81 AB F7 07 (random + padding length)
427 Random (res, res.Length - padding, padding - 1);
428 res [res.Length - 1] = padding;
429 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
430 // the last padded block will be transformed in-place
431 InternalTransformBlock (res, full, BlockSizeByte, res, full);
432 break;
433 case PaddingMode.PKCS7:
434 // XX 07 07 07 07 07 07 07 (padding length)
435 for (int i = res.Length; --i >= (res.Length - padding);)
436 res [i] = padding;
437 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
438 // the last padded block will be transformed in-place
439 InternalTransformBlock (res, full, BlockSizeByte, res, full);
440 break;
441 default:
442 InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
443 break;
445 #endif // NET_2_1
446 return res;
449 private byte[] FinalDecrypt (byte[] inputBuffer, int inputOffset, int inputCount)
451 if ((inputCount % BlockSizeByte) > 0)
452 throw new CryptographicException ("Invalid input block size.");
454 int total = inputCount;
455 if (lastBlock)
456 total += BlockSizeByte;
458 byte[] res = new byte [total];
459 int outputOffset = 0;
461 while (inputCount > 0) {
462 int len = InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
463 inputOffset += BlockSizeByte;
464 outputOffset += len;
465 inputCount -= BlockSizeByte;
468 if (lastBlock) {
469 Transform (workBuff, workout);
470 Buffer.BlockCopy (workout, 0, res, outputOffset, BlockSizeByte);
471 outputOffset += BlockSizeByte;
472 lastBlock = false;
475 // total may be 0 (e.g. PaddingMode.None)
476 byte padding = ((total > 0) ? res [total - 1] : (byte) 0);
477 #if MOONLIGHT
478 // only PKCS7 is supported Silverlight 2.0
479 if ((padding == 0) || (padding > BlockSizeByte))
480 throw new CryptographicException (Locale.GetText ("Bad padding length."));
481 for (int i = padding - 1; i > 0; i--) {
482 if (res [total - 1 - i] != padding)
483 throw new CryptographicException (Locale.GetText ("Bad padding at position {0}.", i));
485 total -= padding;
486 #else
487 switch (algo.Padding) {
488 case PaddingMode.ANSIX923:
489 if ((padding == 0) || (padding > BlockSizeByte))
490 ThrowBadPaddingException (algo.Padding, padding, -1);
491 for (int i = padding - 1; i > 0; i--) {
492 if (res [total - 1 - i] != 0x00)
493 ThrowBadPaddingException (algo.Padding, -1, i);
495 total -= padding;
496 break;
497 case PaddingMode.ISO10126:
498 if ((padding == 0) || (padding > BlockSizeByte))
499 ThrowBadPaddingException (algo.Padding, padding, -1);
500 total -= padding;
501 break;
502 case PaddingMode.PKCS7:
503 if ((padding == 0) || (padding > BlockSizeByte))
504 ThrowBadPaddingException (algo.Padding, padding, -1);
505 for (int i = padding - 1; i > 0; i--) {
506 if (res [total - 1 - i] != padding)
507 ThrowBadPaddingException (algo.Padding, -1, i);
509 total -= padding;
510 break;
511 case PaddingMode.None: // nothing to do - it's a multiple of block size
512 case PaddingMode.Zeros: // nothing to do - user must unpad himself
513 break;
515 #endif // NET_2_1
517 // return output without padding
518 if (total > 0) {
519 byte[] data = new byte [total];
520 Buffer.BlockCopy (res, 0, data, 0, total);
521 // zeroize decrypted data (copy with padding)
522 Array.Clear (res, 0, res.Length);
523 return data;
525 else
526 return new byte [0];
529 public virtual byte[] TransformFinalBlock (byte[] inputBuffer, int inputOffset, int inputCount)
531 if (m_disposed)
532 throw new ObjectDisposedException ("Object is disposed");
533 CheckInput (inputBuffer, inputOffset, inputCount);
535 if (encrypt)
536 return FinalEncrypt (inputBuffer, inputOffset, inputCount);
537 else
538 return FinalDecrypt (inputBuffer, inputOffset, inputCount);