使用 CryptoAPI Next Generation (CNG) 加密数据的直接示例

问题描述 投票:0回答:2

我想在 Windows 上运行的 C++ 应用程序中实现数据加密和解密。我花了相当多的时间浏览网络,我认为我应该使用 Windows 加密 API:下一代 (CNG) 函数(尽管我愿意接受更好的替代方案)。

我发现到处都是复杂的例子,可以做各种各样的事情。我对这方面不太有信心,所以我想找一个简单的例子。最后,我需要一个接受字符串并加密的方法,以及另一个将数据解密回字符串的方法。用户将为这两项操作提供密码。

这肯定已经做过无数次了。有人能给我一个完整且有能力的例子吗?最终,我将得到一个

Encrypt()
Decrypt()
方法。

既安全又高性能的东西将是理想的。

c++ windows encryption aes cng
2个回答
4
投票

在加密(和解密)之前,您需要使用密钥派生函数从密码派生密钥(例如使用 SHA256 的 PBKDF2)。防止预先计算的字典攻击 除了密码之外,您还需要随机字符串(称为盐)。
接下来选择密码算法(具有 256 位密钥的 AES 是不错的选择)和 密码模式(ECB 密码模式被认为较弱,因此使用任何其他密码模式,例如 CBC)。此外,它还需要一个随机字符串(称为初始化向量)。

所以加密算法将是:

  1. 生成随机盐
  2. 派生密钥(密码,盐)=密钥
  3. 生成随机IV
  4. 加密(密钥,IV,明文)=密文

输入参数:明文、密码
输出参数:密文、盐、IV

解密算法为:

  1. 派生密钥(密码,盐)=密钥
  2. 解密(密钥,iv,密文)=明文
    输入参数:密文、盐、iv、密码
    输出参数:纯文本

示例代码:

#include <Windows.h>
#include <iostream>
#include <vector>
#include <array>

#pragma comment(lib, "bcrypt")

static NTSTATUS gen_random(BYTE* buf, ULONG buf_len)
{
    BCRYPT_ALG_HANDLE hAlg = nullptr;
    NTSTATUS status = NTE_FAIL;
    do {
        status = BCryptOpenAlgorithmProvider(&hAlg, L"RNG", nullptr, 0);
        if (status != ERROR_SUCCESS) {
            return status;
        }
        status = BCryptGenRandom(hAlg, buf, buf_len, 0);
    } while (0);
    if (hAlg) {
        BCryptCloseAlgorithmProvider(hAlg, 0);
    }
    return status;
}

static NTSTATUS derive_key(BYTE* pass, ULONG pass_len, BYTE* salt,
                           ULONG salt_len, const ULONG iteration, BYTE* derived_key, ULONG derived_key_len)
{
    BCRYPT_ALG_HANDLE hPrf = nullptr;
    NTSTATUS status = ERROR_SUCCESS;
    do {
        status = BCryptOpenAlgorithmProvider(&hPrf, L"SHA256", nullptr, BCRYPT_ALG_HANDLE_HMAC_FLAG);
        if (status != ERROR_SUCCESS) {
            break;
        }
        status = BCryptDeriveKeyPBKDF2(hPrf, pass, pass_len, salt, salt_len, iteration, derived_key, derived_key_len, 0);
    } while (0);
    if (hPrf) {
        BCryptCloseAlgorithmProvider(hPrf, 0);
    }
    return status;
}

