#对称加密和非对称加密

一、对称加密

​ 加解密使用同一个密钥,加密效率高,但是加密强度比较低,密钥分发困难,不能再网络环境中直接发送。对称密钥加密法主要基于块加密,选取固定长度的密钥,去加密明文中固定长度的块,生成的密文块与明文块长度一样。显然密钥长度十分重要,块的长度也很重要。如果太短,则很容易枚举出所有的明文-密文映射;如果太长,性能则会急剧下降对称加密有如下方法:

  • DES/3DES
    • DES现在已经不够安全
      • 密钥长度为8字节
      • 加密方式为分组加密,每组8字节通过算法和密钥进行运算
      • 明文和密文长度相同
    • 3DES安全但是效率低
      • 三重数据加密, 对每个数据块应用三次DES加密算法
      • 密钥长度为24字节,密钥会被平均分为三部分
      • 加密过程:加密->解密->加密
      • 解密过程:解密->加密->解密
  • AES
    • 安全,效率高,也是分组加密
    • 密钥长度可选16byte, 24byte, 32 byte,分组长度和密钥长度相同
  • Blowfish
  • RC2/RC4/RC5
  • IDEA
  • SKIPJACK

二、非对称加密

​ 加解密使用的秘钥不同, 使用的是一个密钥对,加密效率低,加密强度高,秘钥长度比对称加密长,密钥对需要使用对应的非对称加密算法生成,这两个密钥对分别保存在两个文件中。公钥可以公开,私钥 不能公开, 要妥善保管。如果使用公钥加密, 必须使用私钥解密。如果使用私钥加密, 必须使用公钥解密。

  • RSA(数字签名和密钥交换)
    • 秘钥交换 -> 交换的对称加密的密钥
      1. 主机A::生成非对称加密的密钥对, 分发公钥给主机B。
      2. 主机B::得到了主机A的公钥,生成对称加密的秘钥, 使用得到的公钥进程加密,得到密文发送给主机A。
      3. 主机A:接收主机B加密之后的密文,使用私钥解密,得到明文(对称加密的秘钥)。
  • ECC(椭圆曲线加密算法)
  • Diffie-Hellman(DH, 密钥交换)
  • El Gamal(数字签名)
  • DSA(数字签名)

三、哈希算法(单向散列函数)

​ 将任意长度的数据转换成固定长度的数据,长度由哈希算法确定。有很强的抗碰撞性:原始数据不同, 通过哈希算法进行运算, 得到的结果也不同。不可逆性:哈希运算的结果无法转换为原始数据。哈希运算得到的结果可称为:散列值、哈希值、指纹。哈希函数的散列值默认是二进制格式的,哈希算法得到的散列值常用于消息认证码和数字签名。常用的哈希算法有:

  • MD4/MD5,散列值长度为16byte
  • SHA-1,散列值长度为20byte
  • SHA224 ,散列值长度为28byte
  • SHA256,散列值长度为32byte
  • SHA384,散列值长度为48byte
  • SHA512,散列值长度为64byte

四、消息认证码

​ 使用消息认证码的目的是为了确认数据是否被他人篡改。消息认证码=(原始数据+密钥)进行哈希运算。发送的数据=(原始数据+消息认证码)。消息认证码使用的是对称加密的密钥。

  • 主机A:
    • 对原始数据和密钥进行哈希运算 -> 消息认证码
    • 把消息认证码放到原始数据的后边, 发送出去
  • 主机B:
    • 将消息认证码和原始数据拆分
    • 求原始数据和密钥的散列值
    • 比较散列值和消息认证码
      • 相同: 校验成功
      • 不同: 校验失败

缺点:无法确认数据是由通信的哪一方发送。而数字签名可以实现不可否认性。

五、 数字签名

数字签名是为了证明某条数据/信息属于某某人的, 数字签名使用非对称加密的密钥。

  • 签名过程:

    • 对原始数据进行哈希运算 -> 散列值
    • 使用非对称加密的私钥, 对散列值加密 -> 密文
    • 把密文放到原始数据的后边, 发送出去
  • 校验签名的过程:

    • 接收签名的数据
    • 原始数据和密文拆分
    • 使用哈希函数求原始数据的散列值 -> 散列值1
    • 使用公钥将密文解析出来 -> 散列值2
    • 比较两个散列值
      • 相同: 校验成功
      • 不同: 校验失败

六、案例

1、安装openssl

安装openssl教程网上比比皆是,且比较简单,这里不提供方法。

2、哈希
  • 头文件
#include <openssl/md5.h>
#include <openssl/sha.h>
  • MD5函数
// 散列值长度的宏 
# define MD5_DIGEST_LENGTH 16

