CapiSymmetricAlgorithm.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / Core / System / Security / Cryptography / CapiSymmetricAlgorithm.cs / 1305376 / CapiSymmetricAlgorithm.cs

                            // ==++== 
//
//   Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ==--== 

using System; 
using System.Diagnostics; 
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices; 
using System.Diagnostics.Contracts;
using Microsoft.Win32.SafeHandles;

namespace System.Security.Cryptography { 
    /// 
    ///     Flag to indicate if we're doing encryption or decryption 
    ///  
    internal enum EncryptionMode {
        Encrypt, 
        Decrypt
    }

    ///  
    ///     Implementation of a generic CAPI symmetric encryption algorithm. Concrete SymmetricAlgorithm classes
    ///     which wrap CAPI implementations can use this class to perform the actual encryption work. 
    ///  
    internal sealed class CapiSymmetricAlgorithm : ICryptoTransform {
        private int m_blockSize; 
        private byte[] m_depadBuffer;
        private EncryptionMode m_encryptionMode;
        private SafeCapiKeyHandle m_key;
        private PaddingMode m_paddingMode; 
        private SafeCspHandle m_provider;
 
        //  
        // 
        //  
        // 
        // 
        // 
        //  
        // 
        //  
        //  
        // 
        [System.Security.SecurityCritical] 
        [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Reviewed")]
        public CapiSymmetricAlgorithm(int blockSize,
                                      int feedbackSize,
                                      SafeCspHandle provider, 
                                      SafeCapiKeyHandle key,
                                      byte[] iv, 
                                      CipherMode cipherMode, 
                                      PaddingMode paddingMode,
                                      EncryptionMode encryptionMode) { 
            Contract.Requires(0 < blockSize && blockSize % 8 == 0);
            Contract.Requires(0 <= feedbackSize);
            Contract.Requires(provider != null && !provider.IsInvalid && !provider.IsClosed);
            Contract.Requires(key != null && !key.IsInvalid && !key.IsClosed); 
            Contract.Ensures(m_provider != null && !m_provider.IsInvalid && !m_provider.IsClosed);
 
            m_blockSize = blockSize; 
            m_encryptionMode = encryptionMode;
            m_paddingMode = paddingMode; 
            m_provider = provider.Duplicate();
            m_key = SetupKey(key, ProcessIV(iv, blockSize, cipherMode), cipherMode, feedbackSize);
        }
 
        public bool CanReuseTransform {
            get { return true; } 
        } 

        public bool CanTransformMultipleBlocks { 
            get { return true; }
        }

        // 
        // Note: both input and output block size are in bytes rather than bits
        // 
 
        public int InputBlockSize {
            [Pure] 
            get { return m_blockSize / 8; }
        }

        public int OutputBlockSize { 
            get { return m_blockSize / 8; }
        } 
 
        // 
        //  
        // 
        // 
        // 
        //  
        [System.Security.SecurityCritical]
        public void Dispose() { 
            Contract.Ensures(m_key == null || m_key.IsClosed); 
            Contract.Ensures(m_provider == null || m_provider.IsClosed);
            Contract.Ensures(m_depadBuffer == null); 

            if (m_key != null) {
                m_key.Dispose();
            } 

            if (m_provider != null) { 
                m_provider.Dispose(); 
            }
 
            if (m_depadBuffer != null) {
                Array.Clear(m_depadBuffer, 0, m_depadBuffer.Length);
            }
 
            return;
        } 
 
        /// 
        ///     Decrypt blocks of ciphertext 
        /// 
        // 
        // 
        //  
        // 
        [System.Security.SecurityCritical] 
        private int DecryptBlocks(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) { 
            Contract.Requires(m_key != null);
            Contract.Requires(inputBuffer != null && inputCount <= inputBuffer.Length - inputOffset); 
            Contract.Requires(inputOffset >= 0);
            Contract.Requires(inputCount > 0 && inputCount % InputBlockSize == 0);
            Contract.Requires(outputBuffer != null && inputCount <= outputBuffer.Length - outputOffset);
            Contract.Requires(inputOffset >= 0); 
            Contract.Requires(m_depadBuffer == null || (m_paddingMode != PaddingMode.None && m_paddingMode != PaddingMode.Zeros));
            Contract.Ensures(Contract.Result() >= 0); 
 
            //
            // If we're decrypting, it's possible to be called with the last blocks of the data, and then 
            // have TransformFinalBlock called with an empty array. Since we don't know if this is the case,
            // we won't decrypt the last block of the input until either TransformBlock or
            // TransformFinalBlock is next called.
            // 
            // We don't need to do this for PaddingMode.None because there is no padding to strip, and
            // we also don't do this for PaddingMode.Zeros since there is no way for us to tell if the 
            // zeros at the end of a block are part of the plaintext or the padding. 
            //
 
            int decryptedBytes = 0;
            if (m_paddingMode != PaddingMode.None && m_paddingMode != PaddingMode.Zeros) {
                // If we have data saved from a previous call, decrypt that into the output first
                if (m_depadBuffer != null) { 
                    int depadDecryptLength = RawDecryptBlocks(m_depadBuffer, 0, m_depadBuffer.Length);
                    Buffer.BlockCopy(m_depadBuffer, 0, outputBuffer, outputOffset, depadDecryptLength); 
                    Array.Clear(m_depadBuffer, 0, m_depadBuffer.Length); 
                    outputOffset += depadDecryptLength;
                    decryptedBytes += depadDecryptLength; 
                }
                else {
                    m_depadBuffer = new byte[InputBlockSize];
                } 

                // Copy the last block of the input buffer into the depad buffer 
                Debug.Assert(inputCount >= m_depadBuffer.Length, "inputCount >= m_depadBuffer.Length"); 
                Buffer.BlockCopy(inputBuffer,
                                 inputOffset + inputCount - m_depadBuffer.Length, 
                                 m_depadBuffer,
                                 0,
                                 m_depadBuffer.Length);
                inputCount -= m_depadBuffer.Length; 
                Debug.Assert(inputCount % InputBlockSize == 0, "Did not remove whole blocks for depadding");
            } 
 
            // CryptDecrypt operates in place, so if after reserving the depad buffer there's still data to decrypt,
            // make a copy of that in the output buffer to work on. 
            if (inputCount > 0) {
                Buffer.BlockCopy(inputBuffer, inputOffset, outputBuffer, outputOffset, inputCount);
                decryptedBytes += RawDecryptBlocks(outputBuffer, outputOffset, inputCount);
            } 

            return decryptedBytes; 
        } 

        ///  
        ///     Remove the padding from the last blocks being decrypted
        /// 
        private byte[] DepadBlock(byte[] block, int offset, int count) {
            Contract.Requires(block != null && count >= block.Length - offset); 
            Contract.Requires(0 <= offset);
            Contract.Requires(0 <= count); 
            Contract.Ensures(Contract.Result() != null && Contract.Result().Length <= block.Length); 

            int padBytes = 0; 

            // See code:System.Security.Cryptography.CapiSymmetricAlgorithm.PadBlock for a description of the
            // padding modes.
            switch (m_paddingMode) { 
                case PaddingMode.ANSIX923:
                    padBytes = block[offset + count - 1]; 
 
                    // Verify the amount of padding is reasonable
                    if (padBytes <= 0 || padBytes > InputBlockSize) { 
                        throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding));
                    }

                    // Verify that all the padding bytes are 0s 
                    for (int i = offset + count - padBytes; i < offset + count - 1; i++) {
                        if (block[i] != 0) { 
                            throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding)); 
                        }
                    } 