static NTSTATUS do_encrypt(BYTE* key, ULONG key_len, BYTE* plain_text, ULONG plain_text_len,
                           std::vector<BYTE>& iv, std::vector<BYTE>& cipher_text)
{
    NTSTATUS status = NTE_FAIL;
    BCRYPT_ALG_HANDLE hAlg = nullptr;
    BCRYPT_KEY_HANDLE hKey = nullptr;
    do {
        status = BCryptOpenAlgorithmProvider(&hAlg, L"AES", nullptr, 0);
        if (status != ERROR_SUCCESS) {
            break;
        }

        /* create key object */
        status = BCryptGenerateSymmetricKey(hAlg, &hKey, nullptr, 0, key, key_len, 0);
        if (status != ERROR_SUCCESS) {
            break;
        }

        /* set chaining mode */
        std::wstring mode = BCRYPT_CHAIN_MODE_CBC;
        BYTE* ptr = reinterpret_cast<BYTE*>(const_cast<wchar_t*>(mode.data()));
        ULONG size = static_cast<ULONG>(sizeof(wchar_t) * (mode.size() + 1));
        status = BCryptSetProperty(hAlg, BCRYPT_CHAINING_MODE, ptr, size, 0);
        if (status != ERROR_SUCCESS) {
            break;
        }

        /* generate iv */
        ULONG block_len = 0;
        ULONG res = 0;
        status = BCryptGetProperty(hAlg, BCRYPT_BLOCK_LENGTH, reinterpret_cast<BYTE*>(&block_len), sizeof(block_len), &res, 0);
        if (status != ERROR_SUCCESS) {
            break;
        }
        iv.resize(block_len);
        status = gen_random(iv.data(), static_cast<ULONG>(iv.size()));
        if (status != ERROR_SUCCESS) {
            break;
        }

        /* BCryptEncrypt modify iv parameter, so we need to make copy */
        std::vector<BYTE> iv_copy = iv;

        /* get cipher text length */
        ULONG cipher_text_len = 0;
        status = BCryptEncrypt(hKey, plain_text, plain_text_len, nullptr, iv_copy.data(), static_cast<ULONG>(iv_copy.size()),
                               nullptr, cipher_text_len, &cipher_text_len, BCRYPT_BLOCK_PADDING);
        if (status != ERROR_SUCCESS) {
            break;
        }
        cipher_text.resize(static_cast<size_t>(cipher_text_len));

        /* now encrypt */
        status = BCryptEncrypt(hKey, plain_text, plain_text_len, nullptr, iv_copy.data(), static_cast<ULONG>(iv_copy.size()),
                               cipher_text.data(), cipher_text_len, &cipher_text_len, BCRYPT_BLOCK_PADDING);
    } while (0);
    /* cleanup */
    if (hKey) {
        BCryptDestroyKey(hKey);
    }
    if (hAlg) {
        BCryptCloseAlgorithmProvider(hAlg, 0);
    }
    return status;
}

static NTSTATUS do_decrypt(BYTE* key, ULONG key_len, BYTE* cipher_text, ULONG cipher_text_len,
                           const std::vector<BYTE>& iv, std::vector<BYTE>& plain_text)
{
    NTSTATUS status = NTE_FAIL;
    BCRYPT_ALG_HANDLE hAlg = nullptr;
    BCRYPT_KEY_HANDLE hKey = nullptr;
    do {
        status = BCryptOpenAlgorithmProvider(&hAlg, L"AES", nullptr, 0);
        if (status != ERROR_SUCCESS) {
            break;
        }

        /* create key object */
        status = BCryptGenerateSymmetricKey(hAlg, &hKey, nullptr, 0, key, key_len, 0);
        if (status != ERROR_SUCCESS) {
            break;
        }

        /* set chaining mode */
        std::wstring mode = BCRYPT_CHAIN_MODE_CBC;
        BYTE* ptr = reinterpret_cast<BYTE*>(const_cast<wchar_t*>(mode.data()));
        ULONG size = static_cast<ULONG>(sizeof(wchar_t) * (mode.size() + 1));
        status = BCryptSetProperty(hAlg, BCRYPT_CHAINING_MODE, ptr, size, 0);
        if (status != ERROR_SUCCESS) {
            break;
        }

        /* BCryptEncrypt modify iv parameter, so we need to make copy */
        std::vector<BYTE> iv_copy = iv;

        /* get expected plain text length */
        ULONG plain_text_len = 0;
        status = BCryptDecrypt(hKey, cipher_text, cipher_text_len, nullptr, iv_copy.data(), static_cast<ULONG>(iv_copy.size()),
                               nullptr, plain_text_len, &plain_text_len, BCRYPT_BLOCK_PADDING);
        plain_text.resize(static_cast<size_t>(plain_text_len));

        /* decrypt */
        status = BCryptDecrypt(hKey, cipher_text, cipher_text_len, nullptr, iv_copy.data(), static_cast<ULONG>(iv_copy.size()),
                               plain_text.data(), plain_text_len, &plain_text_len, BCRYPT_BLOCK_PADDING);
        /* actualize size */
        plain_text.resize(static_cast<size_t>(plain_text_len));
    } while (0);
    /* cleanup */
    if (hKey) {
        BCryptDestroyKey(hKey);
    }
    if (hAlg) {
        BCryptCloseAlgorithmProvider(hAlg, 0);
    }
    return status;
}


