2 // Mono.Security.Cryptography.SymmetricTransform implementation
5 // Thomas Neidhart (tome@sbox.tugraz.at)
6 // Sebastien Pouliot <sebastien@ximian.com>
8 // Portions (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
9 // Copyright (C) 2004-2005 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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
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...
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;
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;
58 public SymmetricTransform (SymmetricAlgorithm symmAlgo, bool encryption, byte[] rgbIV)
62 BlockSizeByte = (algo.BlockSize >> 3);
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;
71 workBuff = new byte [BlockSizeByte];
72 workout = new byte [BlockSizeByte];
75 ~SymmetricTransform ()
80 void IDisposable.Dispose ()
83 GC.SuppressFinalize (this); // Finalization is now unnecessary
86 // MUST be overriden by classes using unmanaged ressources
87 // the override method must call the base class
88 protected void Dispose (bool disposing)
92 // dispose managed object: zeroize and free
93 Array.Clear (temp, 0, BlockSizeByte);
95 Array.Clear (temp2, 0, BlockSizeByte);
102 public virtual bool CanTransformMultipleBlocks {
106 public bool CanReuseTransform {
107 get { return false; }
110 public virtual int InputBlockSize {
111 get { return BlockSizeByte; }
114 public virtual int OutputBlockSize {
115 get { return BlockSizeByte; }
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)
139 throw new NotImplementedException ("Unkown CipherMode" + algo.Mode.ToString ());
143 // Electronic Code Book (ECB)
144 protected abstract void ECB (byte[] input, byte[] output);
146 // Cipher-Block-Chaining (CBC)
147 protected virtual void CBC (byte[] input, byte[] output)
150 for (int i = 0; i < BlockSizeByte; i++)
153 Buffer.BlockCopy (output, 0, temp, 0, BlockSizeByte);
156 Buffer.BlockCopy (input, 0, temp2, 0, BlockSizeByte);
158 for (int i = 0; i < BlockSizeByte; i++)
159 output[i] ^= temp[i];
160 Buffer.BlockCopy (temp2, 0, temp, 0, BlockSizeByte);
164 // Cipher-FeedBack (CFB)
165 protected virtual void CFB (byte[] input, byte[] output)
168 for (int x = 0; x < FeedBackIter; x++) {
169 // temp is first initialized with the IV
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);
179 for (int x = 0; x < FeedBackIter; x++) {
180 // we do not really decrypt this data!
182 // temp is first initialized with the IV
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]);
194 // Output-FeedBack (OFB)
195 protected virtual void OFB (byte[] input, byte[] output)
197 throw new CryptographicException ("OFB isn't supported by the framework");
200 // Cipher Text Stealing (CTS)
201 protected virtual void CTS (byte[] input, byte[] output)
203 throw new CryptographicException ("CTS isn't supported by the framework");
206 private void CheckInput (byte[] inputBuffer, int inputOffset, int inputCount)
208 if (inputBuffer == null)
209 throw new ArgumentNullException ("inputBuffer");
211 throw new ArgumentOutOfRangeException ("inputOffset", "< 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"));
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)
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"));
234 return InternalTransformBlock (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
237 private bool KeepLastBlock {
239 return ((!encrypt) && (algo.Mode != CipherMode.ECB) && (algo.Padding != PaddingMode.None));
243 private int InternalTransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
245 int offs = inputOffset;
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.");
254 full = inputCount / BlockSizeByte;
265 Transform (workBuff, workout);
266 Buffer.BlockCopy (workout, 0, outputBuffer, outputOffset, BlockSizeByte);
267 outputOffset += BlockSizeByte;
268 total += BlockSizeByte;
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;
282 Buffer.BlockCopy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
290 RandomNumberGenerator _rng;
292 private void Random (byte[] buffer, int start, int length)
295 _rng = RandomNumberGenerator.Create ();
297 byte[] random = new byte [length];
298 _rng.GetBytes (random);
299 Buffer.BlockCopy (random, 0, buffer, start, length);
302 private void ThrowBadPaddingException (PaddingMode padding, int length, int position)
304 string msg = String.Format (Locale.GetText ("Bad {0} padding."), padding);
306 msg += String.Format (Locale.GetText (" Invalid length {0}."), length);
308 msg += String.Format (Locale.GetText (" Error found at position {0}."), position);
309 throw new CryptographicException (msg);
313 private byte[] FinalEncrypt (byte[] inputBuffer, int inputOffset, int inputCount)
315 // are there still full block to process ?
316 int full = (inputCount / BlockSizeByte) * BlockSizeByte;
317 int rem = inputCount - full;
320 switch (algo.Padding) {
322 case PaddingMode.ANSIX923:
323 case PaddingMode.ISO10126:
325 case PaddingMode.PKCS7:
326 // we need to add an extra block for padding
327 total += BlockSizeByte;
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;
340 inputCount = paddedInput.Length;
346 byte[] res = new byte [total];
347 int outputOffset = 0;
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;
357 // now we only have a single last block to encrypt
358 byte padding = (byte) (BlockSizeByte - rem);
359 switch (algo.Padding) {
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);
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);
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);)
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);
386 InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
392 private byte[] FinalDecrypt (byte[] inputBuffer, int inputOffset, int inputCount)
394 if ((inputCount % BlockSizeByte) > 0)
395 throw new CryptographicException ("Invalid input block size.");
397 int total = inputCount;
399 total += BlockSizeByte;
401 byte[] res = new byte [total];
402 int outputOffset = 0;
404 while (inputCount > 0) {
405 int len = InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
406 inputOffset += BlockSizeByte;
408 inputCount -= BlockSizeByte;
412 Transform (workBuff, workout);
413 Buffer.BlockCopy (workout, 0, res, outputOffset, BlockSizeByte);
414 outputOffset += BlockSizeByte;
418 // total may be 0 (e.g. PaddingMode.None)
419 byte padding = ((total > 0) ? res [total - 1] : (byte) 0);
420 switch (algo.Padding) {
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);
431 case PaddingMode.ISO10126:
432 if ((padding == 0) || (padding > BlockSizeByte))
433 ThrowBadPaddingException (algo.Padding, padding, -1);
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);
446 case PaddingMode.PKCS7:
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
455 // return output without padding
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);
467 public virtual byte[] TransformFinalBlock (byte[] inputBuffer, int inputOffset, int inputCount)
470 throw new ObjectDisposedException ("Object is disposed");
471 CheckInput (inputBuffer, inputOffset, inputCount);
474 return FinalEncrypt (inputBuffer, inputOffset, inputCount);
476 return FinalDecrypt (inputBuffer, inputOffset, inputCount);