                    break;

                case PaddingMode.ISO10126: 
                    padBytes = block[offset + count - 1];
 
                    // Verify the amount of padding is reasonable 
                    if (padBytes <= 0 || padBytes > InputBlockSize) {
                        throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding)); 
                    }

                    // Since the padding consists of random bytes, we cannot verify the actual pad bytes themselves
                    break; 

                case PaddingMode.PKCS7: 
                    padBytes = block[offset + count - 1]; 

                    // Verify the amount of padding is reasonable 
                    if (padBytes <= 0 || padBytes > InputBlockSize) {
                        throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding));
                    }
 
                    // Verify all the padding bytes match the amount of padding
                    for (int i = offset + count - padBytes; i < offset + count; i++) { 
                        if (block[i] != padBytes) { 
                            throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding));
                        } 
                    }

                    break;
 
                    // We cannot remove Zeros padding because we don't know if the zeros at the end of the block
                    // belong to the padding or the plaintext itself. 
                case PaddingMode.Zeros: 
                case PaddingMode.None:
                    padBytes = 0; 
                    break;

                default:
                    throw new CryptographicException(SR.GetString(SR.Cryptography_UnknownPaddingMode)); 
            }
 
            // Copy everything but the padding to the output 
            byte[] depadded = new byte[count - padBytes];
            Buffer.BlockCopy(block, offset, depadded, 0, depadded.Length); 
            return depadded;
        }

        ///  
        ///     Encrypt blocks of plaintext
        ///  
        //  
        // 
        //  
        // 
        // 
        // 
        //  
        [System.Security.SecurityCritical]
        private int EncryptBlocks(byte[] buffer, int offset, int count) { 
            Contract.Requires(m_key != null); 
            Contract.Requires(buffer != null && count <= buffer.Length - offset);
            Contract.Requires(offset >= 0); 
            Contract.Requires(count > 0 && count % InputBlockSize == 0);
            Contract.Ensures(Contract.Result() >= 0);

            // 
            // Do the encryption. Note that CapiSymmetricAlgorithm will do all padding itself since the CLR
            // supports padding modes that CAPI does not, so we will always tell CAPI that we are not working 
            // with the final block. 
            //
 
            int dataLength = count;
            unsafe {
                fixed (byte* pData = &buffer[offset]) {
                    if (!CapiNative.UnsafeNativeMethods.CryptEncrypt(m_key, 
                                                                     SafeCapiHashHandle.InvalidHandle,
                                                                     false, 
                                                                     0, 
                                                                     new IntPtr(pData),
                                                                     ref dataLength, 
                                                                     buffer.Length - offset)) {
                        throw new CryptographicException(Marshal.GetLastWin32Error());
                    }
                } 
            }
 
            return dataLength; 
        }
 
        /// 
        ///     Calculate the padding for a block of data
        /// 
        //  
        // 
        //  
        //  
        [System.Security.SecurityCritical]
        private byte[] PadBlock(byte[] block, int offset, int count) { 
            Contract.Requires(m_provider != null);
            Contract.Requires(block != null && count <= block.Length - offset);
            Contract.Requires(0 <= offset);
            Contract.Requires(0 <= count); 
            Contract.Ensures(Contract.Result() != null && Contract.Result().Length % InputBlockSize == 0);
 
            byte[] result = null; 
            int padBytes = InputBlockSize - (count % InputBlockSize);
 
            switch (m_paddingMode) {
                    // ANSI padding fills the blocks with zeros and adds the total number of padding bytes as
                    // the last pad byte, adding an extra block if the last block is complete.
                    // 
                    // x 00 00 00 00 00 00 07
                case PaddingMode.ANSIX923: 
                    result = new byte[count + padBytes]; 
                    Buffer.BlockCopy(block, 0, result, 0, count);
                    result[result.Length - 1] = (byte)padBytes; 
                    break;

                    // ISO padding fills the blocks up with random bytes and adds the total number of padding
                    // bytes as the last pad byte, adding an extra block if the last block is complete. 
                    //
                    // xx rr rr rr rr rr rr 07 
                case PaddingMode.ISO10126: 
                    result = new byte[count + padBytes];
 
                    CapiNative.UnsafeNativeMethods.CryptGenRandom(m_provider, result.Length - 1, result);
                    Buffer.BlockCopy(block, 0, result, 0, count);
                    result[result.Length - 1] = (byte)padBytes;
                    break; 

                    // No padding requires that the input already be a multiple of the block size 
                case PaddingMode.None: 
                    if (count % InputBlockSize != 0) {
                        throw new CryptographicException(SR.GetString(SR.Cryptography_PartialBlock)); 
                    }

                    result = new byte[count];
                    Buffer.BlockCopy(block, offset, result, 0, result.Length); 
                    break;
 
                    // PKCS padding fills the blocks up with bytes containing the total number of padding bytes 
                    // used, adding an extra block if the last block is complete.
                    // 
                    // xx xx 06 06 06 06 06 06
                case PaddingMode.PKCS7:
                    result = new byte[count + padBytes];
                    Buffer.BlockCopy(block, offset, result, 0, count); 

                    for (int i = count; i < result.Length; i++) { 
                        result[i] = (byte)padBytes; 
                    }
                    break; 

                    // Zeros padding fills the last partial block with zeros, and does not add a new block to
                    // the end if the last block is already complete.
                    // 
                    //  xx 00 00 00 00 00 00 00
                case PaddingMode.Zeros: 
                    if (padBytes == InputBlockSize) { 
                        padBytes = 0;
                    } 

                    result = new byte[count + padBytes];
                    Buffer.BlockCopy(block, offset, result, 0, count);
                    break; 

                default: 
                    throw new CryptographicException(SR.GetString(SR.Cryptography_UnknownPaddingMode)); 
            }
 
            return result;
        }

        ///  
        ///     Validate and transform the user's IV into one that we will pass on to CAPI
        /// 
        ///     If we have an IV, make a copy of it so that it doesn't get modified while we're using it. If 
        ///     not, and we're not in ECB mode then throw an error, since we cannot decrypt without the IV, and
        ///     generating a random IV to encrypt with would lead to data which is not decryptable. 
        ///
        ///     For compatibility with v1.x, we accept IVs which are longer than the block size, and truncate
        ///     them back.  We will reject an IV which is smaller than the block size however.
        ///  
        private static byte[] ProcessIV(byte[] iv, int blockSize, CipherMode cipherMode) {
            Contract.Requires(blockSize % 8 == 0); 
            Contract.Ensures(cipherMode == CipherMode.ECB || 
                             (Contract.Result() != null && Contract.Result().Length == blockSize / 8));
 
            byte[] realIV = null;

            if (iv != null) {
                if (blockSize / 8 <= iv.Length) { 
                    realIV = new byte[blockSize / 8];
                    Buffer.BlockCopy(iv, 0, realIV, 0, realIV.Length); 
                } 
                else {
                    throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidIVSize)); 
                }
            }
            else if (cipherMode != CipherMode.ECB) {
                throw new CryptographicException(SR.GetString(SR.Cryptography_MissingIV)); 
            }
 
            return realIV; 
        }
 
        /// 
        ///     Do a direct decryption of the ciphertext blocks. This method should not be called from anywhere
        ///     but DecryptBlocks or TransformFinalBlock since it does not account for the depadding buffer and
        ///     direct use could lead to incorrect decryption values. 
        /// 
        //  
        //  
        // 
        //  
        // 
        // 
        // 
        [System.Security.SecurityCritical] 
        private int RawDecryptBlocks(byte[] buffer, int offset, int count) {
            Contract.Requires(m_key != null); 
            Contract.Requires(buffer != null && count <= buffer.Length - offset); 
            Contract.Requires(offset >= 0);
            Contract.Requires(count > 0 && count % InputBlockSize == 0); 
            Contract.Ensures(Contract.Result() >= 0);

            //
            // Do the decryption. Note that CapiSymmetricAlgorithm will do all padding itself since the CLR 
            // supports padding modes that CAPI does not, so we will always tell CAPI that we are not working
            // with the final block. 
            // 

            int dataLength = count; 
            unsafe {
                fixed (byte* pData = &buffer[offset]) {
                    if (!CapiNative.UnsafeNativeMethods.CryptDecrypt(m_key,
                                                                     SafeCapiHashHandle.InvalidHandle, 
                                                                     false,
                                                                     0, 
                                                                     new IntPtr(pData), 
                                                                     ref dataLength)) {
                        throw new CryptographicException(Marshal.GetLastWin32Error()); 
                    }
                }
            }
 
            return dataLength;
        } 
 
        /// 
        ///     Reset the state of the algorithm so that it can begin processing a new message 
        /// 
        // 
        // 
        //  
        // 
        //  
        //  
        // 
        [System.Security.SecurityCritical] 
        private void Reset() {
            Contract.Requires(m_key != null);
            Contract.Ensures(m_depadBuffer == null);
 
            //
            // CryptEncrypt / CryptDecrypt must be called with the Final parameter set to true so that 
            // their internal state is reset. Since we do all padding by hand, this isn't done by 
            // TransformFinalBlock so is done on an empty buffer here.
            // 

            byte[] buffer = new byte[OutputBlockSize];
            int resetSize = 0;
            unsafe { 
                fixed (byte* pBuffer = buffer) {
                    if (m_encryptionMode == EncryptionMode.Encrypt) { 
                        CapiNative.UnsafeNativeMethods.CryptEncrypt(m_key, 
                                                                    SafeCapiHashHandle.InvalidHandle,
                                                                    true, 
                                                                    0,
                                                                    new IntPtr(pBuffer),
                                                                    ref resetSize,
                                                                    buffer.Length); 
                    }
                    else { 
                        CapiNative.UnsafeNativeMethods.CryptDecrypt(m_key, 
                                                                    SafeCapiHashHandle.InvalidHandle,
                                                                    true, 
                                                                    0,
                                                                    new IntPtr(pBuffer),
                                                                    ref resetSize);
                    } 
                }
            } 
 
            // Also erase the depadding buffer so we don't cross data from the previous message into this one
            if (m_depadBuffer != null) { 
                Array.Clear(m_depadBuffer, 0, m_depadBuffer.Length);
                m_depadBuffer = null;
            }
        } 

        ///  
        ///     Encrypt or decrypt a single block of data 
        /// 
        //  
        // 
        // 
        // 
        [System.Security.SecurityCritical] 
        public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) {
            Contract.Ensures(Contract.Result() >= 0); 
 
            if (inputBuffer == null) {
                throw new ArgumentNullException("inputBuffer"); 
            }
            if (inputOffset < 0) {
                throw new ArgumentOutOfRangeException("inputOffset");
            } 
            if (inputCount <= 0) {
                throw new ArgumentOutOfRangeException("inputCount"); 
            } 
            if (inputCount % InputBlockSize != 0) {
                throw new ArgumentOutOfRangeException("inputCount", SR.GetString(SR.Cryptography_MustTransformWholeBlock)); 
            }
            if (inputCount > inputBuffer.Length - inputOffset) {
                throw new ArgumentOutOfRangeException("inputCount", SR.GetString(SR.Cryptography_TransformBeyondEndOfBuffer));
            } 
            if (outputBuffer == null) {
                throw new ArgumentNullException("outputBuffer"); 
            } 
            if (inputCount > outputBuffer.Length - outputOffset) {
                throw new ArgumentOutOfRangeException("outputOffset", SR.GetString(SR.Cryptography_TransformBeyondEndOfBuffer)); 
            }

            if (m_encryptionMode == EncryptionMode.Encrypt) {
                // CryptEncrypt operates in place, so make a copy of the original data in the output buffer for 
                // it to work on.
                Buffer.BlockCopy(inputBuffer, inputOffset, outputBuffer, outputOffset, inputCount); 
                return EncryptBlocks(outputBuffer, outputOffset, inputCount); 
            }
            else { 
                return DecryptBlocks(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
            }
        }
 
        /// 
        ///     Encrypt or decrypt the last block of data in the current message 
        ///  
        // 
        //  
        // 
        // 
        // 
        //  
        [System.Security.SecurityCritical]
        public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) { 
            Contract.Ensures(Contract.Result() != null); 

            if (inputBuffer == null) { 
                throw new ArgumentNullException("inputBuffer");
            }
            if (inputOffset < 0) {
                throw new ArgumentOutOfRangeException("inputOffset"); 
            }
            if (inputCount < 0) { 
                throw new ArgumentOutOfRangeException("inputCount"); 
            }
            if (inputCount > inputBuffer.Length - inputOffset) { 
                throw new ArgumentOutOfRangeException("inputCount", SR.GetString(SR.Cryptography_TransformBeyondEndOfBuffer));
            }

            byte[] outputData = null; 

            if (m_encryptionMode == EncryptionMode.Encrypt) { 
                // If we're encrypting, we need to pad the last block before encrypting it 
                outputData = PadBlock(inputBuffer, inputOffset, inputCount);
                if (outputData.Length > 0) { 
                    EncryptBlocks(outputData, 0, outputData.Length);
                }
            }
            else { 
                // We can't complete decryption on a partial block
                if (inputCount % InputBlockSize != 0) { 
                    throw new CryptographicException(SR.GetString(SR.Cryptography_PartialBlock)); 
                }
 
                //
                // If we have a depad buffer, copy that into the decryption buffer followed by the input data.
                // Otherwise the decryption buffer is just the input data.
                // 

                byte[] ciphertext = null; 
 
                if (m_depadBuffer == null) {
                    ciphertext = new byte[inputCount]; 
                    Buffer.BlockCopy(inputBuffer, inputOffset, ciphertext, 0, inputCount);
                }
                else {
                    ciphertext = new byte[m_depadBuffer.Length + inputCount]; 
                    Buffer.BlockCopy(m_depadBuffer, 0, ciphertext, 0, m_depadBuffer.Length);
                    Buffer.BlockCopy(inputBuffer, inputOffset, ciphertext, m_depadBuffer.Length, inputCount); 
                } 

                // Decrypt the data, then strip the padding to get the final decrypted data. 
                if (ciphertext.Length > 0) {
                    int decryptedBytes = RawDecryptBlocks(ciphertext, 0, ciphertext.Length);
                    outputData = DepadBlock(ciphertext, 0, decryptedBytes);
                } 
                else {
                    outputData = new byte[0]; 
                } 
            }
 
            Reset();
            return outputData;
        }
 
        /// 
        ///     Prepare the cryptographic key for use in the encryption / decryption operation 
        ///  
        // 
        //  
        // 
        // 
        // 
        //  
        // 
        //  
        //  
        // 
        [System.Security.SecurityCritical] 
        private static SafeCapiKeyHandle SetupKey(SafeCapiKeyHandle key, byte[] iv, CipherMode cipherMode, int feedbackSize) {
            Contract.Requires(key != null);
            Contract.Requires(cipherMode == CipherMode.ECB || iv != null);
            Contract.Requires(0 <= feedbackSize); 
            Contract.Ensures(Contract.Result() != null &&
                             !Contract.Result().IsInvalid && 
                             !Contract.Result().IsClosed); 

            // Make a copy of the key so that we don't modify the properties of the caller's copy 
            SafeCapiKeyHandle encryptionKey = key.Duplicate();

            // Setup the cipher mode first
            CapiNative.SetKeyParameter(encryptionKey, CapiNative.KeyParameter.Mode, (int)cipherMode); 

            // If we're not in ECB mode then setup the IV 
            if (cipherMode != CipherMode.ECB) { 
                CapiNative.SetKeyParameter(encryptionKey, CapiNative.KeyParameter.IV, iv);
            } 

            // OFB and CFB require a feedback loop size
            if (cipherMode == CipherMode.CFB || cipherMode == CipherMode.OFB) {
                CapiNative.SetKeyParameter(encryptionKey, CapiNative.KeyParameter.ModeBits, feedbackSize); 
            }
 
            return encryptionKey; 
        }
    } 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// ==++== 
