Translate WinApi Crypt to the .NET Framework System.Security.Cryptography

-1

I would like to ask for a help with refactoring following C# code.

The target is to remove all external WinApi call and replace them with methods from System.Security.Cryptography namespace.

private static IntPtr GenerateKey(IntPtr hCryptProv, byte[] keyData)
{
    var hHash = IntPtr.Zero;
    Win32.CryptCreateHash(hCryptProv, Win32.CALG_MD5, IntPtr.Zero, 0, ref hHash);
    var len = (uint)keyData.Length;
    Win32.CryptHashData(hHash, keyData, len, 0);
    var hKey = IntPtr.Zero;
    Win32.CryptDeriveKey(hCryptProv, Win32.CALG_3DES, hHash, 0, ref hKey);
    if (hHash != IntPtr.Zero) Win32.CryptDestroyHash(hHash);
    return hKey;
}

public static byte[] Encrypt(byte[] dataToEncrypt)
{                        
    var keyData = Encoding.ASCII.GetBytes("{2B9B4443-74CE-42A8-8803-076B136B5967}");
    var size = (uint)dataToEncrypt.Length;                
    var buffer = new byte[size * 2];
    Array.Copy(dataToEncrypt, 0, buffer, 0, size);
    var hCryptProv = IntPtr.Zero;
    bool gotcsp = Win32.CryptAcquireContext(ref hCryptProv, null, null, Win32.PROV_RSA_FULL, Win32.CRYPT_VERIFYCONTEXT | Win32.CRYPT_MACHINE_KEYSET);
    if (!gotcsp)
    {
        Win32.CryptAcquireContext(ref hCryptProv, null, null, Win32.PROV_RSA_FULL, Win32.CRYPT_NEWKEYSET | Win32.CRYPT_VERIFYCONTEXT | Win32.CRYPT_MACHINE_KEYSET);
    }                        

    if (hCryptProv == IntPtr.Zero) return null;

    var hKey = GenerateKey(hCryptProv, keyData);
    Win32.CryptEncrypt(hKey, IntPtr.Zero, 1, 0, buffer, ref size, size*2);
    var encryptedData = new byte[size];
    Array.Copy(buffer, 0, encryptedData, 0, size);
    if (hKey != IntPtr.Zero) Win32.CryptDestroyKey(hKey);
    if (hCryptProv != IntPtr.Zero) Win32.CryptReleaseContext(hCryptProv, 0);            
    return encryptedData;
}
/// <summary>
/// WinAPI Imports
/// </summary>
internal class Win32
{
    public const uint PROV_RSA_FULL = 1;
    public const uint NTE_BAD_KEYSET = 0x80090016;
    public const uint CRYPT_NEWKEYSET = 0x00000008;
    public const uint CRYPT_VERIFYCONTEXT = 0xF0000000;
    public const uint CRYPT_MACHINE_KEYSET = 0x00000020;

    public const uint ALG_CLASS_HASH = (4 << 13);
    public const uint ALG_SID_MD5 = 3;
    public const uint CALG_MD5 = (ALG_CLASS_HASH | ALG_SID_MD5);
    public const uint ALG_CLASS_DATA_ENCRYPT = (3 << 13);
    public const uint ALG_TYPE_BLOCK = (3 << 9);
    public const uint ALG_SID_3DES = 3;
    public const uint CALG_3DES = (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_3DES);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool CryptAcquireContext(ref IntPtr hProv, string pszContainer, string pszProvider, uint dwProvType, uint dwFlags);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CryptReleaseContext(IntPtr hProv, uint dwFlags);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CryptDestroyKey(IntPtr hKey);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CryptCreateHash(IntPtr hProv, uint Algid, IntPtr hKey, uint dwFlags, ref IntPtr hHash);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CryptHashData(IntPtr hHash, [In, Out] byte[] pbData, uint dwDataLen, uint dwSize);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CryptDestroyHash(IntPtr hHash);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CryptDeriveKey(IntPtr hProv, uint Algid, IntPtr hHash, uint dwFlags, ref IntPtr hKey);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CryptEncrypt(IntPtr hKey, IntPtr hHash, int Final, uint dwFlags, [In, Out] byte[] pbData, ref uint pdwDataLen, uint dwBufLen);
}

I have tried different combinations, options and encryption providers, and there is no problem to create similar method using System.Security.Cryptography, but the problem is that I need a method that will replace a code. That means that with the same data passed for encryption I must get the same result. And here is a problem. My knowledge of encryption are definitely is not so deep to take into account all nuances of this method.

Can you help me with this issue? I don't mean to give me a link to encryption tutorial, but to tell me what methods with which options I should to use.

[2017-03-28 11:27GMT] Additional information:

I really do not think that it will helps, but there is one of my experimental code that I finish with:

