常用加密及其相关的概念、简介(对称、AES、非对称、RSA、散列、HASH、消息认证码、HMAC、签名、CA、数字证书、base64、填充)

PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

环境说明

前言


在之前,一直是通过生活、工作零零碎碎接触过加密及加密算法相关的信息,但是也只是听说过,并不知道这些算法用处和区别。

最近由于工作安排,需要将Fernet算法实现一版C++的加解密定制程序,由于Fernet组合了许多的基础加密算法,因此在这个地方,对一些常用的基础加密算法做一个总结。

加密是什么?


介绍:把"原始数据"通过某种算法+秘钥变换后,得到了"加密数据"的这个过程叫做加密。从这里也可以看到,对于加密来说,其核心要素就是:加密算法+秘钥。

意义:对于"加密数据"来说,未授权的用户,不能够直接知道"原始数据"的内容。

下面介绍一些常见的加密类别和加密相关配套的其他内容。

对称加密


对称加密是指使用 一个相同秘钥 就能对消息进行加密和解密的加密方法。

对称加密的特点就是使用 一个相同秘钥。

我们常见的对称加密有:XOR、AES等。下面我们来简单分析一下AES加密。

AES加密

高级加密标准(Advanced Encryption Standard,AES),是一种区块加密标准。从它的定义来看,其特点是区块加密,一般来说,我们常见的有AES128和AES256。

例如我们可以用如下的openssl提供的方法进行AES的加解密:

c 复制代码
int AES_set_encrypt_key(const unsigned char *userKey, const int bits,
                        AES_KEY *key);
int AES_set_decrypt_key(const unsigned char *userKey, const int bits,
                        AES_KEY *key);

//注意这里只是一种cbc加密方法,aes还有很多其他加密方法
void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
                     size_t length, const AES_KEY *key,

其实我们从上面的描述来看,就知道AES一般以16字节或者32字节为一组(我们将其定义为区块大小),进行加密。这里就会引申出一个问题,如果我们的数据(最后一组消息)不够一个区块大小,我们应该怎么办?这里提供的常见方案就是做填充。

非对称加密(公钥加密算法)


我们知道,加密一般是用于消息传递,那么如果是对称加密的情况下,要传递的两个事务:加密消息+秘钥。对于对称加密来说,如果我们的秘钥泄露了,就基本上等于明文传递了。因此,有了非对称加密。其实后面很多的加密方法就在平衡 加密消息+秘钥 两个事情。

非对称加密就是有两个秘钥(私钥,公钥),实现1对多的双向通信。其中我们常见的加密方式是:RSA,ECC等。下面,我们尝试简单介绍RSA。

RSA 加密算法

简介(来自于Wikipedia):RSA加密算法是一种非对称加密算法。RSA是由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)在1977年一起提出的。当时他们三人都在麻省理工学院工作。RSA 就是他们三人姓氏开头字母拼在一起组成的。

RSA只从使用的角度来说,只需要了解如下的大致的概念:

  • 模数N、公钥指数pubE、私钥指数priE
  • 加密:秘文=明文^priE mod N,秘文=明文^pubE mod N
  • 解密:明文=秘文^pubE mod N,明文=秘文^priE mod N

从上面来看,私钥= 私钥指数priE + 模数N,公钥= 公钥指数pubE + 模数N。至于公钥,私钥,模数怎么来的,网上已有许多资料,有兴趣可以去看看。

基于openssl的RSA代码节选:

c 复制代码
// 生成秘钥,秘钥里面包含了私钥+公钥
int RSA_generate_key_ex(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb);

// 基于公钥和私钥的加解密
int RSA_public_encrypt(int flen, const unsigned char *from,
                       unsigned char *to, RSA *rsa, int padding);
int RSA_private_encrypt(int flen, const unsigned char *from,
                        unsigned char *to, RSA *rsa, int padding);
int RSA_public_decrypt(int flen, const unsigned char *from,
                       unsigned char *to, RSA *rsa, int padding);
int RSA_private_decrypt(int flen, const unsigned char *from,
                        unsigned char *to, RSA *rsa, int padding);

散列函数(Hash function)


介绍:使用 散列函数(md5, sha1, sha256) 对原始数据进行处理,生成散列值(数字摘要)。这些散列函数保证一个输入对应一个输出,并且通过散列值无法得到原始数据输入,因此不属于加密。

此外,如果散列函数不够健壮,可能会出现碰撞的可能性。

意义:由于其不同的数据输入会得到不同的散列值,因此主要用来校验数据的完整性。

基于openssl的常见散列函数代码节选:

c 复制代码
//注意,根据官网的描述,如下这些接口已经逐步被弃用,这里只是起示例作用。
int SHA1_Init(SHA_CTX *c);
int SHA1_Update(SHA_CTX *c, const void *data, size_t len);
int SHA1_Final(unsigned char *md, SHA_CTX *c);

//注意,根据官网的描述,如下这些接口已经逐步被弃用,这里只是起示例作用。
int SHA256_Init(SHA256_CTX *c);
int SHA256_Update(SHA256_CTX *c, const void *data, size_t len);
int SHA256_Final(unsigned char *md, SHA256_CTX *c);

其实做过编程的人,对hash这个概念并不陌生。

消息认证码


含义:使用 散列函数+秘钥来生成认证码(MAC)。相较于散列函数来说,对于同一个消息,需要指定的秘钥+散列函数才能够得到最终的认证码。

意义:在散列函数中,原始数据和散列值是绑定的。在一个消息传递系统中,散列函数只解决了数据完整性的问题,并没有解决数据来源是否可靠的问题(可以伪造原始数据+新散列值)。 通过消息验证码,由于有了秘钥的保护, 解决了 散列值+消息 来源不可靠的问题。

基于openssl的消息认证码代码节选:

c 复制代码
//这里的evp_md用来描述不同的散列函数
unsigned char *HMAC(const EVP_MD *evp_md, const void *key,
                     int key_len, const unsigned char *d, size_t n,
                     unsigned char *md, unsigned int *md_len);

签名算法


下面是签名的一个简单流程:

  • 基于公钥加密系统,生成秘钥对(公钥、私钥)。
  • 对消息进行数字摘要计算。
  • 对数字摘要进行私钥加密,得到数字签名。
  • 将信息、数字签名、公钥发给接收者,接收者可以验证:信息的完整性+发送者的身份。

从这里来看,我们可以知道,签名其实和消息认证码具有相同的意义,都能够验证:完整性+发送者的身份。但是由于其原理,导致其应用场景不一致。例如:数字签名由于使用非对称加密,而消息认证码可以认为是使用了对称加密,导致他们的使用场景是不一样的。

CA机构与数字证书


其实从名字上来看,就知道CA机构与数字证书和签名算法相关,为了解决公钥加密系统中,公钥是否可信的问题。下面是它们的一些概念:

  • CA(Certificate Authority)是"证书颁发机构"的简称,它是"受信任"的第三方组织,负责验证申请者身份并发放数字证书。
  • 数字证书是:特定用户或系统身份信息(如域名、组织名等)+ 该用户公钥 + 由权威机构(CA)签署的数字签名(对用户的信息+公钥) 组成的数据结构。

简单来说,由于我们信任了CA的证书(CA的公钥),我们可以用CA的公钥解密出 数字证书用户的公钥,然后我们用 数字证书用户的公钥 和 数字证书用户加密通信。总的来说,数字证书其实是一个信任链的问题。

Base64 编码


介绍:一种将 二进制数据 编码为 可见字符 的编码方法。简单来说就是用 4个字节的可见字符 代替是 3个字节数据 的编码过程。

意义:可以将二进制数据表达为文本信息,方便后续对二进制数据进行处理。

数据区块填充,PKCS(Public-Key Cryptography Standards)


特别说明,PKCS是一系列公钥密码标准,里面具备很多的内容,其大概有15个标准,分别是pkcs1,pkcs2 ... ... pkcs14,pkcs15

从上面我们知道,现在常用的填充方法是:公钥加密标准(PKCS,Public-Key Cryptography Standards)里面的节选内容。其有很多填充类别,例如:PKCS1-padding节选, PKCS5-padding节选, PKCS7-padding节选,我们这里只讨论pkcs5和pkcs7。(注意,这些标准除了数据填充,还有其他定义内容。)

下面先介绍PKCS5-padding,先定义数据区块大小为SIZE,pkcs5和pkcs7的填充说明,下面以SIZE=8字节举例:

  • 要填充7个字节,那么填入的值就是0×07;
  • 如果只填充1个字节,那么填入的值就是0×01;
  • 数据恰好是8的倍数时还要补8个字节的0×08。

从上面的PKCS5-padding,我们可以知道,这里需要数据区块固定为8字节。如果我们想给其他大小的数据区块做数据对齐,那么PKCS5-padding就不满足要求,因此就有PKCS7-padding的填充方法:

  • PKCS7-padding的数据区块大小可以是:1-255字节。
  • 其他的,PKCS7-padding填充原理和PKCS5一样。

从上面可知,PKCS7-padding节选包含了PKCS5-padding节选,且适用性范围更加的广泛。

到了这里,我们就能够对AES128或者AES256做基于PKCS7-padding的16字节对齐或者32字节对齐的操作了。

这里的代码实现很简单,这里提供一种方法,大概就是:首先对消息进行强制扩大255字节,然后根据数据区块大小做填充即可,基本可以实现pkcs7填充的原理。

后记


其实,我们从上面的内容来看,能发现一些规律:

  • 由于信任链的存在,因此有了CA机构和数字证书,而数字证书与签名算法相关,而签名算法又与 非对称加密和散列函数强相关。

  • 然后为了实现我们现在常见的加密系统,我们使用了:对称加密、非对称加密、散列函数、消息认证码和base64编码等手段。

参考文献


打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)

PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。