#对称加密和非对称加密
程序员文章站
2024-03-16 20:37:28
...
一、对称加密
加解密使用同一个**,加密效率高,但是加密强度比较低,**分发困难,不能再网络环境中直接发送。对称**加密法主要基于块加密,选取固定长度的**,去加密明文中固定长度的块,生成的密文块与明文块长度一样。显然**长度十分重要,块的长度也很重要。如果太短,则很容易枚举出所有的明文-密文映射;如果太长,性能则会急剧下降对称加密有如下方法:
-
DES/3DES
- DES现在已经不够安全
- **长度为8字节
- 加密方式为分组加密,每组8字节通过算法和**进行运算
- 明文和密文长度相同
- 3DES安全但是效率低
- 三重数据加密, 对每个数据块应用三次DES加密算法
- **长度为24字节,**会被平均分为三部分
- 加密过程:加密->解密->加密
- 解密过程:解密->加密->解密
- DES现在已经不够安全
-
AES
- 安全,效率高,也是分组加密
- **长度可选16byte, 24byte, 32 byte,分组长度和**长度相同
- Blowfish
- RC2/RC4/RC5
- IDEA
- SKIPJACK
二、非对称加密
加解密使用的秘钥不同, 使用的是一个**对,加密效率低,加密强度高,秘钥长度比对称加密长,**对需要使用对应的非对称加密算法生成,这两个**对分别保存在两个文件中。公钥可以公开,私钥 不能公开, 要妥善保管。如果使用公钥加密, 必须使用私钥解密。如果使用私钥加密, 必须使用公钥解密。
-
RSA(数字签名和**交换)
- 秘钥交换 -> 交换的对称加密的**
- 主机A::生成非对称加密的**对, 分发公钥给主机B。
- 主机B::得到了主机A的公钥,生成对称加密的秘钥, 使用得到的公钥进程加密,得到密文发送给主机A。
- 主机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>
}