Wincrypt:无法解密用 C# 加密的文件。 CryptDecrypt 的 NTE_BAD_DATA

发布于 2024-08-06 19:33:11 字数 3102 浏览 5 评论 0原文

我正在尝试使用 wincrypt 解密文件的一部分,但似乎无法使该函数正确解密。这些字节使用 C# 中的 RC2 实现进行加密,并且我向加密和解密过程提供相同的密码和 IV(在 C# 中加密,在 C++ 中解密)。

我一路上的所有函数都返回 true,直到最终的“CryptDecrypt”函数。我不用再打字了,这里是函数:

static char* DecryptMyFile(char *input, char *password, int size)
{
    HCRYPTPROV provider = NULL;

    if(CryptAcquireContext(&provider, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, 0))
    {printf("Context acquired.");}
    else
    {
        if (GetLastError() == NTE_BAD_KEYSET)
        {
        if(CryptAcquireContext(&provider, 0, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET))
            {printf("new key made.");}
            else
            {
                printf("Could not acquire context.");
            }
        }
        else
        {printf("Could not acquire context.");}
    }

    HCRYPTKEY key = NULL;
    HCRYPTHASH hash = NULL;

    if(CryptCreateHash(provider, CALG_MD5, 0, 0, &hash))
    {printf("empty hash created.");}
    else
    {printf("could not create hash.");}

    if(CryptHashData(hash, (BYTE *)password, strlen(password), 0))
    {printf("data buffer is added to hash.");}
    else
    {printf("error. could not add data buffer to hash.");}

    if(CryptDeriveKey(provider, CALG_RC2, hash, 0, &key)) 
    {printf("key derived.");}
    else
    {printf("Could not derive key.");}

    DWORD dwKeyLength = 128;

if(CryptSetKeyParam(key, KP_EFFECTIVE_KEYLEN, reinterpret_cast<BYTE*>(&dwKeyLength), 0))
    {printf("success");}
    else
    {printf("failed.");}

    BYTE IV[8] = {0,0,0,0,0,0,0,0};

    if(CryptSetKeyParam(key, KP_IV, IV, 0))
    {printf("worked");}
    else
    {printf("faileD");}

    DWORD dwCount = size;
    BYTE *decrypted = new BYTE[dwCount + 1];

    memcpy(decrypted, input, dwCount);
    decrypted[dwCount] = 0;


    if(CryptDecrypt(key,0, true, 0, decrypted, &dwCount))
    {printf("succeeded");}
    else
    {printf("failed");}

return (char *)decrypted;
}

输入是传递给函数的数据,经过加密。密码与 C# 中用于加密数据的密码相同。 size 是加密时数据的大小。
所有上述函数在 CryptDecrypt 之前都返回 true,我似乎无法弄清楚为什么。同时,我不确定 CryptDecrypt 函数如何编辑我的“解密”变量,因为我没有传递它的引用。

任何关于为什么这不起作用的帮助或建议将不胜感激。这是我第一次尝试使用 wincrypt,也是多年来第一次使用 C++。

如果还有更多帮助的话,这是我的加密(在 C# 中):

 public static byte[] EncryptString(byte[] input, string password)
    {
        PasswordDeriveBytes pderiver = new PasswordDeriveBytes(password, null);
        byte[] ivZeros = new byte[8];
        byte[] pbeKey = pderiver.CryptDeriveKey("RC2", "MD5", 128, ivZeros);

        RC2CryptoServiceProvider RC2 = new RC2CryptoServiceProvider();

        //using an empty initialization vector for convenience.
        byte[] IV = new byte[8];
        ICryptoTransform encryptor = RC2.CreateEncryptor(pbeKey, IV);

        MemoryStream msEncrypt = new MemoryStream();
        CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);
        csEncrypt.Write(input, 0, input.Length);
        csEncrypt.FlushFinalBlock();

        return msEncrypt.ToArray();
    }

我已经确认我在 C++ 中的哈希值与我在 C# 中的密钥相同,由 PasswordDeriveBytes.CryptDeriveKey 创建

I am trying to decrypt a piece of a file with wincrypt and I cannot seem to make this function decrypt correctly. The bytes are encrypted with the RC2 implementation in C# and I am supplying the same password and IV to both the encryption and decryption process (encrypted in C#, decrypted in c++).

All of my functions along the way are returning true until the final "CryptDecrypt" function. Instead of me typing out any more, here is the function:

static char* DecryptMyFile(char *input, char *password, int size)
{
    HCRYPTPROV provider = NULL;

    if(CryptAcquireContext(&provider, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, 0))
    {printf("Context acquired.");}
    else
    {
        if (GetLastError() == NTE_BAD_KEYSET)
        {
        if(CryptAcquireContext(&provider, 0, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET))
            {printf("new key made.");}
            else
            {
                printf("Could not acquire context.");
            }
        }
        else
        {printf("Could not acquire context.");}
    }

    HCRYPTKEY key = NULL;
    HCRYPTHASH hash = NULL;

    if(CryptCreateHash(provider, CALG_MD5, 0, 0, &hash))
    {printf("empty hash created.");}
    else
    {printf("could not create hash.");}

    if(CryptHashData(hash, (BYTE *)password, strlen(password), 0))
    {printf("data buffer is added to hash.");}
    else
    {printf("error. could not add data buffer to hash.");}

    if(CryptDeriveKey(provider, CALG_RC2, hash, 0, &key)) 
    {printf("key derived.");}
    else
    {printf("Could not derive key.");}

    DWORD dwKeyLength = 128;

if(CryptSetKeyParam(key, KP_EFFECTIVE_KEYLEN, reinterpret_cast<BYTE*>(&dwKeyLength), 0))
    {printf("success");}
    else
    {printf("failed.");}

    BYTE IV[8] = {0,0,0,0,0,0,0,0};

    if(CryptSetKeyParam(key, KP_IV, IV, 0))
    {printf("worked");}
    else
    {printf("faileD");}

    DWORD dwCount = size;
    BYTE *decrypted = new BYTE[dwCount + 1];

    memcpy(decrypted, input, dwCount);
    decrypted[dwCount] = 0;


    if(CryptDecrypt(key,0, true, 0, decrypted, &dwCount))
    {printf("succeeded");}
    else
    {printf("failed");}

return (char *)decrypted;
}

