11. 加解密实现

大家好,我是程序喵。


前面我介绍过数据库的作用,会在上报前先把数据按一定格式存储到数据库中,用于持久化,防止信息丢失,而且也提到过,存储内容一定要是加密过的数据,防止数据泄漏,如果数据随随便便就以明文形式泄漏到外部,这可是很大的安全事故。


所以,本节我就会主要介绍如何实现加解密模块。


加密一般分为对称加密和非对称加密,对称加密大体指的是双方使用同样的秘钥,A使用一个秘钥加密后的数据,B使用同样的秘钥可以直接解密。非对称加密一般要分为公钥和私钥,公钥和私钥是一对,A使用公钥加密后的数据,B拿相同的公钥无法解密,拿其它的私钥也无法加密,一定要拿和此公钥同时相伴而成的私钥才可以成功解密。


常用的对称加密算法有AES、RC4等,常用的非对称加密算法有RSA、ECC等。给大家出个思考题,https使用的是对称加密还是非对称加密?


在本项目中,我们会使用AES这种经典的对称加密算法,会引入mbedtls三方库来实现加解密功能,关于如何引入mbedtls,可以查看第4节。


下面就直接介绍如何实现项目中的加解密算法模块。


std::string AESCrypt::GetKey(const std::string& salt,
const std::string password) {
int32_t keylen = 32;
uint32_t iterations = 1000;
unsigned char key[32] = {0};
mbedtls_md_context_t md_ctx;
mbedtls_md_init(&md_ctx);
const mbedtls_md_info_t* md_info =
mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
mbedtls_md_setup(&md_ctx, md_info, 1);
mbedtls_md_starts(&md_ctx);
int ret = mbedtls_pkcs5_pbkdf2_hmac(
&md_ctx, reinterpret_cast<const unsigned char*>(password.data()),
password.size(), reinterpret_cast<const unsigned char*>(salt.data()),
salt.size(), iterations, keylen, key);
mbedtls_md_free(&md_ctx);
if (ret != 0) {
return "";
}
return std::string((char*)key, keylen);
}


首先,我们需要了解一下AES加密算法是如何工作的,前面说过AES(Advanced Encryption Standard)是一种对称加密算法,意味着加密和解密使用相同的密钥。在这段代码中,我们就会生成一个用于AES加密的密钥。


通过将salt与用户提供的password相结合,可以生成一个更强大的密钥。使用随机的salt可以防止恶意攻击者使用预先计算好的表来破解密码。


password是用户提供的密码,它结合salt,然后使用PBKDF2算法(Password-Based Key Derivation Function 2)和HMAC-SHA256哈希算法来生成密钥。


PBKDF2算法是一种密码推导函数,它使用 password 和salt作为输入,并通过多次迭代的哈希运算生成一个密钥。在这段代码中,我们使用了1000次迭代和SHA256哈希算法来生成一个32字节(256位)的密钥。


通过将password和salt传递给mbedtls_pkcs5_pbkdf2_hmac函数,我们可以生成一个加密安全的密钥。最后,我们将生成的密钥转换为std::string类型并返回。


class AESImpl {
public:
explicit AESImpl(const std::string& key) { Init(key.data(), key.size()); }

~AESImpl() { UnInit(); }

AESImpl(const AESImpl& other) = delete;
AESImpl& operator=(const AESImpl& other) = delete;

void Init(const char* key, size_t key_size);

void UnInit();

std::string Encrypt(const void* input, size_t input_size);

std::string Decrypt(const void* input, size_t input_size);

private:
mbedtls_cipher_context_t encrypt_ctx_;
mbedtls_cipher_context_t decrypt_ctx_;

uint32_t encrypt_block_size_ = 0;
uint32_t decrypt_block_size_ = 0;

unsigned char iv_[16] = {0};
};

