3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 using System.Diagnostics;
9 using System.Diagnostics.CodeAnalysis;
10 using System.Runtime.InteropServices;
11 using System.Diagnostics.Contracts;
12 using Microsoft.Win32.SafeHandles;
14 namespace System.Security.Cryptography {
16 /// Flag to indicate if we're doing encryption or decryption
18 internal enum EncryptionMode {
24 /// Implementation of a generic CAPI symmetric encryption algorithm. Concrete SymmetricAlgorithm classes
25 /// which wrap CAPI implementations can use this class to perform the actual encryption work.
27 internal sealed class CapiSymmetricAlgorithm : ICryptoTransform {
28 private int m_blockSize;
29 private byte[] m_depadBuffer;
30 private EncryptionMode m_encryptionMode;
32 private SafeCapiKeyHandle m_key;
33 private PaddingMode m_paddingMode;
35 private SafeCspHandle m_provider;
37 [System.Security.SecurityCritical]
38 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Reviewed")]
39 public CapiSymmetricAlgorithm(int blockSize,
41 SafeCspHandle provider,
42 SafeCapiKeyHandle key,
44 CipherMode cipherMode,
45 PaddingMode paddingMode,
46 EncryptionMode encryptionMode) {
47 Contract.Requires(0 < blockSize && blockSize % 8 == 0);
48 Contract.Requires(0 <= feedbackSize);
49 Contract.Requires(provider != null && !provider.IsInvalid && !provider.IsClosed);
50 Contract.Requires(key != null && !key.IsInvalid && !key.IsClosed);
51 Contract.Ensures(m_provider != null && !m_provider.IsInvalid && !m_provider.IsClosed);
53 m_blockSize = blockSize;
54 m_encryptionMode = encryptionMode;
55 m_paddingMode = paddingMode;
56 m_provider = provider.Duplicate();
57 m_key = SetupKey(key, ProcessIV(iv, blockSize, cipherMode), cipherMode, feedbackSize);
60 public bool CanReuseTransform {
64 public bool CanTransformMultipleBlocks {
69 // Note: both input and output block size are in bytes rather than bits
72 public int InputBlockSize {
74 get { return m_blockSize / 8; }
77 public int OutputBlockSize {
78 get { return m_blockSize / 8; }
81 [SecuritySafeCritical]
82 public void Dispose() {
83 Contract.Ensures(m_key == null || m_key.IsClosed);
84 Contract.Ensures(m_provider == null || m_provider.IsClosed);
85 Contract.Ensures(m_depadBuffer == null);
91 if (m_provider != null) {
95 if (m_depadBuffer != null) {
96 Array.Clear(m_depadBuffer, 0, m_depadBuffer.Length);
102 [SecuritySafeCritical]
103 private int DecryptBlocks(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) {
104 Contract.Requires(m_key != null);
105 Contract.Requires(inputBuffer != null && inputCount <= inputBuffer.Length - inputOffset);
106 Contract.Requires(inputOffset >= 0);
107 Contract.Requires(inputCount > 0 && inputCount % InputBlockSize == 0);
108 Contract.Requires(outputBuffer != null && inputCount <= outputBuffer.Length - outputOffset);
109 Contract.Requires(inputOffset >= 0);
110 Contract.Requires(m_depadBuffer == null || (m_paddingMode != PaddingMode.None && m_paddingMode != PaddingMode.Zeros));
111 Contract.Ensures(Contract.Result<int>() >= 0);
114 // If we're decrypting, it's possible to be called with the last blocks of the data, and then
115 // have TransformFinalBlock called with an empty array. Since we don't know if this is the case,
116 // we won't decrypt the last block of the input until either TransformBlock or
117 // TransformFinalBlock is next called.
119 // We don't need to do this for PaddingMode.None because there is no padding to strip, and
120 // we also don't do this for PaddingMode.Zeros since there is no way for us to tell if the
121 // zeros at the end of a block are part of the plaintext or the padding.
124 int decryptedBytes = 0;
125 if (m_paddingMode != PaddingMode.None && m_paddingMode != PaddingMode.Zeros) {
126 // If we have data saved from a previous call, decrypt that into the output first
127 if (m_depadBuffer != null) {
128 int depadDecryptLength = RawDecryptBlocks(m_depadBuffer, 0, m_depadBuffer.Length);
129 Buffer.BlockCopy(m_depadBuffer, 0, outputBuffer, outputOffset, depadDecryptLength);
130 Array.Clear(m_depadBuffer, 0, m_depadBuffer.Length);
131 outputOffset += depadDecryptLength;
132 decryptedBytes += depadDecryptLength;
135 m_depadBuffer = new byte[InputBlockSize];
138 // Copy the last block of the input buffer into the depad buffer
139 Debug.Assert(inputCount >= m_depadBuffer.Length, "inputCount >= m_depadBuffer.Length");
140 Buffer.BlockCopy(inputBuffer,
141 inputOffset + inputCount - m_depadBuffer.Length,
144 m_depadBuffer.Length);
145 inputCount -= m_depadBuffer.Length;
146 Debug.Assert(inputCount % InputBlockSize == 0, "Did not remove whole blocks for depadding");
149 // CryptDecrypt operates in place, so if after reserving the depad buffer there's still data to decrypt,
150 // make a copy of that in the output buffer to work on.
151 if (inputCount > 0) {
152 Buffer.BlockCopy(inputBuffer, inputOffset, outputBuffer, outputOffset, inputCount);
153 decryptedBytes += RawDecryptBlocks(outputBuffer, outputOffset, inputCount);
156 return decryptedBytes;
160 /// Remove the padding from the last blocks being decrypted
162 private byte[] DepadBlock(byte[] block, int offset, int count) {
163 Contract.Requires(block != null && count >= block.Length - offset);
164 Contract.Requires(0 <= offset);
165 Contract.Requires(0 <= count);
166 Contract.Ensures(Contract.Result<byte[]>() != null && Contract.Result<byte[]>().Length <= block.Length);
170 // See code:System.Security.Cryptography.CapiSymmetricAlgorithm.PadBlock for a description of the
172 switch (m_paddingMode) {
173 case PaddingMode.ANSIX923:
174 padBytes = block[offset + count - 1];
176 // Verify the amount of padding is reasonable
177 if (padBytes <= 0 || padBytes > InputBlockSize) {
178 throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding));
181 // Verify that all the padding bytes are 0s
182 for (int i = offset + count - padBytes; i < offset + count - 1; i++) {
184 throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding));
190 case PaddingMode.ISO10126:
191 padBytes = block[offset + count - 1];
193 // Verify the amount of padding is reasonable
194 if (padBytes <= 0 || padBytes > InputBlockSize) {
195 throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding));
198 // Since the padding consists of random bytes, we cannot verify the actual pad bytes themselves
201 case PaddingMode.PKCS7:
202 padBytes = block[offset + count - 1];
204 // Verify the amount of padding is reasonable
205 if (padBytes <= 0 || padBytes > InputBlockSize) {
206 throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding));
209 // Verify all the padding bytes match the amount of padding
210 for (int i = offset + count - padBytes; i < offset + count; i++) {
211 if (block[i] != padBytes) {
212 throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding));
218 // We cannot remove Zeros padding because we don't know if the zeros at the end of the block
219 // belong to the padding or the plaintext itself.
220 case PaddingMode.Zeros:
221 case PaddingMode.None:
226 throw new CryptographicException(SR.GetString(SR.Cryptography_UnknownPaddingMode));
229 // Copy everything but the padding to the output
230 byte[] depadded = new byte[count - padBytes];
231 Buffer.BlockCopy(block, offset, depadded, 0, depadded.Length);
236 /// Encrypt blocks of plaintext
239 private int EncryptBlocks(byte[] buffer, int offset, int count) {
240 Contract.Requires(m_key != null);
241 Contract.Requires(buffer != null && count <= buffer.Length - offset);
242 Contract.Requires(offset >= 0);
243 Contract.Requires(count > 0 && count % InputBlockSize == 0);
244 Contract.Ensures(Contract.Result<int>() >= 0);
247 // Do the encryption. Note that CapiSymmetricAlgorithm will do all padding itself since the CLR
248 // supports padding modes that CAPI does not, so we will always tell CAPI that we are not working
249 // with the final block.
252 int dataLength = count;
254 fixed (byte* pData = &buffer[offset]) {
255 if (!CapiNative.UnsafeNativeMethods.CryptEncrypt(m_key,
256 SafeCapiHashHandle.InvalidHandle,
261 buffer.Length - offset)) {
262 throw new CryptographicException(Marshal.GetLastWin32Error());
271 /// Calculate the padding for a block of data
273 [SecuritySafeCritical]
274 private byte[] PadBlock(byte[] block, int offset, int count) {
275 Contract.Requires(m_provider != null);
276 Contract.Requires(block != null && count <= block.Length - offset);
277 Contract.Requires(0 <= offset);
278 Contract.Requires(0 <= count);
279 Contract.Ensures(Contract.Result<byte[]>() != null && Contract.Result<byte[]>().Length % InputBlockSize == 0);
281 byte[] result = null;
282 int padBytes = InputBlockSize - (count % InputBlockSize);
284 switch (m_paddingMode) {
285 // ANSI padding fills the blocks with zeros and adds the total number of padding bytes as
286 // the last pad byte, adding an extra block if the last block is complete.
288 // x 00 00 00 00 00 00 07
289 case PaddingMode.ANSIX923:
290 result = new byte[count + padBytes];
291 Buffer.BlockCopy(block, 0, result, 0, count);
292 result[result.Length - 1] = (byte)padBytes;
295 // ISO padding fills the blocks up with random bytes and adds the total number of padding
296 // bytes as the last pad byte, adding an extra block if the last block is complete.
298 // xx rr rr rr rr rr rr 07
299 case PaddingMode.ISO10126:
300 result = new byte[count + padBytes];
302 CapiNative.UnsafeNativeMethods.CryptGenRandom(m_provider, result.Length - 1, result);
303 Buffer.BlockCopy(block, 0, result, 0, count);
304 result[result.Length - 1] = (byte)padBytes;
307 // No padding requires that the input already be a multiple of the block size
308 case PaddingMode.None:
309 if (count % InputBlockSize != 0) {
310 throw new CryptographicException(SR.GetString(SR.Cryptography_PartialBlock));
313 result = new byte[count];
314 Buffer.BlockCopy(block, offset, result, 0, result.Length);
317 // PKCS padding fills the blocks up with bytes containing the total number of padding bytes
318 // used, adding an extra block if the last block is complete.
320 // xx xx 06 06 06 06 06 06
321 case PaddingMode.PKCS7:
322 result = new byte[count + padBytes];
323 Buffer.BlockCopy(block, offset, result, 0, count);
325 for (int i = count; i < result.Length; i++) {
326 result[i] = (byte)padBytes;
330 // Zeros padding fills the last partial block with zeros, and does not add a new block to
331 // the end if the last block is already complete.
333 // xx 00 00 00 00 00 00 00
334 case PaddingMode.Zeros:
335 if (padBytes == InputBlockSize) {
339 result = new byte[count + padBytes];
340 Buffer.BlockCopy(block, offset, result, 0, count);
344 throw new CryptographicException(SR.GetString(SR.Cryptography_UnknownPaddingMode));
351 /// Validate and transform the user's IV into one that we will pass on to CAPI
353 /// If we have an IV, make a copy of it so that it doesn't get modified while we're using it. If
354 /// not, and we're not in ECB mode then throw an error, since we cannot decrypt without the IV, and
355 /// generating a random IV to encrypt with would lead to data which is not decryptable.
357 /// For compatibility with v1.x, we accept IVs which are longer than the block size, and truncate
358 /// them back. We will reject an IV which is smaller than the block size however.
360 private static byte[] ProcessIV(byte[] iv, int blockSize, CipherMode cipherMode) {
361 Contract.Requires(blockSize % 8 == 0);
362 Contract.Ensures(cipherMode == CipherMode.ECB ||
363 (Contract.Result<byte[]>() != null && Contract.Result<byte[]>().Length == blockSize / 8));
365 byte[] realIV = null;
368 if (blockSize / 8 <= iv.Length) {
369 realIV = new byte[blockSize / 8];
370 Buffer.BlockCopy(iv, 0, realIV, 0, realIV.Length);
373 throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidIVSize));
376 else if (cipherMode != CipherMode.ECB) {
377 throw new CryptographicException(SR.GetString(SR.Cryptography_MissingIV));
384 /// Do a direct decryption of the ciphertext blocks. This method should not be called from anywhere
385 /// but DecryptBlocks or TransformFinalBlock since it does not account for the depadding buffer and
386 /// direct use could lead to incorrect decryption values.
389 private int RawDecryptBlocks(byte[] buffer, int offset, int count) {
390 Contract.Requires(m_key != null);
391 Contract.Requires(buffer != null && count <= buffer.Length - offset);
392 Contract.Requires(offset >= 0);
393 Contract.Requires(count > 0 && count % InputBlockSize == 0);
394 Contract.Ensures(Contract.Result<int>() >= 0);
397 // Do the decryption. Note that CapiSymmetricAlgorithm will do all padding itself since the CLR
398 // supports padding modes that CAPI does not, so we will always tell CAPI that we are not working
399 // with the final block.
402 int dataLength = count;
404 fixed (byte* pData = &buffer[offset]) {
405 if (!CapiNative.UnsafeNativeMethods.CryptDecrypt(m_key,
406 SafeCapiHashHandle.InvalidHandle,
411 throw new CryptographicException(Marshal.GetLastWin32Error());
420 /// Reset the state of the algorithm so that it can begin processing a new message
422 [SecuritySafeCritical]
423 private void Reset() {
424 Contract.Requires(m_key != null);
425 Contract.Ensures(m_depadBuffer == null);
428 // CryptEncrypt / CryptDecrypt must be called with the Final parameter set to true so that
429 // their internal state is reset. Since we do all padding by hand, this isn't done by
430 // TransformFinalBlock so is done on an empty buffer here.
433 byte[] buffer = new byte[OutputBlockSize];
436 fixed (byte* pBuffer = buffer) {
437 if (m_encryptionMode == EncryptionMode.Encrypt) {
438 CapiNative.UnsafeNativeMethods.CryptEncrypt(m_key,
439 SafeCapiHashHandle.InvalidHandle,
447 CapiNative.UnsafeNativeMethods.CryptDecrypt(m_key,
448 SafeCapiHashHandle.InvalidHandle,
457 // Also erase the depadding buffer so we don't cross data from the previous message into this one
458 if (m_depadBuffer != null) {
459 Array.Clear(m_depadBuffer, 0, m_depadBuffer.Length);
460 m_depadBuffer = null;
465 /// Encrypt or decrypt a single block of data
467 [SecuritySafeCritical]
468 public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) {
469 Contract.Ensures(Contract.Result<int>() >= 0);
471 if (inputBuffer == null) {
472 throw new ArgumentNullException("inputBuffer");
474 if (inputOffset < 0) {
475 throw new ArgumentOutOfRangeException("inputOffset");
477 if (inputCount <= 0) {
478 throw new ArgumentOutOfRangeException("inputCount");
480 if (inputCount % InputBlockSize != 0) {
481 throw new ArgumentOutOfRangeException("inputCount", SR.GetString(SR.Cryptography_MustTransformWholeBlock));
483 if (inputCount > inputBuffer.Length - inputOffset) {
484 throw new ArgumentOutOfRangeException("inputCount", SR.GetString(SR.Cryptography_TransformBeyondEndOfBuffer));
486 if (outputBuffer == null) {
487 throw new ArgumentNullException("outputBuffer");
489 if (inputCount > outputBuffer.Length - outputOffset) {
490 throw new ArgumentOutOfRangeException("outputOffset", SR.GetString(SR.Cryptography_TransformBeyondEndOfBuffer));
493 if (m_encryptionMode == EncryptionMode.Encrypt) {
494 // CryptEncrypt operates in place, so make a copy of the original data in the output buffer for
496 Buffer.BlockCopy(inputBuffer, inputOffset, outputBuffer, outputOffset, inputCount);
497 return EncryptBlocks(outputBuffer, outputOffset, inputCount);
500 return DecryptBlocks(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
505 /// Encrypt or decrypt the last block of data in the current message
507 [SecuritySafeCritical]
508 public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) {
509 Contract.Ensures(Contract.Result<byte[]>() != null);
511 if (inputBuffer == null) {
512 throw new ArgumentNullException("inputBuffer");
514 if (inputOffset < 0) {
515 throw new ArgumentOutOfRangeException("inputOffset");
517 if (inputCount < 0) {
518 throw new ArgumentOutOfRangeException("inputCount");
520 if (inputCount > inputBuffer.Length - inputOffset) {
521 throw new ArgumentOutOfRangeException("inputCount", SR.GetString(SR.Cryptography_TransformBeyondEndOfBuffer));
524 byte[] outputData = null;
526 if (m_encryptionMode == EncryptionMode.Encrypt) {
527 // If we're encrypting, we need to pad the last block before encrypting it
528 outputData = PadBlock(inputBuffer, inputOffset, inputCount);
529 if (outputData.Length > 0) {
530 EncryptBlocks(outputData, 0, outputData.Length);
534 // We can't complete decryption on a partial block
535 if (inputCount % InputBlockSize != 0) {
536 throw new CryptographicException(SR.GetString(SR.Cryptography_PartialBlock));
540 // If we have a depad buffer, copy that into the decryption buffer followed by the input data.
541 // Otherwise the decryption buffer is just the input data.
544 byte[] ciphertext = null;
546 if (m_depadBuffer == null) {
547 ciphertext = new byte[inputCount];
548 Buffer.BlockCopy(inputBuffer, inputOffset, ciphertext, 0, inputCount);
551 ciphertext = new byte[m_depadBuffer.Length + inputCount];
552 Buffer.BlockCopy(m_depadBuffer, 0, ciphertext, 0, m_depadBuffer.Length);
553 Buffer.BlockCopy(inputBuffer, inputOffset, ciphertext, m_depadBuffer.Length, inputCount);
556 // Decrypt the data, then strip the padding to get the final decrypted data.
557 if (ciphertext.Length > 0) {
558 int decryptedBytes = RawDecryptBlocks(ciphertext, 0, ciphertext.Length);
559 outputData = DepadBlock(ciphertext, 0, decryptedBytes);
562 outputData = new byte[0];
571 /// Prepare the cryptographic key for use in the encryption / decryption operation
573 [System.Security.SecurityCritical]
574 private static SafeCapiKeyHandle SetupKey(SafeCapiKeyHandle key, byte[] iv, CipherMode cipherMode, int feedbackSize) {
575 Contract.Requires(key != null);
576 Contract.Requires(cipherMode == CipherMode.ECB || iv != null);
577 Contract.Requires(0 <= feedbackSize);
578 Contract.Ensures(Contract.Result<SafeCapiKeyHandle>() != null &&
579 !Contract.Result<SafeCapiKeyHandle>().IsInvalid &&
580 !Contract.Result<SafeCapiKeyHandle>().IsClosed);
582 // Make a copy of the key so that we don't modify the properties of the caller's copy
583 SafeCapiKeyHandle encryptionKey = key.Duplicate();
585 // Setup the cipher mode first
586 CapiNative.SetKeyParameter(encryptionKey, CapiNative.KeyParameter.Mode, (int)cipherMode);
588 // If we're not in ECB mode then setup the IV
589 if (cipherMode != CipherMode.ECB) {
590 CapiNative.SetKeyParameter(encryptionKey, CapiNative.KeyParameter.IV, iv);
593 // OFB and CFB require a feedback loop size
594 if (cipherMode == CipherMode.CFB || cipherMode == CipherMode.OFB) {
595 CapiNative.SetKeyParameter(encryptionKey, CapiNative.KeyParameter.ModeBits, feedbackSize);
598 return encryptionKey;