3

I've made a small application to encrypt and decrypt some text. All is fine as long as I use the byte array straight from the encryption. But as soon as I make a copy of the array to mimic the process of sending the encrypted text as a file the decryption will not run.

Why am I unable the run the decryption using a copied array?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;

using System.Runtime.InteropServices.WindowsRuntime;
using System.Runtime.InteropServices;
using System.IO;


namespace EncryptDecryptApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            //Text to encrypt
            string plaintext = "Text to encrypt and decrypt!";

            // Password
            const string password = "password";

            // Constants used in cryptography functions
            const string MS_ENH_RSA_AES_PROV = "Microsoft Enhanced RSA and AES Cryptographic Provider"; //Name of provider. Same as "Microsoft AES Cryptographic Provider".
            const byte PROV_RSA_AES = 24;         //Type of provider
            const string KeyContainer = null;     //Name of the key container to be used, if NULL then a default key container name is used. Must be a null-terminated string.
            const uint ALG_CLASS_HASH = (4 << 13); //32768 = 4*2^13; //Samma tror jag för alla hashalgoritmer
            const uint ALG_TYPE_ANY = (0);       //Samma tror jag för alla hashalgoritmer
            const uint ALG_SID_SHA_256 = 12;    //ALG_SID_MD5 = 3, ALG_SID_SHA = 4, ALG_SID_SHA_256 = 12, ALG_SID_SHA_384 = 13, ALG_SID_SHA_512 = 14
            const uint CALG_SHA_256 = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_256);
            const int ALG_CLASS_DATA_ENCRYPT = 24576;
            const int ALG_TYPE_BLOCK = 1536;       //used in all types of AES, and in RC2
            const int ALG_SID_AES_256 = 16; //ALG_SID_AES_128 = 14, ALG_SID_AES_192 = 15, ALG_SID_AES_256 = 16
            const int CALG_AES_256 = (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES_256);
            const int ENCRYPT_ALGORITHM = CALG_AES_256;

            // Obtain handle to Cryptographic Service Provider (CSP)
            string ProviderCSP = MS_ENH_RSA_AES_PROV + null;     //name of the CSP to be used. Must be a null-terminated string.
            IntPtr CSPhandle = new IntPtr();
            Crypt32.CryptAcquireContext(ref CSPhandle, KeyContainer, ProviderCSP, PROV_RSA_AES, 0);

            //Create hash object
            IntPtr handleHashObj = new IntPtr();
            Crypt32.CryptCreateHash(CSPhandle, CALG_SHA_256, IntPtr.Zero, 0, ref handleHashObj);

            //Hash password
            byte[] pwByteArray = Encoding.Unicode.GetBytes(password);
            uint pwByteAmt = (uint)ASCIIEncoding.Unicode.GetByteCount(password);
            Crypt32.CryptHashData(handleHashObj, pwByteArray, pwByteAmt, 0);

            //Dervie session key from the hashed password
            IntPtr handleSessionKey = new IntPtr();
            Crypt32.CryptDeriveKey(CSPhandle, ENCRYPT_ALGORITHM, handleHashObj, 0, ref handleSessionKey);

            //CryptEncrypt iteration no 1 - Obtain buffer size (output ByteAmt_Itr1)
            byte[] byteArray = new byte[plaintext.Length * sizeof(char)];
            System.Buffer.BlockCopy(plaintext.ToCharArray(), 0, byteArray, 0, byteArray.Length);

            uint byteAmt_Itr1 = (uint)byteArray.Length; //No of bytes, i.e. the size, of the plaintext.
            uint bufferSize_Itr1 = byteAmt_Itr1; //Set buffer size to input data size for now

            Crypt32.CryptEncrypt(handleSessionKey, IntPtr.Zero, 1, 0, null, ref byteAmt_Itr1, 0);

            //CryptEncrypt iteration no 2 - Encryption
            uint byteAmt_Itr2 = (uint)byteArray.Length; //No of bytes, i.e. the size, of the plaintext.
            uint bufferSize_Itr2 = byteAmt_Itr1; //Output from iteration no 1 - size of output data, i.e. correct buffer size

            Crypt32.CryptEncrypt(handleSessionKey, IntPtr.Zero, 1, 0, byteArray, ref byteAmt_Itr2, bufferSize_Itr2);

            Console.WriteLine("Encrypted: " + Encoding.Default.GetString(byteArray));

            // Text encrypted as byteArray, try to decrypt it! //

            //CryptDecrypt - with input from CryptEncrypt".
            Console.WriteLine(Crypt32.CryptDecrypt(handleSessionKey, IntPtr.Zero, 1, 0, byteArray, ref byteAmt_Itr2));

            //Convert decrypted byte array to string
            char[] chars = new char[byteArray.Length / sizeof(char)];
            System.Buffer.BlockCopy(byteArray, 0, chars, 0, byteArray.Length);
            string decryptedText = new string(chars);
            Console.WriteLine("Decrypted: " + decryptedText);
        }
    }

    public class Crypt32
    {
        [DllImport("advapi32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CryptAcquireContext(
            ref IntPtr hProv,
            string pszContainer,
            string pszProvider,
            uint dwProvType,
            uint dwFlags);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CryptCreateHash(
            IntPtr hProv,
            uint algId,
            IntPtr hKey,
            uint dwFlags,
            ref IntPtr phHash);

        [DllImport("advapi32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CryptHashData(
            IntPtr hHash,
            byte[] pbData,
            uint dataLen,
            uint flags);

        [DllImport("advapi32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CryptDeriveKey(
            IntPtr hProv,
            int Algid,
            IntPtr hBaseData,
            int flags,
            ref IntPtr phKey);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CryptEncrypt(
            IntPtr hKey,
            IntPtr hHash,
            int Final,
            uint dwFlags,
            byte[] pbData,
            ref uint pdwDataLen,
            uint dwBufLen);

        [DllImport("advapi32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CryptDecrypt(
            IntPtr hKey,
            IntPtr hHash,
            int Final,
            uint dwFlags,
            byte[] pbData,
            ref uint pdwDataLen);
    }
}

Output:

Encrypted:
B'♦tt'sô?*ý¢┼àò⌂9Z▼?£'$'¥«çæOÆà[/ë?·UÛÙªÄ2?┼[É{&IâínaÇe
True
Decrypted: Text to encrypt and decrypt!

I've sorted out the other inputs to CryptDecrypt (have made another handleSessionKey and also another variable instead of byteAtm_Itr2), they are not the source of the problem. The problem seems to be with the byte array.

If I use this code (or Buffer.BlockCopy) to copy the array the decryption will fail:

byte[] byteArray2 = new byte[byteArray.Length];
byteArray.CopyTo(byteArray2, 0);

The arrays are the same but of course not the same object. Can't work out why this wouldn't work. If I run the code with the copied array this is the output:

Encrypted:
B'♦tt'sô?*ý¢┼àò⌂9Z▼?£'$'¥«çæOÆà[/ë?·UÛÙªÄ2?┼[É{&IâínaÇe
False
Decrypted: Text to encrypt and decr????
Oakgrove
  • 41
  • 1
  • 6
  • Looks like you're using Unicode encoding on the input side, but trying to directly cast bytes to chars on the output side. You need to use `Encoding.Unicode.GetString(byteArray)` to have any hope of getting a real string out of those bytes. – Glorin Oakenfoot Jan 19 '16 at 14:22
  • 1
    With encryption don't use Unicode encoding always use UTF8. – jdweng Jan 19 '16 at 14:24
  • 1
    Completely unrelated, but do you _really_ have to use this byzantine API? There is a lot of good stuff in `System.Cryptography`. – Anton Gogolev Jan 19 '16 at 14:49
  • @AntonGogolev - No, not really. But the encryption is made in VBA and I wanted to decrypt it the same way using C#. I will probably change to another approach if I don't solve this. – Oakgrove Jan 19 '16 at 14:58
  • Ok, I made sure to really check the output and I get some things right. But I would like to have "true" returned from the decryption. – Oakgrove Jan 19 '16 at 15:38

1 Answers1

3

Because you are using Unicode, that uses two bytes per character, you are not reserving enough space for the original byteArray. You need to allocate twice as much space as your currently doing:

  byte[] byteArray = new byte[2*plaintext.Length*sizeof (char)];

After changing this code, you can safely copy the array and decrypt the copied array. It seems can decrypt the original array as the Marshalled library correctly recognised the array length (that it created in the first place). However, when using Array.Copy these additional bytes do not get transferred by the .NET code.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;

using System.Runtime.InteropServices.WindowsRuntime;
using System.Runtime.InteropServices;
using System.IO;


namespace EncryptDecryptApplication
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            //Text to encrypt
            string plaintext = "Text to encrypt and decrypt!";

            // Password
            const string password = "password";

            // Constants used in cryptography functions
            const string MS_ENH_RSA_AES_PROV = "Microsoft Enhanced RSA and AES Cryptographic Provider";
                //Name of provider. Same as "Microsoft AES Cryptographic Provider".
            const byte PROV_RSA_AES = 24; //Type of provider
            const string KeyContainer = null;
                //Name of the key container to be used, if NULL then a default key container name is used. Must be a null-terminated string.
            const uint ALG_CLASS_HASH = (4 << 13); //32768 = 4*2^13; //Samma tror jag för alla hashalgoritmer
            const uint ALG_TYPE_ANY = (0); //Samma tror jag för alla hashalgoritmer
            const uint ALG_SID_SHA_256 = 12;
                //ALG_SID_MD5 = 3, ALG_SID_SHA = 4, ALG_SID_SHA_256 = 12, ALG_SID_SHA_384 = 13, ALG_SID_SHA_512 = 14
            const uint CALG_SHA_256 = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_256);
            const int ALG_CLASS_DATA_ENCRYPT = 24576;
            const int ALG_TYPE_BLOCK = 1536; //used in all types of AES, and in RC2
            const int ALG_SID_AES_256 = 16; //ALG_SID_AES_128 = 14, ALG_SID_AES_192 = 15, ALG_SID_AES_256 = 16
            const int CALG_AES_256 = (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES_256);
            const int ENCRYPT_ALGORITHM = CALG_AES_256;

            // Obtain handle to Cryptographic Service Provider (CSP)
            string ProviderCSP = MS_ENH_RSA_AES_PROV + null;
                //name of the CSP to be used. Must be a null-terminated string.
            IntPtr CSPhandle = new IntPtr();
            Crypt32.CryptAcquireContext(ref CSPhandle, KeyContainer, ProviderCSP, PROV_RSA_AES, 0);

            //Create hash object
            IntPtr handleHashObj = new IntPtr();
            Crypt32.CryptCreateHash(CSPhandle, CALG_SHA_256, IntPtr.Zero, 0, ref handleHashObj);

            //Hash password
            byte[] pwByteArray = Encoding.Unicode.GetBytes(password);
            uint pwByteAmt = (uint) ASCIIEncoding.Unicode.GetByteCount(password);
            Crypt32.CryptHashData(handleHashObj, pwByteArray, pwByteAmt, 0);

            //Dervie session key from the hashed password
            IntPtr handleSessionKey = new IntPtr();
            Crypt32.CryptDeriveKey(CSPhandle, ENCRYPT_ALGORITHM, handleHashObj, 0, ref handleSessionKey);

            //CryptEncrypt iteration no 1 - Obtain buffer size (output ByteAmt_Itr1)
            byte[] byteArray = new byte[2*plaintext.Length*sizeof (char)];
            System.Buffer.BlockCopy(plaintext.ToCharArray(), 0, byteArray, 0, byteArray.Length/2);

            uint byteAmt_Itr1 = (uint) byteArray.Length; //No of bytes, i.e. the size, of the plaintext.
            uint bufferSize_Itr1 = byteAmt_Itr1; //Set buffer size to input data size for now

            Crypt32.CryptEncrypt(handleSessionKey, IntPtr.Zero, 1, 0, null, ref byteAmt_Itr1, 0);

            //CryptEncrypt iteration no 2 - Encryption
            uint byteAmt_Itr2 = (uint) byteArray.Length; //No of bytes, i.e. the size, of the plaintext.
            uint bufferSize_Itr2 = byteAmt_Itr1;
                //Output from iteration no 1 - size of output data, i.e. correct buffer size

            Crypt32.CryptEncrypt(handleSessionKey, IntPtr.Zero, 1, 0, byteArray, ref byteAmt_Itr2, bufferSize_Itr2);

            Console.WriteLine("Encrypted: " + Encoding.Default.GetString(byteArray));

            // Text encrypted as byteArray, try to decrypt it! //

            byte[] byteArray2 = new byte[byteArray.Length];
            byteArray.CopyTo(byteArray2, 0);

            //CryptDecrypt - with input from CryptEncrypt".
            Console.WriteLine(Crypt32.CryptDecrypt(handleSessionKey, IntPtr.Zero, 1, 0, byteArray2, ref byteAmt_Itr2));

            //Convert decrypted byte array to string
            string decryptedText = Encoding.Unicode.GetString(byteArray2).Split(new char[] { '\0' }, StringSplitOptions.RemoveEmptyEntries)[0];
            //char[] chars = new char[byteArray.Length / sizeof(char)];
            //System.Buffer.BlockCopy(byteArray, 0, chars, 0, byteArray.Length);
            //string decryptedText = new string(chars);
            Console.WriteLine("Decrypted: " + decryptedText);
        }
    }

    public class Crypt32
    {
        [DllImport("advapi32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CryptAcquireContext(
            ref IntPtr hProv,
            string pszContainer,
            string pszProvider,
            uint dwProvType,
            uint dwFlags);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CryptCreateHash(
            IntPtr hProv,
            uint algId,
            IntPtr hKey,
            uint dwFlags,
            ref IntPtr phHash);

        [DllImport("advapi32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CryptHashData(
            IntPtr hHash,
            byte[] pbData,
            uint dataLen,
            uint flags);

        [DllImport("advapi32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CryptDeriveKey(
            IntPtr hProv,
            int Algid,
            IntPtr hBaseData,
            int flags,
            ref IntPtr phKey);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CryptEncrypt(
            IntPtr hKey,
            IntPtr hHash,
            int Final,
            uint dwFlags,
            byte[] pbData,
            ref uint pdwDataLen,
            uint dwBufLen);

        [DllImport("advapi32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CryptDecrypt(
            IntPtr hKey,
            IntPtr hHash,
            int Final,
            uint dwFlags,
            byte[] pbData,
            ref uint pdwDataLen);
    }
}
Alex
  • 21,273
  • 10
  • 61
  • 73
  • I added the full code including copying to byteArray2. This correctly decrypts the input string. – Alex Jan 19 '16 at 15:14
  • 1
    As you are using UniCode you are not reserving enough space for `byteArray`. The Marshalled libraries can handle this, but not Array.Copy – Alex Jan 19 '16 at 15:48
  • The return value is still false... With a little more testing I found that the output seems to be ok with "Text to encrypt and decrypt!" but not with "This is a text to encrypt and decrypt!". Why is that? – Oakgrove Jan 20 '16 at 09:52
  • 1
    C# doesn't handle the string correctly.`\0` encodes the end of a string in C, however, C# reads characters after that. If you use the following line you will get the correct string: `string decryptedText = Encoding.Unicode.GetString(byteArray2).Split(new char[] { '\0' }, StringSplitOptions.RemoveEmptyEntries)[0];` I have updated my answer with this correction. – Alex Jan 20 '16 at 10:28
  • Ok, perfect! Now I get the correct string. But the return value is still "false" but I feel it's time to let go of that. Thanks again! – Oakgrove Jan 20 '16 at 11:25
  • I think the return value is `false` because C# and your marshalled code handle arrays in a slightly different way. I am not sure there is an easy way around this. – Alex Jan 20 '16 at 11:36