void AESImpl::Init(const char* key, size_t key_size) {
mbedtls_cipher_init(&encrypt_ctx_);
mbedtls_cipher_setup(
&encrypt_ctx_, mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_256_CBC));
mbedtls_cipher_set_padding_mode(&encrypt_ctx_, MBEDTLS_PADDING_PKCS7);
mbedtls_cipher_setkey(&encrypt_ctx_,
reinterpret_cast<const unsigned char*>(key),
key_size * 8, MBEDTLS_ENCRYPT);

encrypt_block_size_ = mbedtls_cipher_get_block_size(&encrypt_ctx_);

mbedtls_cipher_init(&decrypt_ctx_);
mbedtls_cipher_setup(
&decrypt_ctx_, mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_256_CBC));
mbedtls_cipher_set_padding_mode(&decrypt_ctx_, MBEDTLS_PADDING_PKCS7);
mbedtls_cipher_setkey(&decrypt_ctx_,
reinterpret_cast<const unsigned char*>(key),
key_size * 8, MBEDTLS_DECRYPT);

decrypt_block_size_ = mbedtls_cipher_get_block_size(&decrypt_ctx_);
}

void AESImpl::UnInit() {
mbedtls_cipher_free(&encrypt_ctx_);
mbedtls_cipher_free(&decrypt_ctx_);
}

std::string AESImpl::Encrypt(const void* input, size_t input_size) {
mbedtls_cipher_set_iv(&encrypt_ctx_, iv_, sizeof(iv_));
mbedtls_cipher_reset(&encrypt_ctx_);

std::string output(input_size + encrypt_block_size_, 0);
size_t olen = 0;
int ret = mbedtls_cipher_update(
&encrypt_ctx_, reinterpret_cast<const unsigned char*>(input), input_size,
reinterpret_cast<unsigned char*>(output.data()), &olen);
if (ret != 0) {
return "";
}
size_t olen2 = 0;
ret = mbedtls_cipher_finish(
&encrypt_ctx_, reinterpret_cast<unsigned char*>(output.data()) + olen,
&olen2);
if (ret != 0) {
return "";
}
output.resize(olen + olen2);
return output;
}


这段代码实现了一个AES加密算法模块,主要包括以下几个函数及其作用:


  1. AESImpl::Init(const char key, size_t key_size): 该函数用于初始化加密算法模块。它接受一个密钥 key 和密钥的长度 key_size 作为参数,并进行以下操作:
  2. 调用 mbedtls_cipher_init 初始化加密上下文 encrypt_ctx_ 和解密上下文 decrypt_ctx_。
  3. 调用 mbedtls_cipher_setup 配置加密上下文 encrypt_ctx_ 和解密上下文 decrypt_ctx_,指定加密算法类型为 AES-256-CBC。
  4. 调用 mbedtls_cipher_set_padding_mode 设置加密上下文 encrypt_ctx_ 和解密上下文 decrypt_ctx_ 的填充模式为 PKCS7。
  5. 调用 mbedtls_cipher_setkey 设置加密上下文 encrypt_ctx_ 和解密上下文 decrypt_ctx_ 的密钥,将传入的 key 转换为 unsigned char* 类型,并指定密钥的位数为 key_size * 8。
  6. 获取加密上下文 encrypt_ctx_ 的块大小,并将其保存在变量 encrypt_block_size_ 中。
  7. 获取解密上下文 decrypt_ctx_ 的块大小,并将其保存在变量 decrypt_block_size_ 中。
  8. AESImpl::UnInit(): 该函数用于释放加密算法模块的资源。它进行以下操作:
  9. 调用 mbedtls_cipher_free 释放加密上下文 encrypt_ctx_ 和解密上下文 decrypt_ctx_。
  10. AESImpl::Encrypt(const void input, size_t input_size): 该函数用于对输入数据进行加密。它接受一个指向输入数据的指针 input 和输入数据的大小 input_size,并返回加密后的数据。在函数内部,它进行以下操作:
  11. 调用 mbedtls_cipher_set_iv 设置加密上下文 encrypt_ctx_ 的初始化向量(IV)为全零的 16 字节。
  12. 调用 mbedtls_cipher_reset 重置加密上下文 encrypt_ctx_ 的状态。
  13. 创建一个输出字符串 output,大小为输入数据的大小加上加密块的大小。
  14. 调用 mbedtls_cipher_update 对输入数据进行加密处理,并将加密结果存储在 output 中。
  15. 调用 mbedtls_cipher_finish 完成加密操作,并将最后一部分加密结果追加到 output 中。
  16. 调整 output 的大小为实际加密结果的大小,并返回加密结果。


