OpenSSL是为网络通信提供安全及数据完整性的开放源代码软件库包,囊括了主要的密码算法、常用的密钥和证书封装管理功能以及SSL协议。 |
官网:https://www.openssl.org/ 下载:https://slproweb.com/products/Win32OpenSSL.html |
一、OpenSSL环境搭建(Windows)
1、Vcpkg包安装
vcpkg.exe install openssl |
2、CMakeLists
find_package(OpenSSL REQUIRED) target_link_libraries(main PRIVATE OpenSSL::SSL OpenSSL::Crypto) |
二、OpenSSL环境搭建(Linux)
Linux发行版自带OpenSSL 使用时链接动态库:gcc your_program.c -o your_program -lssl -lcrypto |
三、哈希算法(Hash)
哈希函数,又称散列算法,是一种从任何一种数据中创建小的数字“指纹”的方法; 散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来,该函数将数据打乱混合,重新创建一个叫做散列值(或哈希值)的指纹; 散列值通常用一个短的随机字母和数字组成的字符串来代表。 |
1、SHA256
SHA256是SHA-2下细分出的一种算法; 对于任意长度的消息,SHA256都会产生一个256bit长的哈希值,称作消息摘要。 |
// 头文件 #include <openssl/sha.h> // 初始化 ctx int SHA256_Init(SHA256_CTX *ctx); // 计算 hash ,保存在 ctx 中,可反复调用 int SHA256_Update(SHA256_CTX *ctx, const void *data, size_t len); // 从 ctx 中输出 hash 值 int SHA256_Final(unsigned char *out, SHA256_CTX *ctx); // SHA256_Init() + SHA256_Update() + SHA256_Final() 三合一直接输出 hash 值 unsigned char *SHA256(const unsigned char *data, size_t len, unsigned char *out); |
#include <stdio.h> #include <string.h> #include <openssl/sha.h> static void mdPrint(unsigned char *buffer, int len){ int i; for(i=0; i<len; i++){ printf("%02X", buffer[i]); } putchar('\n'); } static void test_sha256(char* argv[], int argc){ SHA256_CTX ctx; SHA256_Init(&ctx);
int i; for(i=0; i<argc; i++){ SHA256_Update(&ctx, argv[i], strlen(argv[i])); }
unsigned char buffer[32]; SHA256_Final(buffer, &ctx);
printf("SHA256 32 : "); mdPrint(buffer, 32); } int main(int argc, char* argv[]){ if(argc < 2) return -1; test_sha256 (argv+1, argc-1); return 0; } |
./hashTest 0123456789 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ SHA256 32 : B56CC1F0EF1A616B11ED1A8A82239DB6CAD0D73E856D39FCEDEA8BB634CE5B1B |
2、SHA512
SHA512是SHA-2下细分出的一种算法; 对于任意长度的消息,SHA256都会产生一个512bit长的哈希值,称作消息摘要。 |
// 头文件 #include <openssl/sha.h> // 初始化 ctx int SHA512_Init(SHA512_CTX *ctx); // 计算 hash ,保存在 ctx 中,可反复调用 int SHA512_Update(SHA512_CTX *ctx, const void *data, size_t len); // 从 ctx 中输出 hash 值 int SHA512_Final(unsigned char *out, SHA512_CTX *ctx); // SHA512_Init() + SHA512_Update() + SHA512_Final() 三合一直接输出 hash 值 unsigned char *SHA512(const unsigned char *data, size_t len, unsigned char *out); |
#include <stdio.h> #include <string.h> #include <openssl/sha.h> static void mdPrint(unsigned char *buffer, int len){ int i; for(i=0; i<len; i++){ printf("%02X", buffer[i]); } putchar('\n'); } static void test_sha512(char* argv[], int argc){ SHA512_CTX ctx; SHA512_Init(&ctx);
int i; for(i=0; i<argc; i++){ SHA512_Update(&ctx, argv[i], strlen(argv[i])); }
unsigned char buffer[64]; SHA512_Final(buffer, &ctx);
printf("SHA512 64 : "); mdPrint(buffer, 64); } int main(int argc, char* argv[]){ if(argc < 2) return -1; test_sha512 (argv+1, argc-1); return 0; } |
./hashTest 0123456789 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ SHA512 64 : 858362AA339DD91A84822CD3BBE46F4CC1D894AC04114F3C873C0E5F2BC73AE26E9A7EEDD9C11C8A376651D314AF4808045A0BE46593B54083A2DC19FDCD190A |
3、MD5
MD5消息摘要算法,属Hash算法一类,MD5算法对输入任意长度的消息进行运行,产生一个128位的消息摘要(32位的数字字母混合码); 不可逆,相同数据的MD5值肯定一样,不同数据的MD5值不一样; 虽说MD5有不可逆的特点,但是由于某些MD5破解网站,专门用来查询MD5码,其通过把常用的密码先MD5处理,并将数据存储起来,然后跟需要查询的MD5结果匹配,这时就有可能通过匹配的MD5得到明文,所以有些简单的MD5码是反查到加密前原文的; 为了让MD5码更加安全,涌现了很多其他方法,如加盐,盐要足够长足够乱 得到的MD5码就很难查到。 |
// 头文件 #include <openssl/md5.h> // 初始化 ctx int MD5_Init(MD5_CTX *ctx); // 计算 hash ,保存在 ctx 中,可反复调用 int MD5_Update(MD5_CTX *ctx, const void *data, size_t len); // 从 ctx 中输出 hash 值 int MD5_Final(unsigned char *out, MD5_CTX *ctx); // MD5_Init() + MD5_Update() + MD5_Final() 三合一直接输出 hash 值 unsigned char *MD5(const unsigned char *data, size_t len, unsigned char *out); |
#include <stdio.h> #include <string.h> #include <openssl/md5.h> static void mdPrint(unsigned char *buffer, int len){ int i; for(i=0; i<len; i++){ printf("%02X", buffer[i]); } putchar('\n'); } static void test_md5(char* argv[], int argc){ MD5_CTX ctx; MD5_Init(&ctx);
int i; for(i=0; i<argc; i++){ MD5_Update(&ctx, argv[i], strlen(argv[i])); }
unsigned char buffer[16]; MD5_Final(buffer, &ctx);
printf("MD5 16 : "); mdPrint(buffer, 16); } int main(int argc, char* argv[]){ if(argc < 2) return -1; test_md5 (argv+1, argc-1); return 0; } |
./hashTest 0123456789 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ MD5 16 : B9B3CC3F3A30D8EF2BB1E2E267ED97DE |
问题解决: error: openssl/md5.h: No such file or directory sudo apt-get install libssl-dev |
4、Pkcs5Kdf
定义:PKCS5KDF 是 Public-Key Cryptography Standards (PKCS) #5 中定义的密码学标准之一,用于从用户密码导出加密密钥; 算法:最常用的 PKCS5KDF实现是PBKDF2(Password-Based Key Derivation Function 2),其核心是重复应用一个哈希函数; 参数: 密码:用户输入的密码; 盐值(salt):一个随机值,用于增加随机性,防止彩虹表攻击; 迭代次数:增加计算复杂度,使得每次密钥生成都需要大量计算; 哈希函数:例如 SHA-256、SHA-512、MD5,用于每次迭代的计算。 |
#include <openssl/evp.h> #include <openssl/rand.h> #include <stdio.h> #include <string.h> #define SALT_SIZE 16 #define KEY_SIZE 32 #define ITERATIONS 10000 int main() { // 用户输入的密码 const char *password = "password"; unsigned char salt[SALT_SIZE]; unsigned char key[KEY_SIZE]; // 生成随机盐值 if (RAND_bytes(salt, sizeof(salt)) != 1) { fprintf(stderr, "generate salt failed!\n"); return 1; } // 使用 PBKDF2 生成密钥,哈希函数为 SHA-256 if (PKCS5_PBKDF2_HMAC(password, strlen(password), salt, sizeof(salt), ITERATIONS, EVP_sha256(), sizeof(key), key) != 1) { fprintf(stderr, "Key export failed!\n"); return 1; } // 输出盐值和密钥 printf("Salt: "); for (int i = 0; i < sizeof(salt); i++) { printf("%02x", salt[i]); } printf("\n"); printf("KEY: "); for (int i = 0; i < sizeof(key); i++) { printf("%02x", key[i]); } printf("\n"); return 0; } |
四、加密算法(Crypto)
1、对称加密
对称加密是一种加密算法,指加密和解密所使用的密钥是相同的; 在对称加密中,发送方和接收方使用相同的密钥对数据进行加密和解密; 对称加密算法的特点是速度快、加密效率高,并且适合处理大量数据。 |
![]() |
2、非对称加密
非对称加密是一种加密算法,与对称加密不同,非对称加密使用一对不同的密钥来进行加密和解密; 这对密钥中的一个被称为私钥(private key),另一个被称为公钥(public key),私钥只能由密钥的拥有者持有并保密,不对外公开,而公钥可以向任何人公开; 在非对称加密中,使用公钥对数据进行加密,只有用相应的私钥才能解密,非对称加密算法的特点是安全性高,能够提供身份验证和数据完整性保护。 |
![]() |
3、加密模式
ECB模式: |
特点:将每个数据块独立加密,相同的明文块会生成相同的密文块; 缺点:容易暴露数据的模式或结构,不适合加密大块数据。 |
CBC模式: |
特点:每个明文块在加密前与前一个密文块进行异或运算,初始向量(IV)用于加密第一个块; 优点:解决了ECB模式的弱点,不同的明文块即使相同也会生成不同的密文; 缺点:加密过程是串行的,无法并行处理。 |
CFB模式: |
特点:将前一个密文块加密并与下一个明文块异或生成新的密文块,支持小于块大小的数据加密; 优点:可用于流式加密,适合加密不完整的数据块; 缺点:解密时也是串行的。 |
OFB模式: |
特点:类似于CFB模式,但每次加密的输入仅是前一次的加密输出,而不是密文块; 优点:可以将块加密模式转换为流加密模式,且加密输出对明文无依赖性; 缺点:对密文块的任何修改都会影响解密的整个数据流。 |
CTR模式: |
特点:将一个递增的计数器与密钥加密后产生密文流,然后与明文块异或生成最终密文; 优点:支持并行处理,适合高性能加密场景。 |
XTS模式: |
特点:专为磁盘加密设计,结合了密文窃取技术(CipherText Stealing),即使最后一个块不足一个块大小,也能加密整个数据块; 优点:解决了数据块长度不足的问题,广泛用于硬盘加密(如BitLocker)。 |
GCM模式: |
特点:结合了CTR模式的高性能加密与Galois消息认证代码(GMAC)的认证功能; 优点:提供了同时进行加密和认证的功能,常用于需要高安全性的场景如TLS/SSL。 |
4、PKCS#7填充
PKCS#7是一种填充方案,它被广泛应用于加密算法中,特别是对称加密算法如AES;这种填充方案的目的是确保加密前的数据块长度是固定的,以满足加密算法对数据块长度的要求。 注意:当使用XTS模式时,通常不会使用填充方案如PKCS#7,因为XTS模式支持加密不规则长度的数据块,并自动处理长度不足的情况。 |
// PKCS#7填充函数 void pkcs7_pad(const unsigned char *input, unsigned char *output, size_t input_len, size_t output_len) { memcpy(output, input, input_len); size_t pad_value = output_len - input_len; for (size_t i = input_len; i < output_len; i++) { output[i] = (unsigned char)pad_value; } } // PKCS#7移除填充 void pkcs7_unpad(unsigned char *buffer, size_t *buffer_len) { size_t pad_value = buffer[*buffer_len - 1]; *buffer_len -= pad_value; buffer[*buffer_len] = '\0'; // 确保解密后的字符串以NULL结尾 } |
5、AES
AES(Advanced Encryption Standard)是一种对称密钥加密算法,也是目前应用最广泛的加密算法之一; 可以用于保护数据的机密性和完整性,通常用于文件、文件夹和整个磁盘的加密,还可以用于网络通信中数据的加密和解密,AES算法的出现是为了替代DES算法,以提高安全性和效率; AES算法密钥长度可以是128位、192位、256位,因此称为“AES-128”、“AES-192”或“AES-256”; 在加密过程中,AES算法将明文分成一组大小相等的块,每个块的大小为128位,无论使用 AES-128、AES-192 还是 AES-256,加密和解密过程中数据块的大小始终是 128 位(16 字节),AES 的变体(128、192、256)指的是密钥的长度,而不是数据块的大小,然后,通过多轮迭代,每轮迭代中都会对块进行一系列的代换和置换操作,最终生成密文; AES算法的加密过程可以分为以下几个步骤: 密钥扩展:根据AES算法的密钥长度,将密钥进行扩展,生成每轮迭代所需的轮密钥; 初始化:将明文块与第一轮轮密钥进行异或操作; 轮迭代:对每个块进行多轮迭代,每轮迭代包括四个步骤:字节代换、行移位、列混淆和轮密钥加; 最终迭代:在最后一轮迭代中,不进行列混淆操作; 输出:将最后一个块的结果输出,即密文; 解密过程与加密过程类似,但是需要使用相同的密钥和相反的操作来还原明文,由于AES算法使用的是对称密钥加密,因此在使用该算法加密和解密之前,发送方和接收方需要提前协商并共享相同的密钥。 |
- ECB模式
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/aes.h>
#define AES_KEY_SIZE 128 // AES密钥长度
#define AES_BLOCK_SIZE 16 // AES分块大小
// PKCS#7填充函数
void pkcs7_pad(const unsigned char *input, unsigned char *output, size_t input_len, size_t output_len) {
memcpy(output, input, input_len);
size_t pad_value = output_len - input_len;
for (size_t i = input_len; i < output_len; i++) {
output[i] = (unsigned char)pad_value;
}
}
// 移除PKCS#7填充
void pkcs7_unpad(unsigned char *buffer, size_t *buffer_len) {
size_t pad_value = buffer[*buffer_len - 1];
*buffer_len -= pad_value;
buffer[*buffer_len] = '\0'; // 确保解密后的字符串以NULL结尾
}
// 加密函数
void aes_encrypt(const unsigned char *plaintext, unsigned char *ciphertext, const unsigned char *key, size_t len) {
AES_KEY aes_key;
AES_set_encrypt_key(key, AES_KEY_SIZE, &aes_key);
for (size_t i = 0; i < len; i += AES_BLOCK_SIZE) {
AES_encrypt(plaintext + i, ciphertext + i, &aes_key);
}
}
// 解密函数
void aes_decrypt(const unsigned char *ciphertext, unsigned char *plaintext, const unsigned char *key, size_t len) {
AES_KEY aes_key;
AES_set_decrypt_key(key, AES_KEY_SIZE, &aes_key);
for (size_t i = 0; i < len; i += AES_BLOCK_SIZE) {
AES_decrypt(ciphertext + i, plaintext + i, &aes_key);
}
}
int main() {
// 明文密码
const char *plaintext_password = "my_password";
size_t plaintext_password_len = strlen(plaintext_password);
// AES密钥 - AES128
const unsigned char aes_key[] = { 0x7b, 0xf3, 0x5c, 0xd6, 0x9c, 0x47, 0x5d, 0x5e, 0x6f, 0x1d, 0x7a, 0x23, 0x18, 0x7b, 0xf9, 0x34 };
// 计算填充后的长度
size_t padded_len = ((plaintext_password_len / AES_BLOCK_SIZE) + 1) * AES_BLOCK_SIZE;
unsigned char *padded_plaintext = (unsigned char *)malloc(padded_len);
unsigned char *ciphertext_password = (unsigned char *)malloc(padded_len);
// 填充明文并进行加密
pkcs7_pad((const unsigned char *)plaintext_password, padded_plaintext, plaintext_password_len, padded_len);
aes_encrypt(padded_plaintext, ciphertext_password, aes_key, padded_len);
// 输出加密后的密码
printf("crypto: \n");
for (size_t i = 0; i < padded_len; i++) {
printf("%02x", ciphertext_password[i]);
}
printf("\n");
// 分配解密所需的内存空间
unsigned char *decrypted_password = (unsigned char *)malloc(padded_len);
// 对密文进行解密
aes_decrypt(ciphertext_password, decrypted_password, aes_key, padded_len);
// 移除填充并输出解密后的密码
size_t decrypted_len = padded_len;
pkcs7_unpad(decrypted_password, &decrypted_len);
printf("decrypto: %s\n", decrypted_password);
// 释放内存空间
free(padded_plaintext);
free(ciphertext_password);
free(decrypted_password);
return 0;
}
- CBC模式
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/aes.h>
#define AES_KEY_SIZE 128 // AES密钥长度
#define AES_BLOCK_SIZE 16 // AES分块大小
// PKCS#7填充函数
void pkcs7_pad(const unsigned char *input, unsigned char *output, size_t input_len, size_t output_len) {
memcpy(output, input, input_len);
size_t pad_value = output_len - input_len;
for (size_t i = input_len; i < output_len; i++) {
output[i] = (unsigned char)pad_value;
}
}
// 移除PKCS#7填充
void pkcs7_unpad(unsigned char *buffer, size_t *buffer_len) {
size_t pad_value = buffer[*buffer_len - 1];
*buffer_len -= pad_value;
buffer[*buffer_len] = '\0'; // 确保解密后的字符串以NULL结尾
}
// CBC加密函数
void aes_cbc_encrypt(const unsigned char *plaintext, unsigned char *ciphertext, const unsigned char *key, const unsigned char *iv, size_t len) {
AES_KEY aes_key;
AES_set_encrypt_key(key, AES_KEY_SIZE, &aes_key);
unsigned char block[AES_BLOCK_SIZE];
unsigned char current_iv[AES_BLOCK_SIZE];
memcpy(current_iv, iv, AES_BLOCK_SIZE); // 初始化IV
for (size_t i = 0; i < len; i += AES_BLOCK_SIZE) {
// 当前块与前一块的密文进行异或(IV用于第一个块)
for (size_t j = 0; j < AES_BLOCK_SIZE; j++) {
block[j] = plaintext[i + j] ^ current_iv[j];
}
// 加密异或后的结果
AES_encrypt(block, ciphertext + i, &aes_key);
// 更新IV为当前的密文块
memcpy(current_iv, ciphertext + i, AES_BLOCK_SIZE);
}
}
// CBC解密函数
void aes_cbc_decrypt(const unsigned char *ciphertext, unsigned char *plaintext, const unsigned char *key, const unsigned char *iv, size_t len) {
AES_KEY aes_key;
AES_set_decrypt_key(key, AES_KEY_SIZE, &aes_key);
unsigned char block[AES_BLOCK_SIZE];
unsigned char current_iv[AES_BLOCK_SIZE];
memcpy(current_iv, iv, AES_BLOCK_SIZE); // 初始化IV
for (size_t i = 0; i < len; i += AES_BLOCK_SIZE) {
// 解密当前密文块
AES_decrypt(ciphertext + i, block, &aes_key);
// 当前解密结果与前一个密文块(或IV)异或,得到明文
for (size_t j = 0; j < AES_BLOCK_SIZE; j++) {
plaintext[i + j] = block[j] ^ current_iv[j];
}
// 更新IV为当前的密文块
memcpy(current_iv, ciphertext + i, AES_BLOCK_SIZE);
}
}
int main() {
// 明文密码
const char *plaintext_password = "my_password";
size_t plaintext_password_len = strlen(plaintext_password);
// AES密钥 - AES128
const unsigned char aes_key[] = { 0x7b, 0xf3, 0x5c, 0xd6, 0x9c, 0x47, 0x5d, 0x5e, 0x6f, 0x1d, 0x7a, 0x23, 0x18, 0x7b, 0xf9, 0x34 };
// 初始化向量IV(通常应使用随机数生成)
const unsigned char iv[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 };
// 计算填充后的长度
size_t padded_len = ((plaintext_password_len / AES_BLOCK_SIZE) + 1) * AES_BLOCK_SIZE;
unsigned char *padded_plaintext = (unsigned char *)malloc(padded_len);
unsigned char *ciphertext_password = (unsigned char *)malloc(padded_len);
// 填充明文并进行加密
pkcs7_pad((const unsigned char *)plaintext_password, padded_plaintext, plaintext_password_len, padded_len);
aes_cbc_encrypt(padded_plaintext, ciphertext_password, aes_key, iv, padded_len);
// 输出加密后的密码
printf("crypto: %\n");
for (size_t i = 0; i < padded_len; i++) {
printf("%02x", ciphertext_password[i]);
}
printf("\n");
// 分配解密所需的内存空间
unsigned char *decrypted_password = (unsigned char *)malloc(padded_len);
// 对密文进行解密
aes_cbc_decrypt(ciphertext_password, decrypted_password, aes_key, iv, padded_len);
// 移除填充并输出解密后的密码
size_t decrypted_len = padded_len;
pkcs7_unpad(decrypted_password, &decrypted_len);
printf("decrypto: %s\n", decrypted_password);
// 释放内存空间
free(padded_plaintext);
free(ciphertext_password);
free(decrypted_password);
return 0;
}