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-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:
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
;
54 // Silverlight 2.0 does not support any feedback mode
55 private int FeedBackByte
;
56 private int FeedBackIter
;
58 private bool m_disposed
= false;
59 private bool lastBlock
;
61 public SymmetricTransform (SymmetricAlgorithm symmAlgo
, bool encryption
, byte[] rgbIV
)
65 BlockSizeByte
= (algo
.BlockSize
>> 3);
68 rgbIV
= KeyBuilder
.IV (BlockSizeByte
);
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
);
79 temp
= new byte [BlockSizeByte
];
80 Buffer
.BlockCopy (rgbIV
, 0, temp
, 0, System
.Math
.Min (BlockSizeByte
, rgbIV
.Length
));
81 temp2
= new byte [BlockSizeByte
];
83 FeedBackByte
= (algo
.FeedbackSize
>> 3);
84 if (FeedBackByte
!= 0)
85 FeedBackIter
= (int) BlockSizeByte
/ FeedBackByte
;
88 workBuff
= new byte [BlockSizeByte
];
89 workout
= new byte [BlockSizeByte
];
92 ~
SymmetricTransform ()
97 void IDisposable
.Dispose ()
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
)
109 // dispose managed object: zeroize and free
110 Array
.Clear (temp
, 0, BlockSizeByte
);
112 Array
.Clear (temp2
, 0, BlockSizeByte
);
119 public virtual bool CanTransformMultipleBlocks
{
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
)
140 // Silverlight 2.0 only supports CBC
160 throw new NotImplementedException ("Unkown CipherMode" + algo
.Mode
.ToString ());
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
)
172 for (int i
= 0; i
< BlockSizeByte
; i
++)
175 Buffer
.BlockCopy (output
, 0, temp
, 0, BlockSizeByte
);
178 Buffer
.BlockCopy (input
, 0, temp2
, 0, BlockSizeByte
);
180 for (int i
= 0; i
< BlockSizeByte
; i
++)
181 output
[i
] ^
= temp
[i
];
182 Buffer
.BlockCopy (temp2
, 0, temp
, 0, BlockSizeByte
);
187 // Cipher-FeedBack (CFB)
188 protected virtual void CFB (byte[] input
, byte[] output
)
191 for (int x
= 0; x
< FeedBackIter
; x
++) {
192 // temp is first initialized with the IV
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
);
202 for (int x
= 0; x
< FeedBackIter
; x
++) {
203 // we do not really decrypt this data!
205 // temp is first initialized with the IV
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");
230 private void CheckInput (byte[] inputBuffer
, int inputOffset
, int inputCount
)
232 if (inputBuffer
== null)
233 throw new ArgumentNullException ("inputBuffer");
235 throw new ArgumentOutOfRangeException ("inputOffset", "< 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
)
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
;
258 // only PKCS7 is supported Silverlight 2.0
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
) {
265 if (0 > len
+ BlockSizeByte
) {
266 throw new CryptographicException ("outputBuffer", Locale
.GetText ("Overflow"));
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
;
274 throw new CryptographicException ("outputBuffer", Locale
.GetText ("Overflow"));
277 return InternalTransformBlock (inputBuffer
, inputOffset
, inputCount
, outputBuffer
, outputOffset
);
280 private bool KeepLastBlock
{
283 // only PKCS7 is supported Silverlight 2.0
286 return ((!encrypt
) && (algo
.Padding
!= PaddingMode
.None
) && (algo
.Padding
!= PaddingMode
.Zeros
));
291 private int InternalTransformBlock (byte[] inputBuffer
, int inputOffset
, int inputCount
, byte[] outputBuffer
, int outputOffset
)
293 int offs
= inputOffset
;
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
;
313 Transform (workBuff
, workout
);
314 Buffer
.BlockCopy (workout
, 0, outputBuffer
, outputOffset
, BlockSizeByte
);
315 outputOffset
+= BlockSizeByte
;
316 total
+= BlockSizeByte
;
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
;
330 Buffer
.BlockCopy (inputBuffer
, offs
, workBuff
, 0, BlockSizeByte
);
338 RandomNumberGenerator _rng
;
340 private void Random (byte[] buffer
, int start
, int length
)
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
);
354 msg
+= String
.Format (Locale
.GetText (" Invalid length {0}."), length
);
356 msg
+= String
.Format (Locale
.GetText (" Error found at position {0}."), position
);
357 throw new CryptographicException (msg
);
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
;
369 // only PKCS7 is supported Silverlight 2.0
370 total
+= BlockSizeByte
;
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
;
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
;
390 inputCount
= paddedInput
.Length
;
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
);
411 // only PKCS7 is supported Silverlight 2.0
412 for (int i
= res
.Length
; --i
>= (res
.Length
- padding
);)
414 Buffer
.BlockCopy (inputBuffer
, inputOffset
, res
, full
, rem
);
415 InternalTransformBlock (res
, full
, BlockSizeByte
, res
, full
);
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
);
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
);
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
);)
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
);
442 InternalTransformBlock (inputBuffer
, inputOffset
, BlockSizeByte
, res
, outputOffset
);
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
;
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
;
465 inputCount
-= BlockSizeByte
;
469 Transform (workBuff
, workout
);
470 Buffer
.BlockCopy (workout
, 0, res
, outputOffset
, BlockSizeByte
);
471 outputOffset
+= BlockSizeByte
;
475 // total may be 0 (e.g. PaddingMode.None)
476 byte padding
= ((total
> 0) ? res
[total
- 1] : (byte) 0);
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
));
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
);
497 case PaddingMode
.ISO10126
:
498 if ((padding
== 0) || (padding
> BlockSizeByte
))
499 ThrowBadPaddingException (algo
.Padding
, padding
, -1);
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
);
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
517 // return output without padding
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
);
529 public virtual byte[] TransformFinalBlock (byte[] inputBuffer
, int inputOffset
, int inputCount
)
532 throw new ObjectDisposedException ("Object is disposed");
533 CheckInput (inputBuffer
, inputOffset
, inputCount
);
536 return FinalEncrypt (inputBuffer
, inputOffset
, inputCount
);
538 return FinalDecrypt (inputBuffer
, inputOffset
, inputCount
);