//
//   Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ==--== 

using System; 
using System.Diagnostics; 
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices; 
using System.Diagnostics.Contracts;
using Microsoft.Win32.SafeHandles;

namespace System.Security.Cryptography { 
    /// 
    ///     Flag to indicate if we're doing encryption or decryption 
    ///  
    internal enum EncryptionMode {
        Encrypt, 
        Decrypt
    }

    ///  
    ///     Implementation of a generic CAPI symmetric encryption algorithm. Concrete SymmetricAlgorithm classes
    ///     which wrap CAPI implementations can use this class to perform the actual encryption work. 
    ///  
    internal sealed class CapiSymmetricAlgorithm : ICryptoTransform {
        private int m_blockSize; 
        private byte[] m_depadBuffer;
        private EncryptionMode m_encryptionMode;
        private SafeCapiKeyHandle m_key;
        private PaddingMode m_paddingMode; 
        private SafeCspHandle m_provider;
 
        //  
        // 
        //  
        // 
        // 
        // 
        //  
        // 
        //  
        //  
        // 
        [System.Security.SecurityCritical] 
        [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Reviewed")]
        public CapiSymmetricAlgorithm(int blockSize,
                                      int feedbackSize,
                                      SafeCspHandle provider, 
                                      SafeCapiKeyHandle key,
                                      byte[] iv, 
                                      CipherMode cipherMode, 
                                      PaddingMode paddingMode,
                                      EncryptionMode encryptionMode) { 
            Contract.Requires(0 < blockSize && blockSize % 8 == 0);
            Contract.Requires(0 <= feedbackSize);
            Contract.Requires(provider != null && !provider.IsInvalid && !provider.IsClosed);
            Contract.Requires(key != null && !key.IsInvalid && !key.IsClosed); 
            Contract.Ensures(m_provider != null && !m_provider.IsInvalid && !m_provider.IsClosed);
 
            m_blockSize = blockSize; 
            m_encryptionMode = encryptionMode;
            m_paddingMode = paddingMode; 
            m_provider = provider.Duplicate();
            m_key = SetupKey(key, ProcessIV(iv, blockSize, cipherMode), cipherMode, feedbackSize);
        }
 
        public bool CanReuseTransform {
            get { return true; } 
        } 

        public bool CanTransformMultipleBlocks { 
            get { return true; }
        }

        // 
        // Note: both input and output block size are in bytes rather than bits
        // 
 
        public int InputBlockSize {
            [Pure] 
            get { return m_blockSize / 8; }
        }

        public int OutputBlockSize { 
            get { return m_blockSize / 8; }
        } 
 
        // 
        //  
        // 
        // 
        // 
        //  
        [System.Security.SecurityCritical]
        public void Dispose() { 
            Contract.Ensures(m_key == null || m_key.IsClosed); 
            Contract.Ensures(m_provider == null || m_provider.IsClosed);
            Contract.Ensures(m_depadBuffer == null); 

            if (m_key != null) {
                m_key.Dispose();
            } 

            if (m_provider != null) { 
                m_provider.Dispose(); 
            }
 
            if (m_depadBuffer != null) {
                Array.Clear(m_depadBuffer, 0, m_depadBuffer.Length);
            }
 
            return;
        } 
 
        /// 
        ///     Decrypt blocks of ciphertext 
        /// 
        // 
        // 
        //  
        // 
        [System.Security.SecurityCritical] 
        private int DecryptBlocks(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) { 
            Contract.Requires(m_key != null);
            Contract.Requires(inputBuffer != null && inputCount <= inputBuffer.Length - inputOffset); 
            Contract.Requires(inputOffset >= 0);
            Contract.Requires(inputCount > 0 && inputCount % InputBlockSize == 0);
            Contract.Requires(outputBuffer != null && inputCount <= outputBuffer.Length - outputOffset);
            Contract.Requires(inputOffset >= 0); 
            Contract.Requires(m_depadBuffer == null || (m_paddingMode != PaddingMode.None && m_paddingMode != PaddingMode.Zeros));
            Contract.Ensures(Contract.Result() >= 0); 
 
            //
            // If we're decrypting, it's possible to be called with the last blocks of the data, and then 
            // have TransformFinalBlock called with an empty array. Since we don't know if this is the case,
            // we won't decrypt the last block of the input until either TransformBlock or
            // TransformFinalBlock is next called.
            // 
            // We don't need to do this for PaddingMode.None because there is no padding to strip, and
            // we also don't do this for PaddingMode.Zeros since there is no way for us to tell if the 
            // zeros at the end of a block are part of the plaintext or the padding. 
            //
 
            int decryptedBytes = 0;
            if (m_paddingMode != PaddingMode.None && m_paddingMode != PaddingMode.Zeros) {
                // If we have data saved from a previous call, decrypt that into the output first
                if (m_depadBuffer != null) { 
                    int depadDecryptLength = RawDecryptBlocks(m_depadBuffer, 0, m_depadBuffer.Length);
                    Buffer.BlockCopy(m_depadBuffer, 0, outputBuffer, outputOffset, depadDecryptLength); 
                    Array.Clear(m_depadBuffer, 0, m_depadBuffer.Length); 
                    outputOffset += depadDecryptLength;
                    decryptedBytes += depadDecryptLength; 
                }
                else {
                    m_depadBuffer = new byte[InputBlockSize];
                } 

                // Copy the last block of the input buffer into the depad buffer 
                Debug.Assert(inputCount >= m_depadBuffer.Length, "inputCount >= m_depadBuffer.Length"); 
                Buffer.BlockCopy(inputBuffer,
                                 inputOffset + inputCount - m_depadBuffer.Length, 
                                 m_depadBuffer,
                                 0,
                                 m_depadBuffer.Length);
                inputCount -= m_depadBuffer.Length; 
                Debug.Assert(inputCount % InputBlockSize == 0, "Did not remove whole blocks for depadding");
            } 
 
            // CryptDecrypt operates in place, so if after reserving the depad buffer there's still data to decrypt,
            // make a copy of that in the output buffer to work on. 
            if (inputCount > 0) {
                Buffer.BlockCopy(inputBuffer, inputOffset, outputBuffer, outputOffset, inputCount);
                decryptedBytes += RawDecryptBlocks(outputBuffer, outputOffset, inputCount);
            } 

            return decryptedBytes; 
        } 

        ///  
        ///     Remove the padding from the last blocks being decrypted
        /// 
        private byte[] DepadBlock(byte[] block, int offset, int count) {
            Contract.Requires(block != null && count >= block.Length - offset); 
            Contract.Requires(0 <= offset);
            Contract.Requires(0 <= count); 
            Contract.Ensures(Contract.Result() != null && Contract.Result().Length <= block.Length); 

            int padBytes = 0; 

            // See code:System.Security.Cryptography.CapiSymmetricAlgorithm.PadBlock for a description of the
            // padding modes.
            switch (m_paddingMode) { 
                case PaddingMode.ANSIX923:
                    padBytes = block[offset + count - 1]; 
 
                    // Verify the amount of padding is reasonable
                    if (padBytes <= 0 || padBytes > InputBlockSize) { 
                        throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding));
                    }

                    // Verify that all the padding bytes are 0s 
                    for (int i = offset + count - padBytes; i < offset + count - 1; i++) {
                        if (block[i] != 0) { 
                            throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding)); 
                        }
                    } 