public static List<byte> Encrypt(byte[] toEncrypt)
{
    var databytes = Encoding.ASCII.GetBytes("{2B9B4443-74CE-42A8-8803-076B136B5967}");
    var hashmd5 = new MD5CryptoServiceProvider();
    var keyArray = hashmd5.ComputeHash(databytes);
    hashmd5.Clear();            
    var pdb = new PasswordDeriveBytes(keyArray, new byte[0]);
    var hashKey = pdb.CryptDeriveKey("TripleDES", "MD5", 0, new byte[8]);

    var tdes = new TripleDESCryptoServiceProvider();
    tdes.Key = hashKey;
    tdes.Mode = CipherMode.ECB;
    tdes.Padding = PaddingMode.PKCS7;
    var cTransform = tdes.CreateEncryptor();            
    var resultArray = cTransform.TransformFinalBlock(toEncrypt, 0, toEncrypt.Length);
    tdes.Clear();
    return resultArray.ToList();
}

There was many other variations but no any that give me correct result:

Source data:

private byte[] dataToEncrypt = {224,111,176,138,238,238,238,239,115,109,201,144,89,58,161,0,0,0,0,0,0,0,0};

Original function reurns:

private byte[] originalResult = {31,173,65,161,199,249,73,200,210,74,156,21,36,160,94,137,71,205,15,206,99,105,40,83};

Sample function returns:

private byte[] sampleResult = {211,29,187,125,82,9,240,177,199,133,135,7,132,166,166,164,189,36,126,186,104,79,53,159};
c#
winapi
encryption
asked on Stack Overflow Mar 28, 2017 by Victor • edited Mar 28, 2017 by Victor

1 Answer

0

I lost at least one hour because I thought your code was using RSA (I was mislead by the PROV_RSA_FULL)... In truth it is only doing 3DES encryption...

var keyData = Encoding.ASCII.GetBytes("{2B9B4443-74CE-42A8-8803-076B136B5967}");
byte[] key;

using (PasswordDeriveBytes pdb = new PasswordDeriveBytes(keyData, null))
{
    key = pdb.CryptDeriveKey("TripleDES", "MD5", 0, new byte[8]);
    //Debug.WriteLine(BitConverter.ToString(key));
}

using (var prov = new TripleDESCryptoServiceProvider())
{
    using (var encryptor = prov.CreateEncryptor(key, new byte[8]))
    {
        byte[] encrypted = encryptor.TransformFinalBlock(bytes2, 0, bytes2.Length);
        //Debug.WriteLine(BitConverter.ToString(encrypted));
    }
}

Just out of curiousity I'll post a modified version of the original C# code... I wanted to debug a little the generated key, but it is complex to export it in CryptoAPI, and then the original code wasn't like I would have written it :-)

private static IntPtr GenerateKey(IntPtr hProv, byte[] keyData)
{
    var hHash = IntPtr.Zero;

    try
    {
        bool check = Win32.CryptCreateHash(hProv, Win32.CALG_MD5, IntPtr.Zero, 0, out hHash);

        if (!check)
        {
            throw new Win32Exception();
        }

        check = Win32.CryptHashData(hHash, keyData, keyData.Length, 0);

        if (!check)
        {
            throw new Win32Exception();
        }

        IntPtr hKey;

        check = Win32.CryptDeriveKey(hProv, Win32.CALG_3DES, hHash, Win32.CRYPT_EXPORTABLE, out hKey);

        if (!check)
        {
            throw new Win32Exception();
        }

        return hKey;
    }
    finally
    {
        if (hHash != IntPtr.Zero)
        {
            Win32.CryptDestroyHash(hHash);
        }
    }
}

public static byte[] Encrypt(byte[] plainText)
{
    var keyData = Encoding.ASCII.GetBytes("{2B9B4443-74CE-42A8-8803-076B136B5967}");

    IntPtr hCryptProv = IntPtr.Zero;
    IntPtr hKey = IntPtr.Zero;

    try
    {
        bool check = Win32.CryptAcquireContext(out hCryptProv, null, null, Win32.PROV_RSA_FULL, Win32.CRYPT_VERIFYCONTEXT | Win32.CRYPT_MACHINE_KEYSET);

        if (!check)
        {
            check = Win32.CryptAcquireContext(out hCryptProv, null, null, Win32.PROV_RSA_FULL, Win32.CRYPT_NEWKEYSET | Win32.CRYPT_VERIFYCONTEXT | Win32.CRYPT_MACHINE_KEYSET);

            if (!check)
            {
                throw new Win32Exception();
            }
        }

        hKey = GenerateKey(hCryptProv, keyData);

        //byte[] key = ExportSymmetricKey(hCryptProv, hKey);
        //Debug.WriteLine(BitConverter.ToString(key));

        var size = plainText.Length;
        check = Win32.CryptEncrypt(hKey, IntPtr.Zero, 1, 0, null, ref size, 0);

        if (!check)
        {
            throw new Win32Exception();
        }

        var cypherText = new byte[size];

        Array.Copy(plainText, cypherText, plainText.Length);

        size = plainText.Length;

        check = Win32.CryptEncrypt(hKey, IntPtr.Zero, 1, 0, cypherText, ref size, cypherText.Length);

        if (!check)
        {
            throw new Win32Exception();
        }

        return cypherText;
    }
    finally
    {
        if (hKey != IntPtr.Zero)
        {
            Win32.CryptDestroyKey(hKey);
        }

        if (hCryptProv != IntPtr.Zero)
        {
            Win32.CryptReleaseContext(hCryptProv, 0);
        }
    }
}

