1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
|
// Fast, multi-block, ICryptoTransform implementation on top of CommonCrypto
//
// Authors:
// Sebastien Pouliot <sebastien@xamarin.com>
//
// Copyright 2012 Xamarin Inc.
using System;
using System.Security.Cryptography;
using Mono.Security.Cryptography;
namespace Crimson.CommonCrypto {
unsafe class FastCryptorTransform : ICryptoTransform {
IntPtr handle;
bool encrypt;
int BlockSizeByte;
byte[] workBuff;
bool lastBlock;
PaddingMode padding;
public FastCryptorTransform (IntPtr cryptor, SymmetricAlgorithm algo, bool encryption, byte[] iv)
{
BlockSizeByte = (algo.BlockSize >> 3);
if (iv == null) {
iv = KeyBuilder.IV (BlockSizeByte);
} else if (iv.Length < BlockSizeByte) {
string msg = String.Format ("IV is too small ({0} bytes), it should be {1} bytes long.",
iv.Length, BlockSizeByte);
throw new CryptographicException (msg);
}
handle = cryptor;
encrypt = encryption;
padding = algo.Padding;
// transform buffer
workBuff = new byte [BlockSizeByte];
}
~FastCryptorTransform ()
{
Dispose (false);
}
public void Dispose ()
{
Dispose (true);
}
protected virtual void Dispose (bool disposing)
{
if (handle != IntPtr.Zero) {
Cryptor.CCCryptorRelease (handle);
handle = IntPtr.Zero;
}
GC.SuppressFinalize (this);
}
public virtual bool CanTransformMultipleBlocks {
get { return true; }
}
public virtual bool CanReuseTransform {
get { return false; }
}
public virtual int InputBlockSize {
get { return BlockSizeByte; }
}
public virtual int OutputBlockSize {
get { return BlockSizeByte; }
}
int Transform (byte[] input, int inputOffset, byte[] output, int outputOffset, int length)
{
IntPtr len = IntPtr.Zero;
IntPtr in_len = (IntPtr) length;
IntPtr out_len = (IntPtr) (output.Length - outputOffset);
fixed (byte* inputBuffer = &input [0])
fixed (byte* outputBuffer = &output [0]) {
CCCryptorStatus s = Cryptor.CCCryptorUpdate (handle, (IntPtr) (inputBuffer + inputOffset), in_len, (IntPtr) (outputBuffer + outputOffset), out_len, ref len);
if (s != CCCryptorStatus.Success)
throw new CryptographicException (s.ToString ());
}
return (int) len;
}
private void CheckInput (byte[] inputBuffer, int inputOffset, int inputCount)
{
if (inputBuffer == null)
throw new ArgumentNullException ("inputBuffer");
if (inputOffset < 0)
throw new ArgumentOutOfRangeException ("inputOffset", "< 0");
if (inputCount < 0)
throw new ArgumentOutOfRangeException ("inputCount", "< 0");
// ordered to avoid possible integer overflow
if (inputOffset > inputBuffer.Length - inputCount)
throw new ArgumentException ("inputBuffer", "Overflow");
}
// this method may get called MANY times so this is the one to optimize
public virtual int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
{
CheckInput (inputBuffer, inputOffset, inputCount);
// check output parameters
if (outputBuffer == null)
throw new ArgumentNullException ("outputBuffer");
if (outputOffset < 0)
throw new ArgumentOutOfRangeException ("outputOffset", "< 0");
// ordered to avoid possible integer overflow
int len = outputBuffer.Length - inputCount - outputOffset;
if (!encrypt && (0 > len) && ((padding == PaddingMode.None) || (padding == PaddingMode.Zeros))) {
throw new CryptographicException ("outputBuffer", "Overflow");
} else if (KeepLastBlock) {
if (0 > len + BlockSizeByte) {
throw new CryptographicException ("outputBuffer", "Overflow");
}
} else {
if (0 > len) {
// there's a special case if this is the end of the decryption process
if (inputBuffer.Length - inputOffset - outputBuffer.Length == BlockSizeByte)
inputCount = outputBuffer.Length - outputOffset;
else
throw new CryptographicException ("outputBuffer", "Overflow");
}
}
return InternalTransformBlock (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
}
private bool KeepLastBlock {
get {
return ((!encrypt) && (padding != PaddingMode.None) && (padding != PaddingMode.Zeros));
}
}
private int InternalTransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
{
int offs = inputOffset;
int full;
// this way we don't do a modulo every time we're called
// and we may save a division
if (inputCount != BlockSizeByte) {
if ((inputCount % BlockSizeByte) != 0)
throw new CryptographicException ("Invalid input block size.");
full = inputCount / BlockSizeByte;
} else
full = 1;
if (KeepLastBlock)
full--;
int total = 0;
if (lastBlock) {
Transform (workBuff, 0, outputBuffer, outputOffset, BlockSizeByte);
outputOffset += BlockSizeByte;
total += BlockSizeByte;
lastBlock = false;
}
if (full > 0) {
int length = full * BlockSizeByte;
Transform (inputBuffer, offs, outputBuffer, outputOffset, length);
offs += length;
outputOffset += length;
total += length;
}
if (KeepLastBlock) {
Buffer.BlockCopy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
lastBlock = true;
}
return total;
}
private void Random (byte[] buffer, int start, int length)
{
byte[] random = new byte [length];
Cryptor.GetRandom (random);
Buffer.BlockCopy (random, 0, buffer, start, length);
}
private void ThrowBadPaddingException (PaddingMode padding, int length, int position)
{
string msg = String.Format ("Bad {0} padding.", padding);
if (length >= 0)
msg += String.Format (" Invalid length {0}.", length);
if (position >= 0)
msg += String.Format (" Error found at position {0}.", position);
throw new CryptographicException (msg);
}
private byte[] FinalEncrypt (byte[] inputBuffer, int inputOffset, int inputCount)
{
// are there still full block to process ?
int full = (inputCount / BlockSizeByte) * BlockSizeByte;
int rem = inputCount - full;
int total = full;
switch (padding) {
case PaddingMode.ANSIX923:
case PaddingMode.ISO10126:
case PaddingMode.PKCS7:
// we need to add an extra block for padding
total += BlockSizeByte;
break;
default:
if (inputCount == 0)
return new byte [0];
if (rem != 0) {
if (padding == PaddingMode.None)
throw new CryptographicException ("invalid block length");
// zero padding the input (by adding a block for the partial data)
byte[] paddedInput = new byte [full + BlockSizeByte];
Buffer.BlockCopy (inputBuffer, inputOffset, paddedInput, 0, inputCount);
inputBuffer = paddedInput;
inputOffset = 0;
inputCount = paddedInput.Length;
total = inputCount;
}
break;
}
byte[] res = new byte [total];
int outputOffset = 0;
// process all blocks except the last (final) block
if (total > BlockSizeByte) {
outputOffset = InternalTransformBlock (inputBuffer, inputOffset, total - BlockSizeByte, res, 0);
inputOffset += outputOffset;
}
// now we only have a single last block to encrypt
byte pad = (byte) (BlockSizeByte - rem);
switch (padding) {
case PaddingMode.ANSIX923:
// XX 00 00 00 00 00 00 07 (zero + padding length)
res [res.Length - 1] = pad;
Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
// the last padded block will be transformed in-place
InternalTransformBlock (res, full, BlockSizeByte, res, full);
break;
case PaddingMode.ISO10126:
// XX 3F 52 2A 81 AB F7 07 (random + padding length)
Random (res, res.Length - pad, pad - 1);
res [res.Length - 1] = pad;
Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
// the last padded block will be transformed in-place
InternalTransformBlock (res, full, BlockSizeByte, res, full);
break;
case PaddingMode.PKCS7:
// XX 07 07 07 07 07 07 07 (padding length)
for (int i = res.Length; --i >= (res.Length - pad);)
res [i] = pad;
Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
// the last padded block will be transformed in-place
InternalTransformBlock (res, full, BlockSizeByte, res, full);
break;
default:
InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
break;
}
return res;
}
private byte[] FinalDecrypt (byte[] inputBuffer, int inputOffset, int inputCount)
{
if ((inputCount % BlockSizeByte) > 0)
throw new CryptographicException ("Invalid input block size.");
int total = inputCount;
if (lastBlock)
total += BlockSizeByte;
byte[] res = new byte [total];
int outputOffset = 0;
if (inputCount > 0)
outputOffset = InternalTransformBlock (inputBuffer, inputOffset, inputCount, res, 0);
if (lastBlock) {
Transform (workBuff, 0, res, outputOffset, BlockSizeByte);
outputOffset += BlockSizeByte;
lastBlock = false;
}
// total may be 0 (e.g. PaddingMode.None)
byte pad = ((total > 0) ? res [total - 1] : (byte) 0);
switch (padding) {
case PaddingMode.ANSIX923:
if ((pad == 0) || (pad > BlockSizeByte))
ThrowBadPaddingException (padding, pad, -1);
for (int i = pad - 1; i > 0; i--) {
if (res [total - 1 - i] != 0x00)
ThrowBadPaddingException (padding, -1, i);
}
total -= pad;
break;
case PaddingMode.ISO10126:
if ((pad == 0) || (pad > BlockSizeByte))
ThrowBadPaddingException (padding, pad, -1);
total -= pad;
break;
case PaddingMode.PKCS7:
if ((pad == 0) || (pad > BlockSizeByte))
ThrowBadPaddingException (padding, pad, -1);
for (int i = pad - 1; i > 0; i--) {
if (res [total - 1 - i] != pad)
ThrowBadPaddingException (padding, -1, i);
}
total -= pad;
break;
case PaddingMode.None: // nothing to do - it's a multiple of block size
case PaddingMode.Zeros: // nothing to do - user must unpad himself
break;
}
// return output without padding
if (total > 0) {
byte[] data = new byte [total];
Buffer.BlockCopy (res, 0, data, 0, total);
// zeroize decrypted data (copy with padding)
Array.Clear (res, 0, res.Length);
return data;
}
else
return new byte [0];
}
public virtual byte[] TransformFinalBlock (byte[] inputBuffer, int inputOffset, int inputCount)
{
CheckInput (inputBuffer, inputOffset, inputCount);
if (encrypt)
return FinalEncrypt (inputBuffer, inputOffset, inputCount);
else
return FinalDecrypt (inputBuffer, inputOffset, inputCount);
}
}
}
|