// 初始化函数, 传出函数, 初始化一个MD5_CTX变量
int MD5_Init(MD5_CTX *c);

// 添加数据 -> 进行md5运算的数据
int MD5_Update(MD5_CTX *c, const void *data, size_t len);
	参数:
		- c: MD5_Init初始化得到的
		- data: 进行md5运算的数据
		- len: data字符串长度
// 计算md5散列值
int MD5_Final(unsigned char *md, MD5_CTX *c);
	参数:
		- md: 传出参数, 散列值
		- c: MD5_Init初始化得到的 

// 计算散列值的第二种方式,数据量比较少的时候使用
unsigned char *MD5(const unsigned char *d, size_t n, unsigned char *md);
	参数:
		- d: 进行md5运算的数据
		- n: d字符串的长度
		- md: 传出参数, 散列值
  • ​ MD5函数案例
	//准备要求散列值的数据
	unsigned char str[] = "hello world!";
	MD5_CTX md5;
	//初始化md5
	MD5_Init(&md5);
	//添加求散列值的数据,需要减去末尾的‘\0’
	MD5_Update(&md5, str, sizeof(str) - 1);
	unsigned char finalMd[MD5_DIGEST_LENGTH];
	char res[MD5_DIGEST_LENGTH * 2 + 1]{ 0 };
	//求散列值
	MD5_Final(finalMd, &md5);
	//将二进制的散列值转换为十六进制
	for (int i = 0; i < MD5_DIGEST_LENGTH; ++i)
	{
		snprintf(&res[i * 2], sizeof(res), "%02x", finalMd[i]);
	}
	cout << "md: " << res << endl;
3、对称加密
  • AES生成加解密key函数
# define AES_BLOCK_SIZE 16	// 明文分组的大小
// 加密的时候调用
// aes中的秘钥格式 AES_KEY
int AES_set_encrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
	参数:
        - userkey					密钥字符串, 长度可选: 16byte, 24byte, 32byte
        - bites					密钥的长度, 单位: bit -> byte = bit/8
        - key						传出参数: 保存了设置的秘钥信息
int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
  • CBC方式加密 - 密码分组链接模式
//加解密都是这一个函数
void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
                     size_t length, const AES_KEY *key,
                     unsigned char *ivec, const int enc);
	参数:
		- in: 要加密的数据/要解密的数据(明文/密文)
		- out: 加密-> 密文, 解密 -> 明文 (传出参数)
        - length: 第一个参数in的长度, 必须是16的整数倍, 如果不够需要填充
        	- 正好是16的整数倍: (strlen(in)+1) % 16 == 0
            - 不正好, 将大小进行扩充
            	length = ((stlen(in)+1) / 16 +1) * 16
        - key: 秘钥
        - ivec: CBC模式的初始化字符串, 和第一个明文分组进行位运算,16个字符
        	加解密的时候保证该数组中的数据完全相同
        - enc: 加解密的标志
        	# define AES_ENCRYPT     1
			# define AES_DECRYPT     0
  • AES案例
	//CBC的初始化向量
	unsigned char ivec[AES_BLOCK_SIZE];
	memset(ivec, 9, AES_BLOCK_SIZE);
	//准备要加密的数据
	unsigned char str[] = "准备要加密的数据:hello world!";
	//保证length是十六的整数倍
	int length = sizeof(str);
	length = length % 16 == 0 ? length : (length / 16 + 1) * 16;
	//得到加密密钥
	AES_KEY encKey;
	unsigned char userKey[] = "1234567887654321";
	AES_set_encrypt_key(userKey, 128, &encKey);
	//对称加密是基于块加密,明文和密文长度一样
	unsigned char* encStr = new unsigned char[length];
	//使用CBC方式加密
	AES_cbc_encrypt(str, encStr, length, &encKey, ivec, AES_ENCRYPT);
	cout << "加密之后的数据: " << encStr << endl;

	// 解密
	AES_KEY decKey;
	//由于AES_cbc_encrypt中的ivec参数没有const修饰,所以可能是值-结果参数,这里重置一下
	memset(ivec, 9, sizeof(ivec));
	AES_set_decrypt_key(userKey, 128, &decKey);
	unsigned char* decText = new unsigned char[length];
	AES_cbc_encrypt(encStr, decText, length, &decKey, ivec, AES_DECRYPT);
	cout << "解密之后的数据: " << decText << endl;
4、非对称加密
  • 生成RSA密钥对
#include <openssl/rsa.h>
// 申请一块内存
RSA *RSA_new(void);
BIGNUM* BN_new(void);