                    break;

                case PaddingMode.ISO10126: 
                    padBytes = block[offset + count - 1];
 
                    // Verify the amount of padding is reasonable 
                    if (padBytes <= 0 || padBytes > InputBlockSize) {
                        throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding)); 
                    }

                    // Since the padding consists of random bytes, we cannot verify the actual pad bytes themselves
                    break; 

                case PaddingMode.PKCS7: 
                    padBytes = block[offset + count - 1]; 

                    // Verify the amount of padding is reasonable 
                    if (padBytes <= 0 || padBytes > InputBlockSize) {
                        throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding));
                    }
 
                    // Verify all the padding bytes match the amount of padding
                    for (int i = offset + count - padBytes; i < offset + count; i++) { 
                        if (block[i] != padBytes) { 
                            throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding));
                        } 
                    }

                    break;
 
                    // We cannot remove Zeros padding because we don't know if the zeros at the end of the block
                    // belong to the padding or the plaintext itself. 
                case PaddingMode.Zeros: 
                case PaddingMode.None:
                    padBytes = 0; 
                    break;

                default:
                    throw new CryptographicException(SR.GetString(SR.Cryptography_UnknownPaddingMode)); 
            }
 
            // Copy everything but the padding to the output 
            byte[] depadded = new byte[count - padBytes];
            Buffer.BlockCopy(block, offset, depadded, 0, depadded.Length); 
            return depadded;
        }

        ///  
        ///     Encrypt blocks of plaintext
        ///  
        //  
        // 
        //  
        // 
        // 
        // 
        //  
        [System.Security.SecurityCritical]
        private int EncryptBlocks(byte[] buffer, int offset, int count) { 
            Contract.Requires(m_key != null); 
            Contract.Requires(buffer != null && count <= buffer.Length - offset);
            Contract.Requires(offset >= 0); 
            Contract.Requires(count > 0 && count % InputBlockSize == 0);
            Contract.Ensures(Contract.Result() >= 0);

            // 
            // Do the encryption. Note that CapiSymmetricAlgorithm will do all padding itself since the CLR
            // supports padding modes that CAPI does not, so we will always tell CAPI that we are not working 
            // with the final block. 
            //
 
            int dataLength = count;
            unsafe {
                fixed (byte* pData = &buffer[offset]) {
                    if (!CapiNative.UnsafeNativeMethods.CryptEncrypt(m_key, 
                                                                     SafeCapiHashHandle.InvalidHandle,
                                                                     false, 
                                                                     0, 
                                                                     new IntPtr(pData),
                                                                     ref dataLength, 
                                                                     buffer.Length - offset)) {
                        throw new CryptographicException(Marshal.GetLastWin32Error());
                    }
                } 
            }
 
            return dataLength; 
        }
 
        /// 
        ///     Calculate the padding for a block of data
        /// 
        //  
        // 
        //  
        //  
        [System.Security.SecurityCritical]
        private byte[] PadBlock(byte[] block, int offset, int count) { 
            Contract.Requires(m_provider != null);
            Contract.Requires(block != null && count <= block.Length - offset);
            Contract.Requires(0 <= offset);
            Contract.Requires(0 <= count); 
            Contract.Ensures(Contract.Result() != null && Contract.Result().Length % InputBlockSize == 0);
 
            byte[] result = null; 
            int padBytes = InputBlockSize - (count % InputBlockSize);
 
            switch (m_paddingMode) {
                    // ANSI padding fills the blocks with zeros and adds the total number of padding bytes as
                    // the last pad byte, adding an extra block if the last block is complete.
                    // 
                    // x 00 00 00 00 00 00 07
                case PaddingMode.ANSIX923: 
                    result = new byte[count + padBytes]; 
                    Buffer.BlockCopy(block, 0, result, 0, count);
                    result[result.Length - 1] = (byte)padBytes; 
                    break;

                    // ISO padding fills the blocks up with random bytes and adds the total number of padding
                    // bytes as the last pad byte, adding an extra block if the last block is complete. 
                    //
                    // xx rr rr rr rr rr rr 07 
                case PaddingMode.ISO10126: 
                    result = new byte[count + padBytes];
 
                    CapiNative.UnsafeNativeMethods.CryptGenRandom(m_provider, result.Length - 1, result);
                    Buffer.BlockCopy(block, 0, result, 0, count);
                    result[result.Length - 1] = (byte)padBytes;
                    break; 

                    // No padding requires that the input already be a multiple of the block size 
                case PaddingMode.None: 
                    if (count % InputBlockSize != 0) {
                        throw new CryptographicException(SR.GetString(SR.Cryptography_PartialBlock)); 
                    }

                    result = new byte[count];
                    Buffer.BlockCopy(block, offset, result, 0, result.Length); 
                    break;
 
                    // PKCS padding fills the blocks up with bytes containing the total number of padding bytes 
                    // used, adding an extra block if the last block is complete.
                    // 
                    // xx xx 06 06 06 06 06 06
                case PaddingMode.PKCS7:
                    result = new byte[count + padBytes];
                    Buffer.BlockCopy(block, offset, result, 0, count); 

                    for (int i = count; i < result.Length; i++) { 
                        result[i] = (byte)padBytes; 
                    }
                    break; 

                    // Zeros padding fills the last partial block with zeros, and does not add a new block to
                    // the end if the last block is already complete.
                    // 
                    //  xx 00 00 00 00 00 00 00
                case PaddingMode.Zeros: 
                    if (padBytes == InputBlockSize) { 
                        padBytes = 0;
                    } 

                    result = new byte[count + padBytes];
                    Buffer.BlockCopy(block, offset, result, 0, count);
                    break; 

                default: 
                    throw new CryptographicException(SR.GetString(SR.Cryptography_UnknownPaddingMode)); 
            }
 
            return result;
        }

        ///  
        ///     Validate and transform the user's IV into one that we will pass on to CAPI
        /// 
        ///     If we have an IV, make a copy of it so that it doesn't get modified while we're using it. If 
        ///     not, and we're not in ECB mode then throw an error, since we cannot decrypt without the IV, and
        ///     generating a random IV to encrypt with would lead to data which is not decryptable. 
        ///
        ///     For compatibility with v1.x, we accept IVs which are longer than the block size, and truncate
        ///     them back.  We will reject an IV which is smaller than the block size however.
        ///  
        private static byte[] ProcessIV(byte[] iv, int blockSize, CipherMode cipherMode) {
            Contract.Requires(blockSize % 8 == 0); 
            Contract.Ensures(cipherMode == CipherMode.ECB || 
                             (Contract.Result() != null && Contract.Result().Length == blockSize / 8));
 
            byte[] realIV = null;

            if (iv != null) {
                if (blockSize / 8 <= iv.Length) { 
                    realIV = new byte[blockSize / 8];
                    Buffer.BlockCopy(iv, 0, realIV, 0, realIV.Length); 
                } 
                else {
                    throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidIVSize)); 
                }
            }
            else if (cipherMode != CipherMode.ECB) {
                throw new CryptographicException(SR.GetString(SR.Cryptography_MissingIV)); 
            }
 
            return realIV; 
        }
 
        /// 
        ///     Do a direct decryption of the ciphertext blocks. This method should not be called from anywhere
        ///     but DecryptBlocks or TransformFinalBlock since it does not account for the depadding buffer and
        ///     direct use could lead to incorrect decryption values. 
        /// 
        //  
        //  
        // 
        //  
        // 
        // 
        // 
        [System.Security.SecurityCritical] 
        private int RawDecryptBlocks(byte[] buffer, int offset, int count) {
            Contract.Requires(m_key != null); 
            Contract.Requires(buffer != null && count <= buffer.Length - offset); 
            Contract.Requires(offset >= 0);
            Contract.Requires(count > 0 && count % InputBlockSize == 0); 
            Contract.Ensures(Contract.Result() >= 0);

            //
            // Do the decryption. Note that CapiSymmetricAlgorithm will do all padding itself since the CLR 
            // supports padding modes that CAPI does not, so we will always tell CAPI that we are not working
            // with the final block. 
            // 

            int dataLength = count; 
            unsafe {
                fixed (byte* pData = &buffer[offset]) {
                    if (!CapiNative.UnsafeNativeMethods.CryptDecrypt(m_key,
                                                                     SafeCapiHashHandle.InvalidHandle, 
                                                                     false,
                                                                     0, 
                                                                     new IntPtr(pData), 
                                                                     ref dataLength)) {
                        throw new CryptographicException(Marshal.GetLastWin32Error()); 
                    }
                }
            }
 
            return dataLength;
        } 
 
        /// 
        ///     Reset the state of the algorithm so that it can begin processing a new message 
        /// 
        // 
        // 
        //  
        // 
        //  
        //  
        // 
        [System.Security.SecurityCritical] 
        private void Reset() {
            Contract.Requires(m_key != null);
            Contract.Ensures(m_depadBuffer == null);
 
            //
            // CryptEncrypt / CryptDecrypt must be called with the Final parameter set to true so that 
            // their internal state is reset. Since we do all padding by hand, this isn't done by 
            // TransformFinalBlock so is done on an empty buffer here.
            // 

            byte[] buffer = new byte[OutputBlockSize];
            int resetSize = 0;
            unsafe { 
                fixed (byte* pBuffer = buffer) {
                    if (m_encryptionMode == EncryptionMode.Encrypt) { 
                        CapiNative.UnsafeNativeMethods.CryptEncrypt(m_key, 
                                                                    SafeCapiHashHandle.InvalidHandle,
                                                                    true, 
                                                                    0,
                                                                    new IntPtr(pBuffer),
                                                                    ref resetSize,
                                                                    buffer.Length); 
                    }
                    else { 
                        CapiNative.UnsafeNativeMethods.CryptDecrypt(m_key, 
                                                                    SafeCapiHashHandle.InvalidHandle,
                                                                    true, 
                                                                    0,
                                                                    new IntPtr(pBuffer),
                                                                    ref resetSize);
                    } 
                }
            } 
 
            // Also erase the depadding buffer so we don't cross data from the previous message into this one
            if (m_depadBuffer != null) { 
                Array.Clear(m_depadBuffer, 0, m_depadBuffer.Length);
                m_depadBuffer = null;
            }
        } 

        ///  
        ///     Encrypt or decrypt a single block of data 
        /// 
        //  
        // 
        // 
        // 
        [System.Security.SecurityCritical] 
        public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) {
            Contract.Ensures(Contract.Result() >= 0); 
 
            if (inputBuffer == null) {
                throw new ArgumentNullException("inputBuffer"); 
            }
            if (inputOffset < 0) {
                throw new ArgumentOutOfRangeException("inputOffset");
            } 
            if (inputCount <= 0) {
                throw new ArgumentOutOfRangeException("inputCount"); 
            } 
            if (inputCount % InputBlockSize != 0) {
                throw new ArgumentOutOfRangeException("inputCount", SR.GetString(SR.Cryptography_MustTransformWholeBlock)); 
            }
            if (inputCount > inputBuffer.Length - inputOffset) {
                throw new ArgumentOutOfRangeException("inputCount", SR.GetString(SR.Cryptography_TransformBeyondEndOfBuffer));
            } 
            if (outputBuffer == null) {
                throw new ArgumentNullException("outputBuffer"); 
            } 
            if (inputCount > outputBuffer.Length - outputOffset) {
                throw new ArgumentOutOfRangeException("outputOffset", SR.GetString(SR.Cryptography_TransformBeyondEndOfBuffer)); 
            }

            if (m_encryptionMode == EncryptionMode.Encrypt) {
                // CryptEncrypt operates in place, so make a copy of the original data in the output buffer for 
                // it to work on.
                Buffer.BlockCopy(inputBuffer, inputOffset, outputBuffer, outputOffset, inputCount); 
                return EncryptBlocks(outputBuffer, outputOffset, inputCount); 
            }
            else { 
                return DecryptBlocks(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
            }
        }
 
        /// 
        ///     Encrypt or decrypt the last block of data in the current message 
        ///  
        // 
        //  
        // 
        // 
        // 
        //  
        [System.Security.SecurityCritical]
        public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) { 
            Contract.Ensures(Contract.Result() != null); 

            if (inputBuffer == null) { 
                throw new ArgumentNullException("inputBuffer");
            }
            if (inputOffset < 0) {
                throw new ArgumentOutOfRangeException("inputOffset"); 
            }
            if (inputCount < 0) { 
                throw new ArgumentOutOfRangeException("inputCount"); 
            }
            if (inputCount > inputBuffer.Length - inputOffset) { 
                throw new ArgumentOutOfRangeException("inputCount", SR.GetString(SR.Cryptography_TransformBeyondEndOfBuffer));
            }

            byte[] outputData = null; 

            if (m_encryptionMode == EncryptionMode.Encrypt) { 
                // If we're encrypting, we need to pad the last block before encrypting it 
                outputData = PadBlock(inputBuffer, inputOffset, inputCount);
                if (outputData.Length > 0) { 
                    EncryptBlocks(outputData, 0, outputData.Length);
                }
            }
            else { 
                // We can't complete decryption on a partial block
                if (inputCount % InputBlockSize != 0) { 
                    throw new CryptographicException(SR.GetString(SR.Cryptography_PartialBlock)); 
                }
 
                //
                // If we have a depad buffer, copy that into the decryption buffer followed by the input data.
                // Otherwise the decryption buffer is just the input data.
                // 

                byte[] ciphertext = null; 
 
                if (m_depadBuffer == null) {
                    ciphertext = new byte[inputCount]; 
                    Buffer.BlockCopy(inputBuffer, inputOffset, ciphertext, 0, inputCount);
                }
                else {
                    ciphertext = new byte[m_depadBuffer.Length + inputCount]; 
                    Buffer.BlockCopy(m_depadBuffer, 0, ciphertext, 0, m_depadBuffer.Length);
                    Buffer.BlockCopy(inputBuffer, inputOffset, ciphertext, m_depadBuffer.Length, inputCount); 
                } 

                // Decrypt the data, then strip the padding to get the final decrypted data. 
                if (ciphertext.Length > 0) {
                    int decryptedBytes = RawDecryptBlocks(ciphertext, 0, ciphertext.Length);
                    outputData = DepadBlock(ciphertext, 0, decryptedBytes);
                } 
                else {
                    outputData = new byte[0]; 
                } 
            }
 
            Reset();
            return outputData;
        }
 
        /// 
        ///     Prepare the cryptographic key for use in the encryption / decryption operation 
        ///  
        // 
        //  
        // 
        // 
        // 
        //  
        // 
        //  
        //  
        // 
        [System.Security.SecurityCritical] 
        private static SafeCapiKeyHandle SetupKey(SafeCapiKeyHandle key, byte[] iv, CipherMode cipherMode, int feedbackSize) {
            Contract.Requires(key != null);
            Contract.Requires(cipherMode == CipherMode.ECB || iv != null);
            Contract.Requires(0 <= feedbackSize); 
            Contract.Ensures(Contract.Result() != null &&
                             !Contract.Result().IsInvalid && 
                             !Contract.Result().IsClosed); 

            // Make a copy of the key so that we don't modify the properties of the caller's copy 
            SafeCapiKeyHandle encryptionKey = key.Duplicate();

            // Setup the cipher mode first
            CapiNative.SetKeyParameter(encryptionKey, CapiNative.KeyParameter.Mode, (int)cipherMode); 

            // If we're not in ECB mode then setup the IV 
            if (cipherMode != CipherMode.ECB) { 
                CapiNative.SetKeyParameter(encryptionKey, CapiNative.KeyParameter.IV, iv);
            } 

            // OFB and CFB require a feedback loop size
            if (cipherMode == CipherMode.CFB || cipherMode == CipherMode.OFB) {
                CapiNative.SetKeyParameter(encryptionKey, CapiNative.KeyParameter.ModeBits, feedbackSize); 
            }
 
            return encryptionKey; 
        }
    } 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK