【C++】crypto++加密库简单使用
crypto++加密库简单使用
目录
一、简介
crypto++是一个免费开源(公共领域)的C++密码学库,首作者叫Wei Dai(美籍华裔姓Dai)。
它包含主流的密码学方案,比如对称加密AES,非对称RSA,哈希函数SHA2等。还包含更常见但不够安全的功能,比如SHA1、MD5。还有没有安全需求的常见功能,比如CRC32、Base64。在官网还列出一大堆功能,读者可以自行查阅。
二、配置
官网地址:Crypto++ Library 8.6 | Free C++ Class Library of Cryptographic Schemes
wiki地址:Crypto++ Wiki
作者主页:Wei Dai's Home Page
github地址:GitHub - weidai11/cryptopp: free C++ class library of cryptographic schemes
通过官网可以下载库,官网有API参考,不过看wiki的教程会比较轻松。
库自带vc工程文件,在windows平台很方便使用。
三、使用示例
为了使代码简单,我们使用两个类型声明(包含<cryptlib.h>头文件,引用CryptoPP命名空间):
#include <cryptlib.h>
using namespace CryptoPP;
using p = CryptoPP::byte*;
using cp = const CryptoPP::byte*;
1.CRC32校验
CRC本意为循环冗余校验(Cyclic Redundancy Check),CRC32一般用来校验数据的完整性,它输出一个32位长度的值。由于算法简单,输出值不够长,没有安全考虑,只是单纯的验证数据的完整性。比如硬盘出错、网络传输、比特翻转等造成的数据错误可以很快的检测出来,而理论上一些不法分子可以篡改文件内容,但使CRC32校验值依旧相同,从而让别人使用被篡改的文件。
#include <crc.h>
dnd::n32 HashCrc32(const Buffer& buf)
{
n32 ret;
CRC32 hash;
hash.Update((cp)buf._p, buf._size);
hash.Final((p)&ret);
return ret;
}
bool HashCrc32Check(const Buffer& buf, n32 digest)
{
CRC32 hash;
hash.Update((cp)buf._p, buf._size);
return hash.Verify((cp)&digest);
}
其中Buffer为一段内存,p为首地址,size为长度。n32为32位无符号整型。
将代码中的CRC32类型,替换为CRC32C即可使用CRC32-C版本。CRC32-C在TCP/IP中使用,所以也叫Internet校验和,它比CRC32更新一些,效率也高一点,应该优先使用它。
2.Base64编码
所谓Base64编码,意译即是使用64个符号来编码。首先它编码的目标是字节流,即基本单位为字节(byte),当然在一般的C/C++代码里面,处理数据的基本单位也是字节,而不是位。所以大家常说的二进制流,比特流与字节流差异不大(但还是有区别)。
众所周知1个字节为8位,即表示范围为[0, 255],但是ascii码表并非都是可见字符。所以任意的一段内存数据并不能直接使用可见字符的形式表示出来(比如数据中的0,在ascii码中表示结束符)。
所以Base64编码将256个值用64个值表示。64个值如下:
A | B | C | D | E | F | G | H |
I | J | K | L | M | N | O | P |
Q | R | S | T | U | V | W | X |
Y | Z | a | b | c | d | e | f |
g | h | i | j | k | l | m | n |
o | p | q | r | s | t | u | v |
w | x | y | z | 0 | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | + | / |
由于64=2^6,那么一个Base64码,需要6位二进制。那么3个比特(byte)是24位,则可以用4个Base64码表示。
所以任意数据都可以通过Base64编码为可见字符,它还有两个规则:每76个字符(密文)换行;原文不是3字节的倍数补0,每补一个字节末尾添加一个=。当然3的非0余数只有1和2,所以只会出现最多两个=。
#include <base64.h>
dnd::Buffer EncodeBase64(const Buffer& buf)
{
Buffer ret;
string str_sink;
StringSource ss((cp)buf._p, buf._size, true,
new Base64Encoder(
new StringSink(str_sink)
) // Base64Encoder
); // StringSource
ret.Copy(str_sink);
return ret;
}
dnd::Buffer DecodeBase64(const Buffer& buf)
{
Buffer ret;
string str_sink;
StringSource ss((cp)buf._p, buf._size, true,
new Base64Decoder(
new StringSink(str_sink)
) // Base64Encoder
); // StringSource
ret.Copy(str_sink);
return ret;
}
上面的代码中,我们使用StringSink接收编码(或解码)后的数据。然后我们调用Buffer::Copy从string_sink复制了一份内存。此处的内存复制讲道理可以优化,不过暂未研究出来。
3.Blake2b
此算法用于替代不安全的MD5,更快更简单更安全。与CRC32等哈希函数一样,它的结果值是固定长度的,所以我们使用固定大小的类型来返回结果。
using Blake2b = array<byte, 64>;//长度为64字节
#include <blake2.h>
Blake2b HashBlake2b(const Buffer& buf)
{
assert(!buf.Empty());
Blake2b ret;
BLAKE2b hash;
hash.Update((cp)buf._p, buf._size);
hash.Final((p)&ret);
return ret;
}
bool HashBlake2bCheck(const Buffer& buf, Blake2b digest)
{
assert(!buf.Empty());
BLAKE2b hash;
hash.Update((cp)buf._p, buf._size);
return hash.Verify((cp)&digest);
}
4.AES
AES全称高级加密标准(Advanced Encryption Standard),属于对称加密算法,即加密与解密均使用同一密钥。它比DES算法先进,DES加密算法已经不安全。
使用AES首先要生成密钥,它还会生成一个IV来重复使用同一个密钥(可以简单视为密码的一部分)。它的长度我们使用默认的,所以如下定义类型:
using AesKey = array<byte, 16>;
using AesIV = array<byte, 16>;
而库本身使用SecByteBlock类来保存密码,因为它释放后会清空内存,防止关键信息残留内存。这一点可具体看文档说明,这里简单起见,直接复制了出来。
#include <osrng.h>
#include <rijndael.h>
tuple<AesKey, AesIV> GenerateAES()
{
AutoSeededRandomPool prng;
SecByteBlock key(AES::DEFAULT_KEYLENGTH);
SecByteBlock iv(AES::BLOCKSIZE);
prng.GenerateBlock(key, key.size());
prng.GenerateBlock(iv, iv.size());
tuple<AesKey, AesIV> ret;
AesKey& ret_key = get<0>(ret);
AesIV& ret_iv = get<1>(ret);
memcpy(ret_key.data(), key.data(), key.size());
memcpy(ret_iv.data(), iv.data(), iv.size());
return ret;
}
通过上面的函数可以生成密钥(key、iv对),如下所示,使用了std::tuple,然后我将其打印了出来。
auto [key, iv] = Crypto::GenerateAES();
debug_test(format("key:{}", toString(key)));
debug_test(format("iv :{}", toString(iv)));
key:2d9e6c261b2625eb728121e77a68fe6c
iv :f9f9178a3ca880c21895ae4b3047a68a
接下来是加密与解密,它俩代码基本一致,只是CBC_Mode<AES>::Encryption换为了CBC_Mode<AES>::Decryption:
Buffer EncryptAES(const Buffer& buf, const AesKey& in_key, const AesIV& in_iv)
{
SecByteBlock key((cp)in_key.data(), in_key.size());
SecByteBlock iv((cp)in_iv.data(), in_iv.size());
CBC_Mode<AES>::Encryption e;
e.SetKeyWithIV(key, key.size(), iv);
Buffer ret;
//ArraySink是固定长度的接收缓冲区
string str_sink;
StringSource s((cp)buf._p, buf._size, true,
new StreamTransformationFilter(e,
new StringSink(str_sink)
) // StreamTransformationFilter
); // StringSource
ret.Copy(str_sink);
return ret;
}
Buffer DecryptAES(const Buffer& buf, const AesKey& in_key, const AesIV& in_iv)
{
SecByteBlock key((cp)in_key.data(), in_key.size());
SecByteBlock iv((cp)in_iv.data(), in_iv.size());
CBC_Mode<AES>::Decryption e;
e.SetKeyWithIV(key, key.size(), iv);
Buffer ret;
//ArraySink是固定长度的接收缓冲区
string str_sink;
StringSource s((cp)buf._p, buf._size, true,
new StreamTransformationFilter(e,
new StringSink(str_sink)
) // StreamTransformationFilter
); // StringSource
ret.Copy(str_sink);
return ret;
}
5.RSA
RSA由三位作者完成,所以RSA为三位作者名字首字母的合称。RSA为非对称加密算法,它的密钥分为公钥和私钥。一般公钥用于加密数据,私钥用于解密数据。但是也可以私钥加密,而公钥来解密。
私钥由自己保存,不会通过网络传输,也就不存在窃听的可能(除非主机被入侵直接获取到私钥,或者你把私钥发给别人)。
公钥是公开的密码,别人使用公钥对数据进行加密,然后其余人没有私钥是无法进行解密的。所以这保证了:别人发送给你的信息不会泄漏。
而你私钥加密的信息,任何人有公钥都能解密,这只能证明你拥有私钥,即验证身份。但不能对信息保密。
RSA算法的根基是基于大质数难以分解的数学问题,原理可以参考我这篇博客:
假设你要与朋友交流一些不可告人的事情,实际上使用AES对称加密算法也足够了。首先生成一个AES密钥,然后两人记录下来(不能通过网络发送,存在被窃听可能)。然后将加密后的数据通过网络传输,这样别人即使窃取到数据,而不知道密钥,是无法解密的。
服务器与客户端的交互,可以生成一个临时的AES密钥,用于双方交流信息。但是它们无法提前约定密钥,所以必须使用非对称加密的魔法来传递密钥信息。
但是使用RSA发送AES密钥时,需要注意对方给予的公钥是真的。比如甲给乙要发送AES密钥,所以乙给了甲自己的公钥,等到乙接受到密文后使用乙的私钥即可解密获得AES密钥。但是这个过程,丙可以伪装自己是乙,将丙的公钥发送给甲,待甲用丙的公钥加密后,这样甲的AES密钥便会泄漏给丙。
所以现在的CA证书,便是通过CA机构认证后颁发,来证明某公钥是某人的拥有者。
代码如下,我没有直接生成密钥,而是通过参数保存,私钥是(n,d)对,而公钥是(n,e)对,pq是两个大质数,不过此处不需要使用pq:
#include <modes.h>
#include <rsa.h>
#include <integer.h>
//BigInteger就是Integer,可以自行替换
struct RsaKeyPrivate
{
BigInteger _n;
BigInteger _e;
BigInteger _d;
};
struct RsaKeyPublic
{
BigInteger _n;
BigInteger _e;
};
tuple<RsaKeyPrivate, RsaKeyPublic> GenerateRSA()
{
AutoSeededRandomPool rng;
InvertibleRSAFunction params;
params.GenerateRandomWithKeySize(rng, 3072);
/*
///
// Generated Parameters
const Integer& n = params.GetModulus();
const Integer& p = params.GetPrime1();
const Integer& q = params.GetPrime2();
const Integer& d = params.GetPrivateExponent();
const Integer& e = params.GetPublicExponent();
///
// Dump
cout << "RSA Parameters:" << endl;
cout << " n: " << n << endl;
cout << " p: " << p << endl;
cout << " q: " << q << endl;
cout << " d: " << d << endl;
cout << " e: " << e << endl;
cout << endl;
*/
///
//生成密钥
/*RSA::PrivateKey priKey(params);
RSA::PublicKey pubKey(params);*/
//返回
tuple<RsaKeyPrivate, RsaKeyPublic> ret;
RsaKeyPrivate& key_pri = get<0>(ret);
RsaKeyPublic& key_pub = get<1>(ret);
const Integer& n = params.GetModulus();
const Integer& d = params.GetPrivateExponent();
const Integer& e = params.GetPublicExponent();
*((Integer*)(key_pri._n.GetImp())) = n;
*((Integer*)(key_pri._d.GetImp())) = d;
*((Integer*)(key_pri._e.GetImp())) = e;
*((Integer*)(key_pub._n.GetImp())) = n;
*((Integer*)(key_pub._e.GetImp())) = e;
return ret;
}
在加密和解密时,再生成密钥,代码如下:
dnd::Buffer EncryptRSA(const Buffer& buf, const RsaKeyPublic& key_pub)
{
AutoSeededRandomPool rng;
RSA::PublicKey pubKey;
RsaKeyPublic& key = const_cast<RsaKeyPublic&>(key_pub);
const Integer n = *((Integer*)key._n.GetImp());
const Integer e = *((Integer*)key._e.GetImp());
pubKey.Initialize(n, e);
RSAES_OAEP_SHA_Encryptor encryptor(pubKey);
Buffer ret;
string str_sink;
StringSource ss((cp)buf._p, buf._size, true,
new PK_EncryptorFilter(rng, encryptor,
new StringSink(str_sink)
) // PK_EncryptorFilter
); // StringSource
ret.Copy(str_sink);
return ret;
}
dnd::Buffer DecryptRSA(const Buffer& buf, const RsaKeyPrivate& key_pri)
{
AutoSeededRandomPool rng;
RSA::PrivateKey priKey;
RsaKeyPrivate& key = const_cast<RsaKeyPrivate&>(key_pri);
const Integer& n = *((Integer*)key._n.GetImp());
const Integer& e = *((Integer*)key._e.GetImp());
const Integer& d = *((Integer*)key._d.GetImp());
priKey.Initialize(n, e, d);
RSAES_OAEP_SHA_Decryptor decryptor(priKey);
Buffer ret;
string str_sink;
StringSource ss((cp)buf._p, buf._size, true,
new PK_DecryptorFilter(rng, decryptor,
new StringSink(str_sink)
) // PK_DecryptorFilter
); // StringSource
ret.Copy(str_sink);
return ret;
}
不过上面的代码会产生内存泄漏(反复使用不会产生额外的泄漏),原因参见:
Memory leak in Singleton::Ref()? · Issue #550 · weidai11/cryptopp · GitHub
更多推荐
所有评论(0)