// Based on https://books.google.it/books?id=aL3P3eJdiREC&pg=PT271&lpg=PT271&dq=PROV_RSA_FULL+CryptEncrypt&source=bl&ots=STsuConTHr&sig=W-BWwch8aZ-RqFb8N67rMHTrqYc&hl=it&sa=X&ved=0ahUKEwit2qKnlfvSAhWCtRQKHbL9BbQQ6AEIQzAF#v=onepage&q=PROV_RSA_FULL%20CryptEncrypt&f=false
// Page 248
private static byte[] ExportSymmetricKey(IntPtr hProv, IntPtr hKey)
{
    IntPtr hExpKey = IntPtr.Zero;

    try
    {
        bool check = Win32.CryptGenKey(hProv, 1 /* AT_KEYEXCHANGE */, 1024 << 16, out hExpKey);

        if (!check)
        {
            throw new Win32Exception();
        }

        int size = 0;
        check = Win32.CryptExportKey(hKey, hExpKey, 1 /* SIMPLEBLOB */, 0, null, ref size);

        if (!check)
        {
            throw new Win32Exception();
        }

        var bytes = new byte[size];

        check = Win32.CryptExportKey(hKey, hExpKey, 1 /* SIMPLEBLOB */, 0, bytes, ref size);

        if (!check)
        {
            throw new Win32Exception();
        }

        // The next lines could be optimized by using a CryptDecrypt 
        // that accepts a IntPtr and adding directly 12 to the ref bytes
        // instead of copying around the byte array

        // 12 == sizeof(BLOBHEADER) + sizeof(ALG_ID)
        var bytes2 = new byte[size - 12];

        Array.Copy(bytes, 12, bytes2, 0, bytes2.Length);

        bytes = bytes2;
        bytes2 = null;

        check = Win32.CryptDecrypt(hExpKey, IntPtr.Zero, true, 0, bytes, ref size);

        if (!check)
        {
            throw new Win32Exception();
        }

        Array.Resize(ref bytes, size);

        return bytes;
    }
    finally
    {
        if (hExpKey != IntPtr.Zero)
        {
            Win32.CryptDestroyKey(hExpKey);
        }
    }
}

/// <summary>
/// WinAPI Imports
/// </summary>
internal class Win32
{
    public const uint PROV_RSA_FULL = 1;
    public const uint NTE_BAD_KEYSET = 0x80090016;
    public const uint CRYPT_NEWKEYSET = 0x00000008;
    public const uint CRYPT_VERIFYCONTEXT = 0xF0000000;
    public const uint CRYPT_MACHINE_KEYSET = 0x00000020;

    public const uint CRYPT_EXPORTABLE = 1;

    public const uint ALG_CLASS_HASH = (4 << 13);
    public const uint ALG_SID_MD5 = 3;
    public const uint CALG_MD5 = (ALG_CLASS_HASH | ALG_SID_MD5);
    public const uint ALG_CLASS_DATA_ENCRYPT = (3 << 13);
    public const uint ALG_TYPE_BLOCK = (3 << 9);
    public const uint ALG_SID_3DES = 3;
    public const uint CALG_3DES = (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_3DES);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool CryptAcquireContext(out IntPtr hProv, string pszContainer, string pszProvider, uint dwProvType, uint dwFlags);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CryptReleaseContext(IntPtr hProv, uint dwFlags);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CryptDestroyKey(IntPtr hKey);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CryptCreateHash(IntPtr hProv, uint Algid, IntPtr hKey, uint dwFlags, out IntPtr hHash);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CryptHashData(IntPtr hHash, [In, Out] byte[] pbData, int dwDataLen, uint dwSize);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CryptDestroyHash(IntPtr hHash);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CryptDeriveKey(IntPtr hProv, uint Algid, IntPtr hHash, uint dwFlags, out IntPtr hKey);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CryptGenKey(IntPtr hProv, uint Algid, uint dwFlags, out IntPtr hKey);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CryptEncrypt(IntPtr hKey, IntPtr hHash, int final, uint dwFlags, [In, Out] byte[] pbData, ref int pdwDataLen, int dwBufLen);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CryptExportKey(IntPtr hKey, IntPtr hExpKey, uint dwBlobType, uint dwFlags, [Out] byte[] pbData, ref int pdwDataLen);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CryptGetKeyParam(IntPtr hKey, uint dwParam, [Out] byte[] pbData, ref int pdwDataLen, uint dwFlags);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CryptDecrypt(IntPtr hKey, IntPtr hHash, bool final, uint dwFlags, [In, Out] byte[] pbData, ref int pdwDataLen);
}
answered on Stack Overflow Mar 29, 2017 by xanatos • edited Mar 29, 2017 by xanatos

User contributions licensed under CC BY-SA 3.0