// 生成密钥对, 密钥对存储在内存中
int RSA_generate_key_ex(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb);
	参数:
		- rsa: 结构体, 通过RSA_new()得到一个实例, 密钥对最终存储在该变量中
		- bits: 秘钥的长度单位-bit, 字节数: bits/8 
        - e: BIGNUM结构体变量, 通过BN_new(),可以得到一个实例
        - cb: 函数指针, 通常不同, 指定为NULL

// 将参数rsa中的公钥提取出来
RSA *RSAPublicKey_dup(RSA *rsa);
// 将参数rsa中的私钥提取出来
RSA *RSAPrivateKey_dup(RSA *rsa);

// 写入文件中的公钥私钥数据不是原始数据, 写入的编码之后的数据
// 是一种pem的文件格式, 数据使用base64进行编码
BIO *BIO_new_file(const char *filename, const char *mode);
	参数:
		- filename: 操作的磁盘文件
		- mode: 对磁盘文件的操作方式 == fopen的打开文件的方式
			- r / w / r+ / w+ 
int PEM_write_bio_RSAPublicKey(BIO* bp, const RSA* r);
int PEM_write_bio_RSAPrivateKey(BIO* bp, const RSA* r, const EVP_CIPHER* enc, 
	unsigned char* kstr, int klen, pem_password_cb *cb, void* u);
int PEM_write_RSAPublicKey(FILE* fp, const RSA* r);
	参数:
		- fp: 通过fopen打开公钥文件
		- r: 这边变量中有公钥信息
int PEM_write_RSAPrivateKey(FILE* fp, const RSA* r, const EVP_CIPHER* enc, 
	unsigned char* kstr, int klen, pem_password_cb *cb, void* u);
	参数:
		- fp: 通过fopen打开私钥文件
		- r: 这边变量中有私钥信息
		- enc: 对私钥数据加密使用的加密算法 - 不加密指定为NULL
		- kstr: NULL, 据分析:这是对称加密算法的秘钥
		- klen: 对称加密的秘钥长度, 没有秘钥指定为0
		- cb: 回调函数, 不使用, NULL
		- u: 给回调函数传参, NULL


//读取RSA密钥对
RSA* PEM_read_bio_RSAPublicKey(BIO* bp, RSA** r, pem_password_cb *cb, void* u);
RSA* PEM_read_bio_RSAPrivateKey(BIO* bp, RSA** r, pem_password_cb *cb, void* u);
RSA* PEM_read_RSAPublicKey(FILE* fp, RSA** r, pem_password_cb *cb, void* u);
RSA* PEM_read_RSAPrivateKey(FILE* fp, RSA** r, pem_password_cb *cb, void* u);
	参数:
		- fp: 使用fopen打开公钥/私钥文件
		- r: 传出参数, 公钥/私钥信息被写入该变量
		- cb: 回调函数, NULL
		- u: 给回调传参, NULL
	返回值: 
		成功: 这个指针指向第二个参数的地址 r指针指向的地址
		失败: NULL
  • 加解密
int RSA_public_encrypt(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);
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);
参数:
	- flen: 要加密/解密的数据长度, 不能大于秘钥长度
			数据的最大长度: 秘钥长度 - 11
	- from: 要加密/解密的数据 -> 传入参数
	- to: 存储加密或解密得到的数据 -> 传出参数
	- rsa: 公钥/私钥数据
	- padding: 填充, 一般使用 RSA_PKCS1_PADDING ->11个字节
  • 签名
int RSA_sign(int type, const unsigned char *m, unsigned int m_length,
             unsigned char *sigret, unsigned int *siglen, RSA *rsa);
	参数:
		- type: 指定使用什么样的哈希算法
			NID_MD5/NID_SHA1/NID_SH224
		- m: 要进行签名的数据
		- m_length: 要签名的数据长度, 第二个参数字符串长度
		- sigret: 传出参数, 签名之后的数据
		- siglen: 得到的签名的长度
		- rsa: 私钥
int RSA_verify(int type, const unsigned char *m, unsigned int m_length,
               const unsigned char *sigbuf, unsigned int siglen, RSA *rsa);
	参数:
		- type: 指定使用什么样的哈希算法
		- m: 要验证签名的数据
		- m_length: 要签名的数据长度, 第二个参数字符串长度
		- sigret: 传入参数, 对方签名得到的数据, 要校验的签名
		- siglen: 要校验的签名的长度
		- rsa: 公钥
	返回值:
		- 校验失败: !=1
        - 成功: 1
5、其他

​ 在使用非对称加密函数时,需要包含一个源文件->applink.c,如果创建的是c++项目,需要使用extern。

extern "c"
{
    #include<openssl/applink.c>
}

猜你喜欢

转载自blog.csdn.net/qq_44630120/article/details/108442145