1 // Fast, multi-block, ICryptoTransform implementation on top of CommonCrypto
4 // Sebastien Pouliot <sebastien@xamarin.com>
6 // Copyright 2012 Xamarin Inc.
9 using System.Security.Cryptography;
11 using Mono.Security.Cryptography;
13 namespace Crimson.CommonCrypto {
15 unsafe class FastCryptorTransform : ICryptoTransform {
24 public FastCryptorTransform (IntPtr cryptor, SymmetricAlgorithm algo, bool encryption, byte[] iv)
26 BlockSizeByte = (algo.BlockSize >> 3);
29 iv = KeyBuilder.IV (BlockSizeByte);
30 } else if (iv.Length < BlockSizeByte) {
31 string msg = String.Format ("IV is too small ({0} bytes), it should be {1} bytes long.",
32 iv.Length, BlockSizeByte);
33 throw new CryptographicException (msg);
38 padding = algo.Padding;
40 workBuff = new byte [BlockSizeByte];
43 ~FastCryptorTransform ()
48 public void Dispose ()
53 protected virtual void Dispose (bool disposing)
55 if (handle != IntPtr.Zero) {
56 Cryptor.CCCryptorRelease (handle);
59 GC.SuppressFinalize (this);
62 public virtual bool CanTransformMultipleBlocks {
66 public virtual bool CanReuseTransform {
70 public virtual int InputBlockSize {
71 get { return BlockSizeByte; }
74 public virtual int OutputBlockSize {
75 get { return BlockSizeByte; }
78 int Transform (byte[] input, int inputOffset, byte[] output, int outputOffset, int length)
80 IntPtr len = IntPtr.Zero;
81 IntPtr in_len = (IntPtr) length;
82 IntPtr out_len = (IntPtr) (output.Length - outputOffset);
83 fixed (byte* inputBuffer = &input [0])
84 fixed (byte* outputBuffer = &output [0]) {
85 CCCryptorStatus s = Cryptor.CCCryptorUpdate (handle, (IntPtr) (inputBuffer + inputOffset), in_len, (IntPtr) (outputBuffer + outputOffset), out_len, ref len);
86 if (s != CCCryptorStatus.Success)
87 throw new CryptographicException (s.ToString ());
92 private void CheckInput (byte[] inputBuffer, int inputOffset, int inputCount)
94 if (inputBuffer == null)
95 throw new ArgumentNullException ("inputBuffer");
97 throw new ArgumentOutOfRangeException ("inputOffset", "< 0");
99 throw new ArgumentOutOfRangeException ("inputCount", "< 0");
100 // ordered to avoid possible integer overflow
101 if (inputOffset > inputBuffer.Length - inputCount)
102 throw new ArgumentException ("inputBuffer", "Overflow");
105 // this method may get called MANY times so this is the one to optimize
106 public virtual int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
108 CheckInput (inputBuffer, inputOffset, inputCount);
109 // check output parameters
110 if (outputBuffer == null)
111 throw new ArgumentNullException ("outputBuffer");
112 if (outputOffset < 0)
113 throw new ArgumentOutOfRangeException ("outputOffset", "< 0");
115 // ordered to avoid possible integer overflow
116 int len = outputBuffer.Length - inputCount - outputOffset;
117 if (!encrypt && (0 > len) && ((padding == PaddingMode.None) || (padding == PaddingMode.Zeros))) {
118 throw new CryptographicException ("outputBuffer", "Overflow");
119 } else if (KeepLastBlock) {
120 if (0 > len + BlockSizeByte) {
121 throw new CryptographicException ("outputBuffer", "Overflow");
125 // there's a special case if this is the end of the decryption process
126 if (inputBuffer.Length - inputOffset - outputBuffer.Length == BlockSizeByte)
127 inputCount = outputBuffer.Length - outputOffset;
129 throw new CryptographicException ("outputBuffer", "Overflow");
132 return InternalTransformBlock (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
135 private bool KeepLastBlock {
137 return ((!encrypt) && (padding != PaddingMode.None) && (padding != PaddingMode.Zeros));
141 private int InternalTransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
143 int offs = inputOffset;
146 // this way we don't do a modulo every time we're called
147 // and we may save a division
148 if (inputCount != BlockSizeByte) {
149 if ((inputCount % BlockSizeByte) != 0)
150 throw new CryptographicException ("Invalid input block size.");
152 full = inputCount / BlockSizeByte;
162 Transform (workBuff, 0, outputBuffer, outputOffset, BlockSizeByte);
163 outputOffset += BlockSizeByte;
164 total += BlockSizeByte;
169 int length = full * BlockSizeByte;
170 Transform (inputBuffer, offs, outputBuffer, outputOffset, length);
172 outputOffset += length;
177 Buffer.BlockCopy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
184 private void Random (byte[] buffer, int start, int length)
186 byte[] random = new byte [length];
187 Cryptor.GetRandom (random);
188 Buffer.BlockCopy (random, 0, buffer, start, length);
191 private void ThrowBadPaddingException (PaddingMode padding, int length, int position)
193 string msg = String.Format ("Bad {0} padding.", padding);
195 msg += String.Format (" Invalid length {0}.", length);
197 msg += String.Format (" Error found at position {0}.", position);
198 throw new CryptographicException (msg);
201 private byte[] FinalEncrypt (byte[] inputBuffer, int inputOffset, int inputCount)
203 // are there still full block to process ?
204 int full = (inputCount / BlockSizeByte) * BlockSizeByte;
205 int rem = inputCount - full;
209 case PaddingMode.ANSIX923:
210 case PaddingMode.ISO10126:
211 case PaddingMode.PKCS7:
212 // we need to add an extra block for padding
213 total += BlockSizeByte;
219 if (padding == PaddingMode.None)
220 throw new CryptographicException ("invalid block length");
221 // zero padding the input (by adding a block for the partial data)
222 byte[] paddedInput = new byte [full + BlockSizeByte];
223 Buffer.BlockCopy (inputBuffer, inputOffset, paddedInput, 0, inputCount);
224 inputBuffer = paddedInput;
226 inputCount = paddedInput.Length;
232 byte[] res = new byte [total];
233 int outputOffset = 0;
235 // process all blocks except the last (final) block
236 if (total > BlockSizeByte) {
237 outputOffset = InternalTransformBlock (inputBuffer, inputOffset, total - BlockSizeByte, res, 0);
238 inputOffset += outputOffset;
241 // now we only have a single last block to encrypt
242 byte pad = (byte) (BlockSizeByte - rem);
244 case PaddingMode.ANSIX923:
245 // XX 00 00 00 00 00 00 07 (zero + padding length)
246 res [res.Length - 1] = pad;
247 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
248 // the last padded block will be transformed in-place
249 InternalTransformBlock (res, full, BlockSizeByte, res, full);
251 case PaddingMode.ISO10126:
252 // XX 3F 52 2A 81 AB F7 07 (random + padding length)
253 Random (res, res.Length - pad, pad - 1);
254 res [res.Length - 1] = pad;
255 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
256 // the last padded block will be transformed in-place
257 InternalTransformBlock (res, full, BlockSizeByte, res, full);
259 case PaddingMode.PKCS7:
260 // XX 07 07 07 07 07 07 07 (padding length)
261 for (int i = res.Length; --i >= (res.Length - pad);)
263 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
264 // the last padded block will be transformed in-place
265 InternalTransformBlock (res, full, BlockSizeByte, res, full);
268 InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
274 private byte[] FinalDecrypt (byte[] inputBuffer, int inputOffset, int inputCount)
276 if ((inputCount % BlockSizeByte) > 0)
277 throw new CryptographicException ("Invalid input block size.");
279 int total = inputCount;
281 total += BlockSizeByte;
283 byte[] res = new byte [total];
284 int outputOffset = 0;
287 outputOffset = InternalTransformBlock (inputBuffer, inputOffset, inputCount, res, 0);
290 Transform (workBuff, 0, res, outputOffset, BlockSizeByte);
291 outputOffset += BlockSizeByte;
295 // total may be 0 (e.g. PaddingMode.None)
296 byte pad = ((total > 0) ? res [total - 1] : (byte) 0);
298 case PaddingMode.ANSIX923:
299 if ((pad == 0) || (pad > BlockSizeByte))
300 ThrowBadPaddingException (padding, pad, -1);
301 for (int i = pad - 1; i > 0; i--) {
302 if (res [total - 1 - i] != 0x00)
303 ThrowBadPaddingException (padding, -1, i);
307 case PaddingMode.ISO10126:
308 if ((pad == 0) || (pad > BlockSizeByte))
309 ThrowBadPaddingException (padding, pad, -1);
312 case PaddingMode.PKCS7:
313 if ((pad == 0) || (pad > BlockSizeByte))
314 ThrowBadPaddingException (padding, pad, -1);
315 for (int i = pad - 1; i > 0; i--) {
316 if (res [total - 1 - i] != pad)
317 ThrowBadPaddingException (padding, -1, i);
321 case PaddingMode.None: // nothing to do - it's a multiple of block size
322 case PaddingMode.Zeros: // nothing to do - user must unpad himself
326 // return output without padding
328 byte[] data = new byte [total];
329 Buffer.BlockCopy (res, 0, data, 0, total);
330 // zeroize decrypted data (copy with padding)
331 Array.Clear (res, 0, res.Length);
338 public virtual byte[] TransformFinalBlock (byte[] inputBuffer, int inputOffset, int inputCount)
340 CheckInput (inputBuffer, inputOffset, inputCount);
343 return FinalEncrypt (inputBuffer, inputOffset, inputCount);
345 return FinalDecrypt (inputBuffer, inputOffset, inputCount);