crypto++加密库简单使用

目录

crypto++密码学库简单使用

一、简介

二、配置

三、使用示例

1.CRC32校验

2.Base64编码

3.Blake2b

4.AES

5.RSA


一、简介

        crypto++是一个免费开源(公共领域)的C++密码学库,首作者叫Wei Dai(美籍华裔姓Dai)。

        它包含主流的密码学方案,比如对称加密AES,非对称RSA,哈希函数SHA2等。还包含更常见但不够安全的功能,比如SHA1MD5。还有没有安全需求的常见功能,比如CRC32Base64。在官网还列出一大堆功能,读者可以自行查阅。

二、配置

        官网地址: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个值如下:

ABCDEFGH
IJKLMNOP
QRSTUVWX
YZabcdef
ghijklmn
opqrstuv
wxyz0123
456789+/

        由于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算法的根基是基于大质数难以分解的数学问题,原理可以参考我这篇博客:

RSA加密原理_略游的博客-CSDN博客

        假设你要与朋友交流一些不可告人的事情,实际上使用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

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