NTSTATUS encrypt(BYTE* pass, ULONG pass_len, const std::vector<BYTE>& plain_text,
                 std::vector<BYTE>& salt, std::vector<BYTE>& iv, std::vector<BYTE>& cipher_text)
{
    NTSTATUS status = NTE_FAIL;
    salt.resize(8);
    std::array<BYTE, 32> key{0x00};
    do {
        /* generate salt */
        status = gen_random(salt.data(), static_cast<ULONG>(salt.size()));
        if (status != ERROR_SUCCESS) {
            break;
        }
        /* derive key from password using SHA256 algorithm and 20000 iteration */
        status = derive_key(pass, pass_len, salt.data(), static_cast<ULONG>(salt.size()), 20000, key.data(), key.size());
        if (status != ERROR_SUCCESS) {
            break;
        }
        /* encrypt */
        status = do_encrypt(key.data(), static_cast<ULONG>(key.size()), const_cast<BYTE*>(plain_text.data()),
                            static_cast<ULONG>(plain_text.size()), iv, cipher_text);
    } while (0);
    SecureZeroMemory(key.data(), key.size());
    return status;
}


NTSTATUS decrypt(BYTE* pass, ULONG pass_len, const std::vector<BYTE>& salt, const std::vector<BYTE>& iv,
                 const std::vector<BYTE>& cipher_text, std::vector<BYTE>& plain_text)
{
    NTSTATUS status = NTE_FAIL;
    std::array<BYTE, 32> key{0x00};
    do {
        /* derive key from password using same algorithm, salt and iteraion count */
        status = derive_key(pass, pass_len, const_cast<BYTE*>(salt.data()), static_cast<ULONG>(salt.size()),
                            20000, key.data(), key.size());
        if (status != ERROR_SUCCESS) {
            break;
        }
        /* decrypt */
        status = do_decrypt(key.data(), static_cast<ULONG>(key.size()), const_cast<BYTE*>(cipher_text.data()),
                            static_cast<ULONG>(cipher_text.size()), const_cast<BYTE*>(iv.data()),
                            static_cast<ULONG>(iv.size()), plain_text);
    } while (0);
    SecureZeroMemory(key.data(), key.size());
    return status;
}

-1
投票

如何使用 CryptoAPI Next Generation (CNG):不要使用它。使用一次性密码。

如果您的用户实际收到了密钥,请使用简单但牢不可破的 OTP 或 One Time Pad(请参阅 https://en.wikipedia.org/wiki/One-time_pad)。

使用起来非常方便。

非常容易理解。

只需向用户提供足够的 OTP 密钥,您或某人通过键盘随机输入这些密钥(我告诉过您这很容易),以持续您认为用户的合理使用。

请勿从密码或之前发布者列出的任何其他内容中获取密钥。这是懒惰且不安全的。按照我说的制作密钥,然后它们就不会链接到任何代码源。

不要浪费时间“学习加密和密码学的一些基础知识”(如果该学习不能直接支持您使用 One Time Pad)。

供您阅读一些与 OTP 相关的讨论和定义: https://www.slideshare.net/AsadAli108/3-l4
https://www.slideshare.net/Jonlitan/one-time-pad-cryption-technique

没有一种众所周知的加密技术可以与 OTP 相媲美。示例:OTP 无法通过数学或计算方式分解为数学或计算过程。

我认为您要求的是简单、容易,而不是那么复杂,但安全,而一次性垫就是这样。如果您直接向用户提供密钥,那么没有人(除了有足够信仰的基督徒之外)可以破解它们。

一些链接可以帮助您获得有用的 C 和 C++ 独立示例:

快速搜索给了我这些示例,由于此赏金的时间限制,我尚未测试:

https://www.sanfoundry.com/cpp-program-implement-one-time-pad-algorithm/

http://www.cplusplus.com/forum/beginner/179981/

https://github.com/DDomjosa/One-time-pad-encryption/blob/master/One%20time%20pad%20encryption.cpp

ps:如果你想知道为什么我发帖但不回复评论:我的浏览器似乎不支持这些 Stack Overflow 页面上的“添加评论”,所以我可以发帖但不能(直到 SO 制作他们的页面)向后兼容对我来说足够了)回复或评论除此之外。

© www.soinside.com 2019 - 2024. All rights reserved.