Merge changes, that were only made in corlib, back into Mono.Security
[mono-project.git] / mcs / class / Mono.Security / Mono.Security.Cryptography / SymmetricTransform.cs
blob221f0fb695da35c55adeb344ec1db015086e5867
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 protected int BlockSizeByte;
49 protected byte[] temp;
50 protected byte[] temp2;
51 private byte[] workBuff;
52 private byte[] workout;
53 protected PaddingMode padmode;
54 // Silverlight 2.0 does not support any feedback mode
55 protected int FeedBackByte;
56 private bool m_disposed = false;
57 protected bool lastBlock;
59 public SymmetricTransform (SymmetricAlgorithm symmAlgo, bool encryption, byte[] rgbIV)
61 algo = symmAlgo;
62 encrypt = encryption;
63 BlockSizeByte = (algo.BlockSize >> 3);
65 if (rgbIV == null) {
66 rgbIV = KeyBuilder.IV (BlockSizeByte);
67 } else {
68 rgbIV = (byte[]) rgbIV.Clone ();
70 // compare the IV length with the "currently selected" block size and *ignore* IV that are too big
71 if (rgbIV.Length < BlockSizeByte) {
72 string msg = Locale.GetText ("IV is too small ({0} bytes), it should be {1} bytes long.",
73 rgbIV.Length, BlockSizeByte);
74 throw new CryptographicException (msg);
76 padmode = algo.Padding;
77 // mode buffers
78 temp = new byte [BlockSizeByte];
79 Buffer.BlockCopy (rgbIV, 0, temp, 0, System.Math.Min (BlockSizeByte, rgbIV.Length));
80 temp2 = new byte [BlockSizeByte];
81 FeedBackByte = (algo.FeedbackSize >> 3);
82 // transform buffers
83 workBuff = new byte [BlockSizeByte];
84 workout = new byte [BlockSizeByte];
87 ~SymmetricTransform ()
89 Dispose (false);
92 void IDisposable.Dispose ()
94 Dispose (true);
95 GC.SuppressFinalize (this); // Finalization is now unnecessary
98 // MUST be overriden by classes using unmanaged ressources
99 // the override method must call the base class
100 protected virtual void Dispose (bool disposing)
102 if (!m_disposed) {
103 if (disposing) {
104 // dispose managed object: zeroize and free
105 Array.Clear (temp, 0, BlockSizeByte);
106 temp = null;
107 Array.Clear (temp2, 0, BlockSizeByte);
108 temp2 = null;
110 m_disposed = true;
114 public virtual bool CanTransformMultipleBlocks {
115 get { return true; }
118 public virtual bool CanReuseTransform {
119 get { return false; }
122 public virtual int InputBlockSize {
123 get { return BlockSizeByte; }
126 public virtual int OutputBlockSize {
127 get { return BlockSizeByte; }
130 // note: Each block MUST be BlockSizeValue in size!!!
131 // i.e. Any padding must be done before calling this method
132 protected virtual void Transform (byte[] input, byte[] output)
134 switch (algo.Mode) {
135 case CipherMode.ECB:
136 ECB (input, output);
137 break;
138 case CipherMode.CBC:
139 CBC (input, output);
140 break;
141 case CipherMode.CFB:
142 CFB (input, output);
143 break;
144 case CipherMode.OFB:
145 OFB (input, output);
146 break;
147 case CipherMode.CTS:
148 CTS (input, output);
149 break;
150 default:
151 throw new NotImplementedException ("Unkown CipherMode" + algo.Mode.ToString ());
155 // Electronic Code Book (ECB)
156 protected abstract void ECB (byte[] input, byte[] output);
158 // Cipher-Block-Chaining (CBC)
159 protected virtual void CBC (byte[] input, byte[] output)
161 if (encrypt) {
162 for (int i = 0; i < BlockSizeByte; i++)
163 temp[i] ^= input[i];
164 ECB (temp, output);
165 Buffer.BlockCopy (output, 0, temp, 0, BlockSizeByte);
167 else {
168 Buffer.BlockCopy (input, 0, temp2, 0, BlockSizeByte);
169 ECB (input, output);
170 for (int i = 0; i < BlockSizeByte; i++)
171 output[i] ^= temp[i];
172 Buffer.BlockCopy (temp2, 0, temp, 0, BlockSizeByte);
176 // Cipher-FeedBack (CFB)
177 // this is how *CryptoServiceProvider implements CFB
178 // only AesCryptoServiceProvider support CFB > 8
179 // RijndaelManaged is incompatible with this implementation (and overrides it in it's own transform)
180 protected virtual void CFB (byte[] input, byte[] output)
182 if (encrypt) {
183 for (int x = 0; x < BlockSizeByte; x++) {
184 // temp is first initialized with the IV
185 ECB (temp, temp2);
186 output [x] = (byte) (temp2 [0] ^ input [x]);
187 Buffer.BlockCopy (temp, 1, temp, 0, BlockSizeByte - 1);
188 Buffer.BlockCopy (output, x, temp, BlockSizeByte - 1, 1);
191 else {
192 for (int x = 0; x < BlockSizeByte; x++) {
193 // we do not really decrypt this data!
194 encrypt = true;
195 // temp is first initialized with the IV
196 ECB (temp, temp2);
197 encrypt = false;
199 Buffer.BlockCopy (temp, 1, temp, 0, BlockSizeByte - 1);
200 Buffer.BlockCopy (input, x, temp, BlockSizeByte - 1, 1);
201 output [x] = (byte) (temp2 [0] ^ input [x]);
206 // Output-FeedBack (OFB)
207 protected virtual void OFB (byte[] input, byte[] output)
209 throw new CryptographicException ("OFB isn't supported by the framework");
212 // Cipher Text Stealing (CTS)
213 protected virtual void CTS (byte[] input, byte[] output)
215 throw new CryptographicException ("CTS isn't supported by the framework");
218 private void CheckInput (byte[] inputBuffer, int inputOffset, int inputCount)
220 if (inputBuffer == null)
221 throw new ArgumentNullException ("inputBuffer");
222 if (inputOffset < 0)
223 throw new ArgumentOutOfRangeException ("inputOffset", "< 0");
224 if (inputCount < 0)
225 throw new ArgumentOutOfRangeException ("inputCount", "< 0");
226 // ordered to avoid possible integer overflow
227 if (inputOffset > inputBuffer.Length - inputCount)
228 throw new ArgumentException ("inputBuffer", Locale.GetText ("Overflow"));
231 // this method may get called MANY times so this is the one to optimize
232 public virtual int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
234 if (m_disposed)
235 throw new ObjectDisposedException ("Object is disposed");
236 CheckInput (inputBuffer, inputOffset, inputCount);
237 // check output parameters
238 if (outputBuffer == null)
239 throw new ArgumentNullException ("outputBuffer");
240 if (outputOffset < 0)
241 throw new ArgumentOutOfRangeException ("outputOffset", "< 0");
243 // ordered to avoid possible integer overflow
244 int len = outputBuffer.Length - inputCount - outputOffset;
245 if (!encrypt && (0 > len) && ((padmode == PaddingMode.None) || (padmode == PaddingMode.Zeros))) {
246 throw new CryptographicException ("outputBuffer", Locale.GetText ("Overflow"));
247 } else if (KeepLastBlock) {
248 if (0 > len + BlockSizeByte) {
249 throw new CryptographicException ("outputBuffer", Locale.GetText ("Overflow"));
251 } else {
252 if (0 > len) {
253 // there's a special case if this is the end of the decryption process
254 if (inputBuffer.Length - inputOffset - outputBuffer.Length == BlockSizeByte)
255 inputCount = outputBuffer.Length - outputOffset;
256 else
257 throw new CryptographicException ("outputBuffer", Locale.GetText ("Overflow"));
260 return InternalTransformBlock (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
263 private bool KeepLastBlock {
264 get {
265 return ((!encrypt) && (padmode != PaddingMode.None) && (padmode != PaddingMode.Zeros));
269 private int InternalTransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
271 int offs = inputOffset;
272 int full;
274 // this way we don't do a modulo every time we're called
275 // and we may save a division
276 if (inputCount != BlockSizeByte) {
277 if ((inputCount % BlockSizeByte) != 0)
278 throw new CryptographicException ("Invalid input block size.");
280 full = inputCount / BlockSizeByte;
282 else
283 full = 1;
285 if (KeepLastBlock)
286 full--;
288 int total = 0;
290 if (lastBlock) {
291 Transform (workBuff, workout);
292 Buffer.BlockCopy (workout, 0, outputBuffer, outputOffset, BlockSizeByte);
293 outputOffset += BlockSizeByte;
294 total += BlockSizeByte;
295 lastBlock = false;
298 for (int i = 0; i < full; i++) {
299 Buffer.BlockCopy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
300 Transform (workBuff, workout);
301 Buffer.BlockCopy (workout, 0, outputBuffer, outputOffset, BlockSizeByte);
302 offs += BlockSizeByte;
303 outputOffset += BlockSizeByte;
304 total += BlockSizeByte;
307 if (KeepLastBlock) {
308 Buffer.BlockCopy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
309 lastBlock = true;
312 return total;
315 RandomNumberGenerator _rng;
317 private void Random (byte[] buffer, int start, int length)
319 if (_rng == null) {
320 _rng = RandomNumberGenerator.Create ();
322 byte[] random = new byte [length];
323 _rng.GetBytes (random);
324 Buffer.BlockCopy (random, 0, buffer, start, length);
327 private void ThrowBadPaddingException (PaddingMode padding, int length, int position)
329 string msg = String.Format (Locale.GetText ("Bad {0} padding."), padding);
330 if (length >= 0)
331 msg += String.Format (Locale.GetText (" Invalid length {0}."), length);
332 if (position >= 0)
333 msg += String.Format (Locale.GetText (" Error found at position {0}."), position);
334 throw new CryptographicException (msg);
337 protected virtual byte[] FinalEncrypt (byte[] inputBuffer, int inputOffset, int inputCount)
339 // are there still full block to process ?
340 int full = (inputCount / BlockSizeByte) * BlockSizeByte;
341 int rem = inputCount - full;
342 int total = full;
344 switch (padmode) {
345 case PaddingMode.ANSIX923:
346 case PaddingMode.ISO10126:
347 case PaddingMode.PKCS7:
348 // we need to add an extra block for padding
349 total += BlockSizeByte;
350 break;
351 default:
352 if (inputCount == 0)
353 return new byte [0];
354 if (rem != 0) {
355 if (padmode == PaddingMode.None)
356 throw new CryptographicException ("invalid block length");
357 // zero padding the input (by adding a block for the partial data)
358 byte[] paddedInput = new byte [full + BlockSizeByte];
359 Buffer.BlockCopy (inputBuffer, inputOffset, paddedInput, 0, inputCount);
360 inputBuffer = paddedInput;
361 inputOffset = 0;
362 inputCount = paddedInput.Length;
363 total = inputCount;
365 break;
368 byte[] res = new byte [total];
369 int outputOffset = 0;
371 // process all blocks except the last (final) block
372 while (total > BlockSizeByte) {
373 InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
374 inputOffset += BlockSizeByte;
375 outputOffset += BlockSizeByte;
376 total -= BlockSizeByte;
379 // now we only have a single last block to encrypt
380 byte padding = (byte) (BlockSizeByte - rem);
381 switch (padmode) {
382 case PaddingMode.ANSIX923:
383 // XX 00 00 00 00 00 00 07 (zero + padding length)
384 res [res.Length - 1] = padding;
385 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
386 // the last padded block will be transformed in-place
387 InternalTransformBlock (res, full, BlockSizeByte, res, full);
388 break;
389 case PaddingMode.ISO10126:
390 // XX 3F 52 2A 81 AB F7 07 (random + padding length)
391 Random (res, res.Length - padding, padding - 1);
392 res [res.Length - 1] = padding;
393 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
394 // the last padded block will be transformed in-place
395 InternalTransformBlock (res, full, BlockSizeByte, res, full);
396 break;
397 case PaddingMode.PKCS7:
398 // XX 07 07 07 07 07 07 07 (padding length)
399 for (int i = res.Length; --i >= (res.Length - padding);)
400 res [i] = padding;
401 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
402 // the last padded block will be transformed in-place
403 InternalTransformBlock (res, full, BlockSizeByte, res, full);
404 break;
405 default:
406 InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
407 break;
409 return res;
412 protected virtual byte[] FinalDecrypt (byte[] inputBuffer, int inputOffset, int inputCount)
414 int full = inputCount;
415 int total = inputCount;
416 if (lastBlock)
417 total += BlockSizeByte;
419 byte[] res = new byte [total];
420 int outputOffset = 0;
422 while (full > 0) {
423 int len = InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
424 inputOffset += BlockSizeByte;
425 outputOffset += len;
426 full -= BlockSizeByte;
429 if (lastBlock) {
430 Transform (workBuff, workout);
431 Buffer.BlockCopy (workout, 0, res, outputOffset, BlockSizeByte);
432 outputOffset += BlockSizeByte;
433 lastBlock = false;
436 // total may be 0 (e.g. PaddingMode.None)
437 byte padding = ((total > 0) ? res [total - 1] : (byte) 0);
438 switch (padmode) {
439 case PaddingMode.ANSIX923:
440 if ((padding == 0) || (padding > BlockSizeByte))
441 ThrowBadPaddingException (padmode, padding, -1);
442 for (int i = padding - 1; i > 0; i--) {
443 if (res [total - 1 - i] != 0x00)
444 ThrowBadPaddingException (padmode, -1, i);
446 total -= padding;
447 break;
448 case PaddingMode.ISO10126:
449 if ((padding == 0) || (padding > BlockSizeByte))
450 ThrowBadPaddingException (padmode, padding, -1);
451 total -= padding;
452 break;
453 case PaddingMode.PKCS7:
454 if ((padding == 0) || (padding > BlockSizeByte))
455 ThrowBadPaddingException (padmode, padding, -1);
456 for (int i = padding - 1; i > 0; i--) {
457 if (res [total - 1 - i] != padding)
458 ThrowBadPaddingException (padmode, -1, i);
460 total -= padding;
461 break;
462 case PaddingMode.None: // nothing to do - it's a multiple of block size
463 case PaddingMode.Zeros: // nothing to do - user must unpad himself
464 break;
467 // return output without padding
468 if (total > 0) {
469 byte[] data = new byte [total];
470 Buffer.BlockCopy (res, 0, data, 0, total);
471 // zeroize decrypted data (copy with padding)
472 Array.Clear (res, 0, res.Length);
473 return data;
475 else
476 return new byte [0];
479 public virtual byte[] TransformFinalBlock (byte[] inputBuffer, int inputOffset, int inputCount)
481 if (m_disposed)
482 throw new ObjectDisposedException ("Object is disposed");
483 CheckInput (inputBuffer, inputOffset, inputCount);
485 if (encrypt)
486 return FinalEncrypt (inputBuffer, inputOffset, inputCount);
487 else
488 return FinalDecrypt (inputBuffer, inputOffset, inputCount);