【OpenSSL】哈希、非对称加密和对称加密函数使用
1.哈希
1.1 md5的使用
- 头文件
#include <openssl/md5.h>
#include <openssl/sha.h>
- MD5 散列值的长度
# define MD5_DIGEST_LENGTH 16 // 根据这个分配一块空内存保存散列值
- 初始化MD5 -> 给MD5传入运算的数据(可以多次传入) -> 计算MD5
# define MD5_DIGEST_LENGTH 16 // md5哈希值长度 // 初始化函数, 初始化参数 c int MD5_Init(MD5_CTX *c); /* 参数c: 传出参数 */ // 添加md5运算的数据, 没有计算数据, 所以可以多次添加数据 int MD5_Update(MD5_CTX *c, const void *data, size_t len); /* 参数: c: MD5_Init() 初始化得到的 data: 传入参数, 字符串 len: data数据的长度 */ // 对添加的数据进行md5计算 int MD5_Final(unsigned char *md, MD5_CTX *c); /* 参数: md: 传出参数, 存储得到的哈希值 c: MD5_Init() 初始化得到的 */
- 通过传参直接生成 md5 哈希值
// 通过传递的参数, 直接生成一个md5哈希值,只能添加一次数据 unsigned char *MD5(const unsigned char *d, size_t n, unsigned char *md); /* 参数: - d: 传入, 要进行md5运算的字符串 - n: 字符串的的长度 - md: 传出, 存储md5的哈希值 返回值: 这个地址的函数第三个参数md地址 */
- 编译时记得加 lcrypto -lssl
2. 非对称加密
2.1 非对称加密简介
- 密钥对:公钥、私钥(非对称加密,一方加密只有另一方可以解密)
- 公钥加密、必须私钥解密
- 私钥加密、必须公钥解密
- 应用场景:
- 密钥分发(对称加密的密钥分发)
- 公钥加密,私钥解密
- 将公钥分发给其他人,其他人都可以使用公钥加密数据,但只有自己的私钥可以解密数据
- 数字签名(验证数据是否被篡改、数据的所有者)
- 私钥加密,公钥解密
- 签名
- 对原始数据进行哈希运算,对哈希值加密得到密文
- 将原始数据和加密的哈希值发送给其他人
- 校验签名
- 收到 原始数据 和 加密的哈希值
- 对原始数据求 哈希值
- 对加密的哈希值解密得到 哈希值
- 比较两个哈希值判断数据是否被篡改(还需要验证公钥的发送人身份,CA机构就是干这个的)
- 密钥分发(对称加密的密钥分发)
2.2 生成RSA密钥对
- 头文件
#include <openssl/rsa.h>
- 得到RSA类型的变量
RSA* RSA_new(void); // 申请一块内存, 存储了公钥和私钥 void RSA_free(RSA *);
- 生成密钥对,取公钥和私钥
int RSA_generate_key_ex(RSA* rsa, int bits, BIGNUM* e, BN_GENCB* cb); /* 参数: rsa: 通过RSA_new()获得 bits: 秘钥长度, 单位: bit, 常用的长度 1024*n (n正整数) e: 比较大的数(5位以内) 通过 BIGNUM* e = BN_new(); 获得 初始化: BN_set_word(e, 12345); cb: 回调函数, 用不到, 直接写NULL */ // rsa公钥私钥类型是一样的: RSA类型 // 将参数rsa中的公钥提取出来 RSA* RSAPublicKey_dup(RSA* rsa); /* rsa参数: 秘钥信息 返回值: rsa公钥 */ // 将参数rsa中的私钥提取出来 RSA* RSAPrivateKey_dup(RSA* rsa); /* rsa参数: 秘钥信息 返回值: rsa私钥 */
- 将密钥存储到磁盘
#include <openssl/pem.h> // 头文件 extern "C" { // 由于dll库里没有这个部分,需要包含进去一起编译 #include <openssl/applink.c> } int PEM_write_RSAPublicKey(FILE* fp, const RSA* 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: 需要打开一个磁盘文件, 并且指定写权限 r: 存储了密钥对 /------- 私钥独有的参数 -------/ enc: 指定的加密算法 -> 对称加密 -> NULL kstr: 对称加密的秘钥 -> NULL klen: 秘钥长度 -> 0 cb: 回调函数, 用不到, NULL u: 给回调传参, 用不到, NULL */ 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);
- 示例代码
#include <openssl/pem.h> using namespace std; int main() { RSA* rsa = RSA_new(); // 创建RSA变量 BIGNUM* e = BN_new(); // 创建bignum变量并实例化 BN_set_word(e, 12345); RSA_generate_key_ex(rsa, 4096, e, NULL); // 生成密钥对 FILE* fp = fopen("public.pem", "w"); PEM_write_RSAPublicKey(fp, rsa); // 写入公钥 fp = fopen("private.pem", "w"); PEM_write_RSAPrivateKey(fp, rsa, NULL, NULL, 0, NULL, NULL); // 写入私钥 fclose(fp); }
2.3 加密
- 使用公钥或私钥,加密或解密(加密后的长度和密钥长度相同,还需要指定填充方案,所以传入的数据应该小于等于密钥长度 - 填充长度
// ---- 加密使用 ---- // 公钥加密 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: 要加密/解密的数据长度 加密数据的长度 0 < flen <= 秘钥长度-11,解密数据的长度=密钥长度 from: 传入, 要加密/解密的数据 to: 传出, 存储数据, 加密->存储密文, 解密->存储明文 rsa: 秘钥: 公钥/私钥 padding: 指定填充方案, 数据填充, 不需要使用者做 RSA_PKCS1_PADDING -> 使用该方案会填充11字节 */
- 代码示例
#include <openssl/pem.h> #include <string.h> #include <iostream> using namespace std; int main() { // 创建密钥过程 RSA *rsa = RSA_new(); // 创建RSA变量 BIGNUM *e = BN_new(); // 创建bignum变量并实例化 BN_set_word(e, 12345); RSA_generate_key_ex(rsa, 1024, e, NULL); // 生成密钥对 RSA *pubKey = RSAPublicKey_dup(rsa); RSA *priKey = RSAPrivateKey_dup(rsa); // 加密过程 int keyLen = RSA_size(pubKey); // 数据被加密后和密钥的长度相同 string msg = "Hello world!"; char *buf = new char[keyLen]; int ret = RSA_public_encrypt(msg.size(), (const unsigned char *)msg.data(), (unsigned char *)buf, pubKey, RSA_PKCS1_PADDING); cout << "加密后的长度为 " << ret << endl; // 解密过程 char *newText = new char[keyLen]; ret = RSA_private_decrypt(128, (const unsigned char *)buf, (unsigned char *)newText, priKey, RSA_PKCS1_PADDING); cout << "解密后的长度为 " << ret << endl; cout << newText << endl; }
2.4 签名
- 加密哈希值和验证哈希值,其实就是帮我们把加密和验证放到一个函数里了(这里有一个坑,要签名的数据长度必须小于密钥长度-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_SHA224 ..... m: 要进行签名的数据 m_length: 要签名的数据长度 - 0 < m_length <= 秘钥长度-11 sigret: 传出, 存储了签名之后的数据 -> 密文 siglen: sigret密文长度 rsa: 私钥 返回值: 判断函数状态 */ int RSA_verify(int type, const unsigned char *m, unsigned int m_length, const unsigned char *sigbuf, unsigned int siglen, RSA *rsa); /* 参数: type: 使用的哈希算法, 和签名使用的哈希算法一致 NID_MD5 NID_SHA1 NID_SHA224 ..... m: 进行签名的原始数据 -> 接收到的 m_length: m参数字符串的长度 sigbuf: 接收到的签名数据 siglen: sigbuf接收到的签名数据的长度 rsa: 公钥 返回值: 如果!=1: 失败 如果==1: 成功 */
3. 对称加密
- 生成加密解密的key
#include <openssl/aes.h> # define AES_BLOCK_SIZE 16 // 明文分组的大小 // 加密的时候调用,aes中的秘钥格式 AES_KEY // 封装加密时候使用的秘钥 AES_KEY key; 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); /* userkey: 对称加密的密钥->字符串长度: 16, 24, 32byte bites: 指定密钥的长度: 单位 bit key: 传出参数 */
- CBC方式加密 - 密码分组链接模式
- ivec: 一个初始化向量,长度与分组长度相同(16, 24, 32byte -> 128, 192, 256bit)。注意这个初始化向量传入之后会一直参与计算,实际上他是个传入传出参数,虽然没什么用
- length: 密文长度必须是16(24, 32)的整数倍,如果不够需要自己计算补充
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的长度 (len = (字符串长度 + \0) % 16) == 0 如果不是在函数内部会自动填充 实际长度: ((len / 16) + 1 ) * 16 key: 初始化之后的秘钥 ivec: 初始化向量, 字符串 ==> 长度和分组长度相同 enc: 指定数据要解密还是解密 # define AES_ENCRYPT 1 -> 加密 # define AES_DECRYPT 0 -> 解密 */
- 示例代码
#include <openssl/aes.h> #include <string.h> #include <iostream> using namespace std; int main() { // 1.准备数据 const char *pt = "data1, data2, data3, data4, data5, data6, data7, data8, data9, data10"; // 2.准备密钥 const char *key = "123456787654321"; // 16位(或24,32) // 3.初始化密钥 AES_KEY encKey; AES_set_encrypt_key((const unsigned char*)key, 128, &encKey); // 4.加密 int length = 0; int len = strlen((char *)pt) + 1; if (len % 16 != 0) { length = ((len / 16) + 1) * 16; } else { length = len; } unsigned char *out = new unsigned char[length]; // 存储密文 unsigned char ivec[16]; memset(ivec, 9, sizeof(ivec)); AES_cbc_encrypt((const unsigned char*)pt, out, length, &encKey, ivec, AES_ENCRYPT); // 5.解密 unsigned char* data = new unsigned char[length]; AES_KEY deckey; AES_set_decrypt_key((const unsigned char*)key, 128, &deckey); memset(ivec, 9, sizeof(ivec)); AES_cbc_encrypt((const unsigned char*)out, data, length, &deckey, ivec, AES_DECRYPT); // 6.打印 cout<<data<<endl; delete[] out; delete[] data; }