input is the data passed to the function, encrypted. password is the same password used to encrypt the data in C#. size is the size of the data while encrypted.
All of the above functions return true until CryptDecrypt, which I cannot seem to figure out why. At the same time, I'm not sure how the CryptDecrypt function would possibly edit my "decrypted" variable, since I am not passing a reference of it.

Any help or advice onto why this is not working would be greatly appreciated. This is my first endeavour with wincrypt and first time using C++ in years.

If it is of any more help, as well, this is my encryption (in C#):

 public static byte[] EncryptString(byte[] input, string password)
    {
        PasswordDeriveBytes pderiver = new PasswordDeriveBytes(password, null);
        byte[] ivZeros = new byte[8];
        byte[] pbeKey = pderiver.CryptDeriveKey("RC2", "MD5", 128, ivZeros);

        RC2CryptoServiceProvider RC2 = new RC2CryptoServiceProvider();

        //using an empty initialization vector for convenience.
        byte[] IV = new byte[8];
        ICryptoTransform encryptor = RC2.CreateEncryptor(pbeKey, IV);

        MemoryStream msEncrypt = new MemoryStream();
        CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);
        csEncrypt.Write(input, 0, input.Length);
        csEncrypt.FlushFinalBlock();

        return msEncrypt.ToArray();
    }

I have confirmed that my hash value in C++ is identical to my key in C#, created by PasswordDeriveBytes.CryptDeriveKey

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

记忆里有你的影子 2024-08-13 19:33:11

首先,正如我的评论中所述,使用 GetLastError() 以便您知道失败的原因。我假设您得到了 NTE_BAD_DATA,所有其他错误都更容易处理,因为它们基本上意味着您错过了 API 调用序列中的某些步骤。

CryptDecrypt 因 NTE_BAD_DATA 失败的典型原因是您正在解密块密码的最后一个块(就像您一样)并且解密的填充字节不正确。如果输入被截断(并非所有加密字节都保存到文件中)或者密钥不正确,则可能会发生这种情况。

我建议您有条不紊地采取这一点,因为有很多地方可能会失败,这些都只会在 CryptDecrypt 时显现出来:

  1. 确保您在 C# 中加密的文件可以在 C# 中解密。这将消除任何文件保存截断问题。
  2. 首先尝试使用固定硬编码密钥进行加密和解密(不派生密码),这将确保您的密钥集代码 IV 初始化正确(以及填充模式和密码链接模式)。
  3. 确保密码派生过程得出相同的哈希值。诸如 ANSI 与 Unicode 或终端 0 之类的东西可能会对 MD5 哈希造成严重破坏,并导致表面上相同的密码哈希产生截然不同的密钥。

First, as in my comment, use GetLastError() so you know what it failed. I'll assume that you get NTE_BAD_DATA, all the other errors are much more easier to deal with since they basically mean you missed some step int he API call sequence.

The typical reason why CryptDecrypt would fail with NTE_BAD_DATA would be that you're decrypting the last block of a block cypher (as you are) and the decrypted padding bytes are incorrect. This can happen if the input is truncated (not all encrypted bytes were saved to the file) or if the key is incorrect.

I would suggest you take this methodically since there are so many places where this can fail that will all manifest only at CryptDecrypt time:

  1. Ensure that the file you encrypt in C# can be decrypted in C#. This would eliminate any file save truncation issues.
  2. Try to encrypt and decrypt with fixed hard codded key first (no password derived), this will ensure that your key set code IV initialization are correct (as well as padding mode and cypher chaining mode).
  3. Ensure that the password derivation process arives at the same hash. Things like ANSI vs. Unicode or terminal 0 can wreak havok on the MD5 hash and result in wildly different keys from apparently the same password hash.
御弟哥哥 2024-08-13 19:33:11

有些人在操作系统之间移动时发现了问题。
CryptDeriveKey 调用使用基于所选操作系统和算法的“默认密钥长度”。对于 RC2,默认生成的密钥长度在 Windows 2000 上为 40 位,在 Windows 2003 上为 128 位。当在 CryptDecrypt 调用中使用生成的密钥时,这会导致“BAD DATA”返回代码。

据推测,这与尝试应用 128 位密钥解密 40 位加密流后最终缓冲区末尾出现的“垃圾”有关。错误代码通常表示填充字节错误 - 但根本原因可能是密钥生成问题。

要生成 40 位加密密钥,请在 CryptDeriveKey 调用的标志字段中使用 ( ( 40 << 16 ) )。

Some people have discovered issues when moving between operating systems.
The CryptDeriveKey call uses a "default key length" based on the operating system and algorithm chosen. For RC2, the default generated key length is 40 bits on Windows 2000 and 128 bits on Windows 2003. This results in a "BAD DATA" return code when the generated key is used in a CryptDecrypt call.

Presumably this is related to "garbage" appearing at the end of the final buffer after trying to apply a 128 bit key to decrypt a 40 bit encrypted stream. The error code typically indicates bad padding bytes - but the root cause may be a key generation issue.

To generate a 40 bit encryption key, use ( ( 40 <<16 ) ) in the flags field of the CryptDeriveKey call.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文