crypto++框架介绍

DinS          Written on 2017/11/22

本文介绍crypto++的一些概念,希望读者已经把环境搭建好了。

一、crypto++初探

这部分将以AES加密方式来演示crypto++的用法。

注意:本节使用的加密方式既不安全也不高效,不要在实际项目中使用这种方式。在此仅仅为了向读者展示crypto++的一些概念。至于推荐的加密和使用方式,可见文末。

AES是什么?是一种加密方式,鉴于在此仅仅演示crypto++的一些概念,就不深入解释了,可见《对称加密算法之AES》。

直接配合代码讲解:

用意有两个,一是看看环境配置有没有成功,二是看看AES基本的一些数据。
运行结果如下:

说明:AES命名空间里指定的数值以字节(Byte)计,乘以8就变成了比特(bit)。

证明配置是成功了。接下来进行真正的加密解密。

大图点这里

整个过程还是很直接的,准备好必要数据后,建立类,设置密钥,调用成员函数,就完了。unsigned char就等同于byte。
值得注意的是Block的含义。之前我们看到了Block就是16,换句话说AES仅仅支持16个字符加密解密?并非如此,只不过对长文本加密会非常耗时,所以如果真的需要也会把长文本拆成一段一段,即Block,然后对每一段加密。
另外有一点,原始文本中最好只有英文字母、数字和常用符号。不能有空格。
还有一个关于输出的明文。绝大多数加密算法都是在bit层级展开的,因此加完密后几乎可以肯定不再是ASCII码表范围内,因此直接按char输出必然乱码,于是只能够按16进制输出。

如果编译报错:

那就是是库的设置与工程设置有出入,MT和MD问题。更改即可。

运行结果如下:

这个输出的意思是说,在采用abcdefghijklmno作为密钥,对HelloAES!进行AES加密后,得到一块数据内存,用16进制表示出来就是这个东西。
有了这个再进行解密,继续上面加密的代码写:

大图点这里

运行结果:

可见解密成功了!

二、使用base64编码

虽然加解密成功了,不过还不够好,哪里不够好?
我们得到的密文不容易传输,因为不能打印出字符串,
我们希望的是得到一个可打印出来的字符串,然后通过网络传输,接收方再解密,这个需求使用base64来解决。

Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。也就是说,给定一块二进制数据区,用base64编码就可以得到一串可打印字符串,这样传输就容易了。反之可以用base64解码得到对应的二进制数据区。

如何来使用呢?加入头文件:

之后在加密代码后加入如下代码:

大图点这里

并不长,但是中间有两句看起来非常疯狂。具体什么意思稍后解释。
先看一下输出效果:

注:使用crypto++进行base64编码,字符串末尾会多一个\n,在绝大多数情况下我们不想要这个\n,记得删掉。

第一个16进制输出后面跟了一个字符串,这个就是base64编码的结果。
再之后的那个含有一堆ff的输出,仔细看跟之前的16进制一样,这证明base64解码成功了。不过现在我们用了一个string来存储二进制,后面怎么进行AES解码呢?很简单:

大图点这里

只要取得char*并强转为byte*即可。因为unsigned char就是byte,所以可以互换,运行结果是一致的,可以得到HelloAES!的原始文本。

还需要补充一点:除了Base64Encoder/Base64Decoder之外还有一个Base64URLEncoder/Base64URLDecoder。区别在于普通的Base64编码会带有+和/符号,这两个符号对于URL和文件名是非法的,所以产生了Base64URL编码,用这种方法得到的字符串不带+和/,对于URL和文件名要使用这种方式,另外对于JSON串也有一定好处。

接下来看看那个很疯狂的代码,这个东西体现了Crypto++的核心理念:Pipelining。
这个理念来源于Unix pipe system,二者非常相似。其思路就是数据流从source走向sink,路途中遇到filter并改变了数据。因此上面那个base64编码的代码表达的语义就是:二进制数据outBlock经过了Base64Encoder处理,放入了str64Encoded这个字符串中,这样思考的话确实很流畅,很简洁。

应该注意到我们new了但是没有delete,会造成内存泄露吗?
设计者早已经考虑到这点了,在pipelinging里new出来的对象会由source负责调用delete,而new出来的对象被delete时会进一步delete掉其new出来的对象,以此类推。还以上述base64编码而言,当ssEncode这个StringSource被销毁时,其会delete掉其new出来的Base64Encoder,而Base64Encoder被销毁时,会进一步delete掉其new出来的StringSink。结果是所有new出来的对象都delete掉了。

这个pipelinging还是很神奇的,让我们继续探讨这个模式,一旦掌握了这个模式crypto++的写法会异常简洁高效。

三、Pipelining设计理念介绍

之前已经在base64时接触过pipelining了,虽然代码看上去有些疯狂但是一旦理解了就会发现很好用。现在是时候正式介绍一下pipelining了,理念来源于Unix,可以用图表示如下:

叫pipelining是很形象的,就是一根管子,数据从Source流向Sink,在流动的过程中遇到Filter,把数据改变。

再来解说一下下面这个代码以加深理解:

StringSource是什么稍后再说,先看括号里面的。
Source就是原始文本(strSource),然后经过了一个Filter,即HashFilter,而这个Filter是采用sha256方法来改变数据。但是不单单只使用了sha256,在这个方法结束后又出现了一个Filter,即HexEncoder,把数据改写成16进制的字符串。最后这个数据流入了StrinkSink里(strValue),得到了最终的结果。

抽象出来的写法是这样,适用于任何Source/Filter/Sink类型:
Source s(source, new Filter(new Filter(new Sink(destination))));
这个过程还是很有美感的,而且不需要手动delete。

再来看看那个StringSource。顾名思义就是类型为String的Source,也可以把这个理解成为架设起了pipeline。
Crypto++提供了几个常用的Source: FileSource,StringSource,RandomNumberSource,SocketSource,用法都是一样的。

官方推荐写法是这样,增加可读性。

Sink跟Source差不多,也有几个常用的Sink:
StringSink,FileSink,ArraySink,SocketSink,RandomNumberSink。

Filter大概要复杂一点,常用的也就是那几个跟加密解密算法相关的东西,如果需要深入可以查看filter.h。能够灵活使用pipelining就可以了,具体的底层没必要关注。

这个pipelining模式一定要理解,是crypto++的核心。掌握这个后咱们再来看几种有代表性的加密算法,并掌握如何在实际项目中使用,见《非对称加密算法之RSA》和《散列算法之SHA》和《对称加密算法之AES》。