通过使用这些函数,可以实现对数据的加密操作。


std::string AESImpl::Decrypt(const void* input, size_t input_size) {
mbedtls_cipher_set_iv(&decrypt_ctx_, iv_, sizeof(iv_));
mbedtls_cipher_reset(&decrypt_ctx_);

std::string output(input_size + decrypt_block_size_, 0);
size_t olen = 0;
int ret = mbedtls_cipher_update(
&decrypt_ctx_, reinterpret_cast<const unsigned char*>(input), input_size,
reinterpret_cast<unsigned char*>(output.data()), &olen);
if (ret != 0) {
return "";
}
size_t olen2 = 0;
ret = mbedtls_cipher_finish(
&decrypt_ctx_, reinterpret_cast<unsigned char*>(output.data()) + olen,
&olen2);
if (ret != 0) {
return "";
}
output.resize(olen + olen2);
return output;
}


再介绍这段解密的代码


这段代码实现了一个AES解密算法模块,主要包括:


  1. AESImpl::Decrypt(const void input, size_t input_size)*: 该函数用于对输入数据进行解密。它接受一个指向输入数据的指针 input 和输入数据的大小 input_size,并返回解密后的数据。在函数内部,它进行以下操作:
  2. 调用 mbedtls_cipher_set_iv 设定解密上下文 decrypt_ctx_ 的初始向量为 iv_。
  3. 调用 mbedtls_cipher_reset 重置解密上下文 decrypt_ctx_。
  4. 创建一个初始大小为 input_size + decrypt_block_size_ 的输出字符串 output。
  5. 调用 mbedtls_cipher_update 对输入数据进行解密操作,并将解密后的数据存储到 output 中。
  6. 检查解密操作是否成功,如果失败则返回空字符串。
  7. 调用 mbedtls_cipher_finish 完成解密操作,并将结果追加到 output 中。
  8. 重新调整 output 的大小,将其截断为正确的长度。
  9. 返回解密后的数据。


可以看到,前面的类声明的是AESCryptImpl,带了个Impl后缀,这里我还是和上一节一样,使用了pimpl模式,对外类会只会暴露以下方法:


class Crypt {
public:
virtual ~Crypt() = default;

virtual std::string Encrypt(const std::string& input) = 0;

virtual std::string Decrypt(const std::string& input) = 0;

virtual std::string Encrypt(const void* input, size_t input_size) = 0;

virtual std::string Decrypt(const void* input, size_t input_size) = 0;
};


然后我们再写个程序测试一下本节功能:


TEST(CryptTest, BasicTest) {
std::string key = buried::AESCrypt::GetKey("salt", "password");
EXPECT_TRUE(!key.empty());

buried::Crypt* crypt = new buried::AESCrypt(key);
std::string input = "hello world";
std::string encrypted = crypt->Encrypt(input);
EXPECT_TRUE(!encrypted.empty());
std::string decrypted = crypt->Decrypt(encrypted);
EXPECT_TRUE(!decrypted.empty());
EXPECT_EQ(input, decrypted);
delete crypt;
}

结果就不贴了,肯定是正确的。


到这里,本节已经介绍完毕,我们下期见。


本节详细代码见:

  1. https://github.com/chengxumiaodaren/BuriedPoint/blob/main/src/crypt/crypt.h
  2. https://github.com/chengxumiaodaren/BuriedPoint/blob/main/src/crypt/crypt.cc
  3. https://github.com/chengxumiaodaren/BuriedPoint/blob/main/tests/test_crypt.cc


0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
程序喵
下载 APP