crypto++笔记
《深入浅出cryptoPP密码学库》学习笔记。crypto++库帮助文档:https://www.cryptopp.com/docs/ref/index.html
进制与编码
以2进制,8进制,10进制,16进制字符串构造整数
Integer Int2("011111101010000b");
Integer Int8("102345676543210o");
Integer Int10("1234567890987654321");
Integer Int16("1234567890ABCDEFh");
base系列编码算法定义在各base64.h base32.h hex.h头文件中
//base64编码与解码
#include<iostream>
#include<base64.h>
#include<files.h>
using namespace std;
using namespace CryptoPP;
int main()
{
string message= "我我我我我我我我我我我";
StringSource Base64String(message, true, new Base64Encoder(new FileSink(cout)));
string base64Message = "ztLO0s7SztLO0s7SztLO0s7SztLO0g==";
StringSource Base64String2(base64Message, true, new Base64Decoder(new FileSink(cout)));
}
ASN.1定义了一套编码标准,其中有BER,DER等,为了与PGP,openSSL兼容,cryptopp定义了对数据进行BER,DER编解码的函数
生成一个512位随机数,并保存为der格式文件
#include<iostream>
#include<osrng.h>
#include<files.h>
using namespace std;
using namespace CryptoPP;
int main()
{
Integer bigNumber;
AutoSeededRandomPool rng;
bigNumber.Randomize(rng, 512);
bigNumber.DEREncode(FileSink("bignumber.der").Ref());
}
pipeling数据处理范式
该范式将数据比作水流,从source经过filter最终流向sink
cryptopp将所有数据源称为source,所有数据处理类称为filter,所有能存储数据的特定数据结构称为sink
从StringSource流向HexEncoder再流向FileSink
#include<iostream>
#include<hex.h>
#include<files.h>
using namespace std;
using namespace CryptoPP;
int main()
{
string my;
cin >> my;
StringSource myCin(my, true, new HexEncoder(new FileSink("my.txt")));
}
source可以使用GET()取出数据,sink可以使用PUT()放入数据,filter可以使用GET()和PUT()取出或放入数据
#include<iostream>
#include<hex.h>
#include<files.h>
#include<secblock.h>
using namespace std;
using namespace CryptoPP;
int main()
{
string my;
cin >> my;
SecByteBlock secGet(8);//每次取8个字符
StringSource myCin(my, true);
HexEncoder hexEncoder;
FileSink fileSink("my.txt");
myCin.Get(secGet, secGet.size());
hexEncoder.Put(secGet, secGet.size());
hexEncoder.Get(secGet, secGet.size());
fileSink.Put(secGet, secGet.size());
}
也可以通过Attach连接“流”的“水道”,通过Detach更改“水道”
#include<iostream>
#include<hex.h>
#include<files.h>
#include<secblock.h>
using namespace std;
using namespace CryptoPP;
int main()
{
string my;
cin >> my;
StringSource myCin(my, true);
HexEncoder hexEncoder;
SecByteBlock secGet(2);
hexEncoder.Attach(new FileSink("my.txt"));
myCin.Get(secGet, secGet.size());
hexEncoder.Put(secGet, secGet.size());
hexEncoder.Detach(new FileSink(cout));
myCin.Get(secGet, secGet.size());
hexEncoder.Put(secGet, secGet.size());
}
通过Redirector和ChannelSwitch多“水道”输出
示例中加入了不经过filter处理的,转为16进制处理的,base64处理的多个“水道”,在通过Redirector连接。
#include<iostream>
#include<hex.h>
#include<files.h>
#include<secblock.h>
#include<base64.h>
#include<channels.h>
using namespace std;
using namespace CryptoPP;
int main()
{
string my;
cin >> my;
FileSink o(cout);
HexEncoder hexEncoder(new FileSink(cout));
Base64Encoder base64Encoder(new FileSink(cout));
ChannelSwitch cs;
cs.AddDefaultRoute(o);
cs.AddDefaultRoute(hexEncoder);
cs.AddDefaultRoute(base64Encoder);
StringSource myCin(my, true, new Redirector(cs));
}
工具
计时
//#include<hrtimer.h>
Timer tm(TimerBase::SECONDS);//设置以秒为单位
cout << tm.GetCurrentTimerValue() << endl;//当前计数器的值
tm.StartTimer();//开始计时
cout << tm.ElapsedTimeAsDouble() << endl;//从开始计时到现在花费时间
压缩工具
三种压缩工具:Deflator、Gzip、ZlibCompressor
对应三种解压工具:Inflator、Gunzip、ZlibDecompressor
压缩与解压缩示例
string a = "my.txt";
FileSource fileZip(a.c_str(), true, new Gzip(new FileSink("my.zip")));
FileSource fileUnzip("my.zip", true, new Gunzip(new FileSink("my2.txt")));
数学工具
定义在nbtheory.h中
数论
素数
#include<iostream>
#include<nbtheory.h>
#include<osrng.h>
using namespace std;
using namespace CryptoPP;
int main()
{
//判断一个小素数是否为素数,能比的范围最大为32719
Integer i("7");
cout << IsSmallPrime(i);
//判断是否有小素因子
Integer j("17");
cout << SmallDivisorsTest(j);
//产生确定的小素数
AutoSeededRandomPool rng;
cout << MihailescuProvablePrime(rng, 64);//产生64比特
cout << MaurerProvablePrime(rng, 32);//产生32比特
//产生一定概率是素数的素数
//按概率从低到高排
Integer b("2");
cout << IsFermatProbablePrime(i, b);//计算b^(i-1)=1(modi)
cout << IsLucasProbablePrime(i);
cout << IsStrongProbablePrime(i, b);
cout << IsStrongLucasProbablePrime(i);
cout << RabinMillerTest(rng, i, 10);//10轮米勒拉宾素性检测
cout << IsPrime(i);//依次调用IsSmallPrime,IsStrongProbablePrime,IsStrongLucasProbablePrime
cout << VerifyPrime(rng, i, 1);//依次调用IsPrime,RabinMillerTest,1表示米勒拉宾素性检测执行1轮,大于一再执行10轮
}
其它算法
Integer a("7");
Integer b("5");
Integer c("3");
cout << GCD(a, b);//求最大公约数
cout << LCM(a, b);//最小公倍数
cout << RelativelyPrime(a, b);//是否互素
cout << EuclideanMultiplicativeInverse(a, b);//求amodb的逆元,逆元存在则返回逆元,不存在则返回0
//中国剩余定理,p,q为素数,且互素,xp,xq为模p,q的值,求同余式组x=xpmodp x=xqmodq
Integer p("7");
Integer q("11");
Integer xp("2");
Integer xq("3");
cout << CRT(xp, p, xq, q, EuclideanMultiplicativeInverse(p, q));
cout << Jacobi(a, b);//雅克比符号,b为奇素数时,等价于计算勒让德符号,用于判断按是否为模b的二次剩余
cout << ModularMultiplication(a, b, c);//计算a*bmodc
cout << ModularExponentiation(a, b, c);//计算a^bmodc
cout << ModularSquareRoot(a, c);//计算满足x^2=amodc的x,c为素数
代数
#include<iostream>
#include<ecp.h>
using namespace std;
using namespace CryptoPP;
int main()
{
ECP ecp(23, 1, 1);//定义有限域上椭圆曲线y^2=x^3+1*x+1mod23
ECP::Point P(3, 10);
ECP::Point Q(9, 7);
ECP::Point result = ecp.Add(P, Q);
cout << result.x << ' ' << result.y;
//计算10P ecp.ScalarMultiply(P,Integer(10))
//计算ecp单位元 ecp.Identify
//计算逆元 ecp.Inverse(P)
}
秘密分割
定义在头文件ida.h
shamir秘密共享算法对应类SecretSharing,SecretRecovery
rabin信息传播算法对应类InformationDispersal,InformationRecovery
利用cryptopp实现shamir对文件的分存与还原
#include<iostream>
#include<ida.h>
#include<osrng.h>
#include<channels.h>
#include<files.h>
#include<string>
using namespace std;
using namespace CryptoPP;
void SecretShareFile(int threshold, int nShares, const string filename)
{
//保证nShares合理
CRYPTOPP_ASSERT(nShares >= 1 && nShares <= 1000);
AutoSeededRandomPool rng;
ChannelSwitch* channelSwitch = new ChannelSwitch;
//file的内容流入SecretSharing中,再流入channelSwitch中
FileSource source(filename.c_str(),false, new SecretSharing(rng, threshold, nShares, channelSwitch));
vector_member_ptrs<FileSink> fileSinks(nShares);
string channel;
for (int i = 0; i < nShares; ++i)
{
//定义5个子文件进行分存
fileSinks[i].reset(new FileSink((filename + "." +to_string(i) ).c_str()));
channel = WordToString<word32>(i);
//每个文件保存4字节的通道号,以便还原
fileSinks[i]->Put((const byte*)channel.data(), 4);
//再将channelSwitch分别接上这5个文件,即5个分叉的“水道”
channelSwitch->AddRoute(channel, *fileSinks[i], DEFAULT_CHANNEL);
}
//“开闸放水”,即取出数据进行处理
source.PumpAll();
}
void SecretRecoverFile(int threshold, const string& outFilename, const vector<string>& inFilename)
{
//保证threshold合理
CRYPTOPP_ASSERT(threshold >= 1 && threshold <= 1000);
SecByteBlock channel(4);
vector_member_ptrs<FileSource>fileSource(threshold);
//SecretRecovery连接最后输出的恢复的文件
SecretRecovery recovery(threshold, new FileSink(outFilename.c_str()));
for (int i = 0; i < threshold; ++i)
{
//将每个输入的文件都连接在ChannelSwitch,再连接到SecretRecovery
fileSource[i].reset(new FileSource(inFilename[i].c_str(), false));
//先取出分存时前4字节的通道号
fileSource[i]->Pump(4);
fileSource[i]->Get(channel, 4);
fileSource[i]->Attach(new ChannelSwitch(recovery, string((char*)channel.begin(), 4)));
}
//对每个输入的文件依次“开闸放水”,即取出数据
for (int i = 0; i < threshold; i++)
{
fileSource[i]->PumpAll();
}
}
int main()
{
//秘密分存,分成5份,3份即可还原
SecretShareFile(3, 5, "F:/code/c++/myPrimer/test.txt");
//秘密还原
vector<string> inFilename = { "F:/code/c++/myPrimer/test.txt.4","F:/code/c++/myPrimer/test.txt.1","F:/code/c++/myPrimer/test.txt.2" };
SecretRecoverFile(3, "F:/code/c++/myPrimer/recoverytest.txt", inFilename);
}
随机数发生器
分为真随机数和伪随机数,都能通过硬件和软件实现
LC_RNG(rng.h):线性同余发生器
RandomPool(randpool.h):基于AES算法构造的随机数池
AutoSeededRandomPool(osrng.h):自动调用系统的随机数池算法,利用系统自动重置种子
AutoSeededX917RNG(osrng.h):自动重置种子的ANSIX9.17伪随机数算法
RDRAND和RDSEED(rdrand):硬件芯片产生的随机数,只适用于英特尔芯片
对于一些随机算法IncorporateEntropy可以更新其内部熵
#include<iostream>
#include<rng.h>
#include<osrng.h>
#include<aes.h>
#include<fstream>
#include<hrtimer.h>
using namespace std;
using namespace CryptoPP;
int main()
{
//LC_RNG
LC_RNG rng(123);//种子设为123
cout << rng.GenerateBit();//产生1比特随机数
cout << rng.GenerateByte();//产生1字节随机数
byte a[32];
rng.GenerateBlock(a, 32);//产生32字节随机数
for (auto i : a)
{
cout << hex << int(i);//以16进制输出,其实这里有问题,会把前面的0省略,所以可以printf("%02X", i) ;
}
cout << rng.GenerateWord32(10, 100);//产生10到100间的随机数
int arr[] = { 1,2,3,4,5,6,7,8,9 };
rng.Shuffle(arr, arr + 9);//打乱arr中的元素
for (auto i : arr)
{
cout << i;
}
//AutoSeededX917RNG
AutoSeededX917RNG<AES> rng;
ofstream file("rand",ofstream::binary|ofstream::app);
byte output[64];
Timer time;
time.StartTimer();
for (int i = 0; i < 16; i++)
{
rng.GenerateBlock(output, 64);
file << output;
}
cout << time.ElapsedTimeAsDouble();
file.close();
//也可以使用Pipeling格式
//RandomNumberSource r(rng,64,true,new FileSink(file))
}
哈希函数
计算字符串的哈希
但是这里中文计算的哈希是不正确的,说明此程序还有问题
#include<iostream>
#include<sha.h>
using namespace std;
using namespace CryptoPP;
int main()
{
SHA256 sha;
cout << "分组长度" << sha.BlockSize()*8 << '\n';
cout << "哈希值长度" << sha.DigestSize() * 8 << '\n';
byte m1[] = "邃密群科济世穷";
byte m2[] = "面壁十年图破壁";
//分次添加带计算哈希的值
sha.Update(m1, sizeof(m1) - 1);
sha.Update(m2, sizeof(m2) - 1);
//创建存放哈希值的空间
size_t len = sha.DigestSize();
byte* digest1 = sha.CreateUpdateSpace(len);
sha.Final(digest1);
cout << "分次计算哈希";
for (size_t i=0;i< sha.DigestSize();++i)
{
cout << hex << int(digest1[i]);
}
cout << '\n';
//-----------------------------------------
SHA256 sha2;
byte m3[] = "邃密群科济世穷面壁十年图破壁";
SecByteBlock digest2(sha2.DigestSize());
sha2.CalculateDigest(digest2, m3, sizeof(m3) - 1);
cout << "一次计算哈希";
for (auto i : digest2)
{
cout << hex << int(i);
}
cout << '\n';
//------------------------------------------------
bool res = sha2.VerifyDigest(digest1, m3, sizeof(m3) - 1);
cout << "分次计算和一次计算哈希是否相同" << boolalpha << res;
}
计算文件的哈希
这里有个问题,如果使用c++的读取文件的方法,如果使用getline等就不能与计算哈希函数中Update的参数匹配,如果使用流的方法,就会忽略文件中的空白符,如果加上file >> noskipws使其不忽略空白符,读取时又有不清楚的错误,算出的哈希不正确。所以使用c中读取文件的方法
#include<iostream>
#include<sm3.h>
#include<fstream>
using namespace std;
using namespace CryptoPP;
int main()
{
SM3 sm3;
FILE* fp;
byte buff[1024];
SecByteBlock digest(sm3.DigestSize());
int len;
fopen_s(&fp, "my.txt", "rb");
while (!feof(fp))
{
len = fread(buff, sizeof(byte), 1024, fp);
sm3.Update(buff, len);
}
sm3.Final(digest);
cout << "哈希";
for (size_t i = 0; i < sm3.DigestSize(); ++i)
{
printf("%02X", digest[i]);
}
cout << '\n';
}
pipeling处理方式
#include<iostream>
#include<fstream>
#include<files.h>
#include<filters.h>
#include<channels.h>
#include<hex.h>
#include<sm3.h>
#include<sha.h>
#include<md5.h>
#include<crc.h>
using namespace std;
using namespace CryptoPP;
int main()
{
SHA256 sha256;
SHA512 sha512;
MD5 md5;
CRC32 crc32;
string s1, s2, s3, s4;
HashFilter f1(sha256, new HexEncoder(new StringSink(s1)));
HashFilter f2(sha512, new HexEncoder(new StringSink(s2)));
HashFilter f3(md5, new HexEncoder(new StringSink(s3)));
HashFilter f4(crc32, new HexEncoder(new StringSink(s4)));
ChannelSwitch cs;
cs.AddDefaultRoute(f1);
cs.AddDefaultRoute(f2);
cs.AddDefaultRoute(f3);
cs.AddDefaultRoute(f4);
string filename = "my.txt";
FileSource fs(filename.c_str(), true, new Redirector(cs));
cout << "sha256: " << s1 << '\n' << "sha512: " << s2 << '\n' << "md5: " << s3 << '\n' << "crc32: " << s4 << '\n';
}
流密码
使用pipeling
#include<iostream>
#include<salsa.h>
#include<osrng.h>
#include<secblock.h>
#include<hex.h>
using namespace std;
using namespace CryptoPP;
int main()
{
//定义加解密对象
XSalsa20::Encryption en;
XSalsa20::Decryption de;
//定义随机数发生器
AutoSeededRandomPool rng;
//明文,密文,恢复的明文
string plain = "邃密群科济世穷";
string cipher;
string recover;
//用随机数生成加密用的密钥与初始向量
SecByteBlock key(en.DefaultKeyLength());
SecByteBlock iv(en.DefaultIVLength());
rng.GenerateBlock(key, key.size());
rng.GenerateBlock(iv, iv.size());
//设置密钥与初始向量,并将加密结果以16进制编码后输出
en.SetKeyWithIV(key, key.size(), iv, iv.size());
StringSource enP(plain, true, new StreamTransformationFilter(en, new HexEncoder(new StringSink(cipher))));
cout <<cipher<<'\n';
//设置密钥与初始向量,并将加密结果以16进制解码后再解密
de.SetKeyWithIV(key, key.size(), iv, iv.size());
StringSource deP(cipher, true, new HexDecoder(new StreamTransformationFilter(de, new StringSink(recover))));
cout << recover<<'\n';
}
分组密码
除了提供基础的CBC,CFB等分组模式外,还有具有认证功能的分组模式:
CCM:CTR与CBC-MAC的组合,只支持128比特的分组
EAX:CCM的改进,对CBC-MAC做了变化,支持任意长度的分组
GCM:CTR与hash函数的组合,支持任意长度的分组
基础分组模式都定义在modes.h中,上述分组模式定义在各自名字命名的头文件中
cryptopp是通过密码算法实例化分组模式模板来实现分组加解密的
如果使用带有认证的分组模式就不能使用StreamTransformationFilter类,加解密要分别使用AuthenticatedEncryptionFilter和AuthenticatedDecryptionFilter类
以gcm模式的SM4为例
#include<iostream>
#include<osrng.h>
#include<filters.h>
#include<secblock.h>
#include<hex.h>
#include<sm4.h>
#include<gcm.h>
using namespace std;
using namespace CryptoPP;
int main()
{
GCM<SM4>::Encryption en;
GCM<SM4>::Decryption de;
string plain = "面壁十年图破壁";
string chiper;
string recover;
AutoSeededRandomPool rng;
SecByteBlock key(en.DefaultKeyLength());
SecByteBlock iv(en.DefaultIVLength());
rng.GenerateBlock(iv, iv.size());
rng.GenerateBlock(key, key.size());
en.SetKeyWithIV(key, key.size(), iv, iv.size());
StringSource enP(plain, true, new AuthenticatedEncryptionFilter(en, new HexEncoder(new StringSink(chiper))));
cout << chiper << '\n';
de.SetKeyWithIV(key, key.size(), iv, iv.size());
StringSource deC(chiper, true, new HexDecoder(new AuthenticatedDecryptionFilter(de, new StringSink(recover))));
cout << recover << '\n';
}
如果不采用带有认证的分组模式而想使其具有认证,就必须组合加密与认证,而这又有顺序区分,以下面三种协议为例:
SSL:先对明文认证,再对明文与认证的组合加密
IPsec:先对明文加密,再对加密结果认证
SSH:先对明文加密,再对明文认证
IPSec在CBC模式下和有伪随机填充并XOR处理的流密码下是可证明安全的,其它不安全
密钥派生
实际常常使用口令验证用户是否有访问数据的权限,但口令不适合用作密钥。所以使用基于口令的密钥函数PBKDF,产生主密钥MK,主密钥可以直接用来加解密,也可以通过密钥派生函数KDF,产生更多密钥来使用。
密钥派生函数与基于口令的密钥派生函数区别在于,其输入必须是具有密码学强度的密钥
基于口令的密钥派生函数,输入除了口令外,还需要盐值,迭代次数,及一些目的前缀(可选)。
cryptopp提供的基于口令的密钥派生算法都是以hash函数为参数的类模板,定义在hkdf.h pwdbased.h中,有HKDF,PKCS12_PBKDF等
派生示例如下,在应用中,使用口令派生密钥MK,保存派生时使用的盐值,随机生成加密的密钥DPK,MK加密DPK后保存,DPK用于加密操作。解密时,用户输入口令,根据保存的盐值与口令恢复密钥MK,然后解密得到DPK,再用DPK用于解密操作
#include<iostream>
#include<osrng.h>
#include<filters.h>
#include<secblock.h>
#include<hex.h>
#include<hkdf.h>
#include<sm3.h>
#include<files.h>
using namespace std;
using namespace CryptoPP;
int main()
{
string password = "123456789";
AutoSeededRandomPool rng;
SecByteBlock salt(128);
SecByteBlock info(16);
SecByteBlock derivedKey(128);
rng.GenerateBlock(salt, salt.size());
rng.GenerateBlock(info, info.size());
HKDF<SM3> hkdf;
//不同派生函数中的参数不同
hkdf.DeriveKey(derivedKey, derivedKey.size(), (byte*)password.c_str(), password.size(), salt, salt.size(), info, info.size());
//以十六进制输出派生的密钥
ArraySource dkeySource(derivedKey, derivedKey.size(), true, new HexEncoder(new FileSink(cout)));
}
密钥协商
这是带认证的diffie-hellman协议,防止中间人攻击
#include<eccrypto.h>
#include<nbtheory.h>
#include<oids.h>
#include<filters.h>
#include<files.h>
using namespace std;
using namespace CryptoPP;
int main()
{
AutoSeededRandomPool rng;
PrimeAndGenerator pg;
pg.Generate(1, rng, 512, 511);//产生p=rq+1形式的素数
Integer p = pg.Prime();
Integer q = pg.SubPrime();
Integer g = pg.Generator();
DH dh(p, q, g);
DH2 Alice(dh);
DH2 Bob(dh);
//长期密钥,用于认证
SecByteBlock A_stpri(Alice.StaticPrivateKeyLength());
SecByteBlock A_stpub(Alice.StaticPublicKeyLength());
//协商密钥
SecByteBlock A_eppri(Alice.EphemeralPrivateKeyLength());
SecByteBlock A_eppub(Alice.EphemeralPublicKeyLength());
//长期密钥,用于认证
SecByteBlock B_stpri(Bob.StaticPrivateKeyLength());
SecByteBlock B_stpub(Bob.StaticPublicKeyLength());
//协商密钥
SecByteBlock B_eppri(Bob.EphemeralPrivateKeyLength());
SecByteBlock B_eppub(Bob.EphemeralPublicKeyLength());
//生成
Alice.GenerateStaticKeyPair(rng, A_stpri, A_stpub);
Alice.GenerateEphemeralKeyPair(rng, A_eppri, A_eppub);
Bob.GenerateStaticKeyPair(rng, B_stpri, B_stpub);
Bob.GenerateEphemeralKeyPair(rng, B_eppri, B_eppub);
//密钥协商
SecByteBlock Ashared(Alice.AgreedValueLength());
SecByteBlock Bshared(Bob.AgreedValueLength());
Alice.Agree(Ashared, A_stpri, A_eppri, B_stpub, B_eppub);
Bob.Agree(Bshared, B_stpri, B_eppri, A_eppub, A_eppub);
//输出
Integer A, B;
A.Decode(Ashared.BytePtr(), Ashared.size());
B.Decode(Bshared.BytePtr(), Bshared.size());
cout << A << '\n' << B;
}
公钥密码
库中算法名带有IES(集成加密,公钥来分发密钥,对称密码来加密,且带有MAC算法)ES(非集成加密)SS(用来签名)PK(公钥密码相关)TF(陷门函数相关)DL(离散对数相关)
#include<iostream>
#include<osrng.h>
#include<rsa.h>
#include<filters.h>
#include<files.h>
using namespace std;
using namespace CryptoPP;
int main()
{
AutoSeededRandomPool rng;
RSA::PrivateKey prikey;
prikey.Initialize(rng, 1024);//随机产生私钥,也可以使用定义好的参数来初始化
RSA::PublicKey pubkey(prikey);//公钥从私钥来初始化,也可以用AssignFrom()用已有对象初始化
//或者可以使用Load() Save()从流导入或保存密钥
//密钥各参数
cout << "n "<<prikey.GetModulus();
cout << "p " << prikey.GetPrime1();
cout << "q " << prikey.GetPrime2();
cout << "d " << prikey.GetPrivateExponent();
cout << "e " << prikey.GetPublicExponent();
//可以利用模板使用不同填充方案的RSA,如<PKCS1v15>
RSAES<PKCS1v15>::Decryptor dec(prikey);
RSAES<PKCS1v15>::Encryptor enc(pubkey);
//加解密
string plain = "面壁十年图破壁";
string cipher;
StringSource encSrc(plain, true, new PK_EncryptorFilter(rng, enc,new StringSink(cipher)));
StringSource decSrc(cipher, true, new PK_DecryptorFilter(rng, dec, new FileSink(cout)));
}
认证
认证可以通过消息认证码与签名实现
消息认证码
如上一节所示,实际应用中,消息认证码与加密的实现主要有三种方式
消息认证码主要有两种构造方式:
上一节中的分组方式:GCM等
基于哈希函数的方式:HMAC等
cryptopp使用与哈希函数相同的HashFilter类来计算消息认证码,同时使用HashVerificationFilter进行消息认证码的验证
以先对明文认证,再对明文与认证的组合加密为例,加密使用CBC分组的AES,认证使用基于SM3哈希的HMAC
#include<iostream>
#include<osrng.h>
#include<filters.h>
#include<secblock.h>
#include<hex.h>
#include<sm3.h>
#include<modes.h>
#include<aes.h>
using namespace std;
using namespace CryptoPP;
int main()
{
CBC_Mode<AES>::Encryption en;
CBC_Mode<AES>::Decryption de;
string plain = "面壁十年图破壁";
string chiper;
string recover;
AutoSeededRandomPool rng;
SecByteBlock key(en.DefaultKeyLength());
SecByteBlock iv(en.DefaultIVLength());
rng.GenerateBlock(iv, iv.size());
rng.GenerateBlock(key, key.size());
HMAC<SM3> hmac;
SecByteBlock hiv(en.DefaultIVLength());
rng.GenerateBlock(hiv, hiv.size());
hmac.SetKey(hiv, hiv.size());
en.SetKeyWithIV(key, key.size(), iv, iv.size());
//第二个true表示要将消息本身传递下去
//先哈希,再与明文拼在一起加密,再将加密结果转为16进制输出
StringSource enP(plain, true, new HashFilter(hmac, new StreamTransformationFilter(en, new HexEncoder(new StringSink(chiper))), true));
cout << chiper << '\n';
de.SetKeyWithIV(key, key.size(), iv, iv.size());
//HASH_AT_END表示MAC在消息后面,PUT_MESSAGE表示要将消息本身传递下去,THROW_EXCEPTION表示如果MAC验证不通过要抛出异常
//先进行16进制解码,再解密,用最后的MAC进行验证
StringSource deC(chiper, true, new HexDecoder(new StreamTransformationFilter(de, new HashVerificationFilter(hmac, new StringSink(recover), HashVerificationFilter::HASH_AT_END | HashVerificationFilter::PUT_MESSAGE | HashVerificationFilter::THROW_EXCEPTION))));
cout << recover << '\n';
}
也可以自己利用不同的hash和加密算法构造自定义的消息认证码算法
签名
相比于消息认证码不需要共享密钥,可以提供不可否认性
不同签名算法在实例化时可能需要提供哈希函数,或者代数结构或签名标准(如PSSR)等模板参数
#include<iostream>
#include<osrng.h>
#include<eccrypto.h>
#include<oids.h>
#include<filters.h>
#include<files.h>
using namespace std;
using namespace CryptoPP;
int main()
{
AutoSeededRandomPool rng;
ECNR<ECP, SHA256>::PrivateKey prikey;//指明代数结构和哈希函数
ECNR<ECP, SHA256>::PublicKey pubkey;
prikey.Initialize(rng, ASN1::secp160r1());
prikey.MakePublicKey(pubkey);
string message = "也无风雨也无晴";
string signature;
ECNR<ECP, SHA256>::Signer sig(prikey);
ECNR<ECP, SHA256>::Verifier ver(pubkey);
StringSource SigSrc(message, true, new SignerFilter(rng, sig, new StringSink(signature), true));//最后一个true表示将消息和签名一起输出,默认是false,只输出签名
StringSource VerSrc(signature, true, new SignatureVerificationFilter(ver, new FileSink(cout), SignatureVerificationFilter::PUT_MESSAGE));
}