OpenSSL Crypto Library
Table of Contents
1. OpenSSL 简介
OpenSSL 是开源的安全相关工具套件,包含了密码学中常用算法的实现。
OpenSSL 的版本历史如表 1 所示。
| Version | Original release date | Last minor version |
|---|---|---|
| 0.9.1 | December 23, 1998 | 0.9.1c (December 23, 1998) |
| 0.9.2 | March 22, 1999 | 0.9.2b (April 6, 1999) |
| 0.9.3 | May 25, 1999 | 0.9.3a (May 27, 1999) |
| 0.9.4 | August 9, 1999 | 0.9.4 (August 9, 1999) |
| 0.9.5 | February 28, 2000 | 0.9.5a (April 1, 2000) |
| 0.9.6 | September 24, 2000 | 0.9.6m (March 17, 2004) |
| 0.9.7 | December 31, 2002 | 0.9.7m (February 23, 2007) |
| 0.9.8 | July 5, 2005 | 0.9.8zh (December 3, 2015) |
| 1.0.0 | March 29, 2010 | 1.0.0t (December 3, 2015) |
| 1.0.1 | March 14, 2012 | 1.0.1u (September 22, 2016) |
| 1.0.2 | January 22, 2015 | 1.0.2u (December 20, 2019) |
| 1.1.0 | August 25, 2016 | 1.1.0l (September 10, 2019) |
| 1.1.1 | September 11, 2018 | 1.1.1w (11 September 2023) |
| 3.0.0 | 7 September 2021 | N/A |
| 3.1.0 | 14 March 2023 | N/A |
执行命令 openssl version 可检查系统中 OpenSSL 的版本:
$ openssl version OpenSSL 1.1.1 11 Sep 2018 $ openssl version -a # 指定 -a,可得到更详细信息 OpenSSL 1.1.1 11 Sep 2018 built on: Wed May 27 19:15:54 2020 UTC platform: debian-amd64 options: bn(64,64) rc4(16x,int) des(int) blowfish(ptr) compiler: gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -Wa,--noexecstack -g -O2 -fdebug-prefix-map=/build/openssl-dyPhHZ/openssl-1.1.1=. -fstack-protector-strong -Wformat -Werror=format-security -DOPENSSL_USE_NODELETE -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DRC4_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DGHASH_ASM -DECP_NISTZ256_ASM -DX25519_ASM -DPADLOCK_ASM -DPOLY1305_ASM -DNDEBUG -Wdate-time -D_FORTIFY_SOURCE=2 OPENSSLDIR: "/usr/lib/ssl" ENGINESDIR: "/usr/lib/x86_64-linux-gnu/engines-1.1" Seeding source: os-specific
1.1. OpenSSL 开发
如果要进行 OpenSSL 相关开发,则需要安装对应的软件包:
$ sudo yum install openssl-devel # redhat $ sudo apt install libssl-dev # debian
OpenSSL 提供了两个库:libssl 和 libcrypto。其中,libssl 实现了 TLS 协议,libcrypto 实现了常用的密码学算法。
2. EVP(高层接口)
OpenSSL EVP(high-level interface to cryptographic functions)提供了丰富的密码学中的各种函数。可以实现了各种对称算法加解密、摘要算法以及非对称加解密、签名与验证等等。
EVP 是单词 Envelope 的缩写,参见:https://stackoverflow.com/questions/3055454/what-does-openssls-evp-mean
2.1. 哈希
下面是使用 EVP 进行哈希(MD5 和 SHA256)计算的例子:
#include <stdio.h>
#include <openssl/evp.h>
int digest_message(const unsigned char *message, size_t message_len, const char *digest_name,
unsigned char *digest, unsigned int *digest_len)
{
EVP_MD_CTX *mdctx;
const EVP_MD *md;
if ((mdctx = EVP_MD_CTX_new()) == NULL) {
return -1;
}
if ((md = EVP_get_digestbyname(digest_name)) == NULL) {
return -1;
}
EVP_DigestInit_ex(mdctx, md, NULL);
EVP_DigestUpdate(mdctx, message, message_len);
EVP_DigestFinal_ex(mdctx, digest, digest_len);
EVP_MD_CTX_free(mdctx);
return 0;
}
void dumphex(const unsigned char *src, size_t len)
{
size_t i;
for (i = 0; i < len; i++) {
fprintf(stderr, "%02x", src[i]);
}
printf("\n");
}
int main() {
unsigned char input[3] = {'a', 'b', 'c'};
unsigned char hash_outbuf[EVP_MAX_MD_SIZE];
unsigned int hash_len;
digest_message(input, 3, "md5", hash_outbuf, &hash_len);
fprintf(stderr, "md5(abc):\t");
dumphex(hash_outbuf, hash_len); // 900150983cd24fb0d6963f7d28e17f72
fprintf(stderr, "sha256(abc):\t");
digest_message(input, 3, "sha256", hash_outbuf, &hash_len);
dumphex(hash_outbuf, hash_len); // ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
return 0;
}
编译并运行上面程序:
$ cc -I/usr/local/opt/openssl/include -L/usr/local/opt/openssl/lib -lcrypto hash_example.c -o hash_example $ ./hash_example md5(abc): 900150983cd24fb0d6963f7d28e17f72 sha256(abc): ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
3. HMAC
OpenSSL 中支持 HMAC,下面是 HMAC SHA256 的一个实例:
#include <stdio.h>
#include <openssl/hmac.h>
void dumphex(const unsigned char *src, size_t len)
{
size_t i;
for (i = 0; i < len; i++) {
fprintf(stderr, "%02x", src[i]);
}
printf("\n");
}
int main() {
unsigned char input[3] = {'a', 'b', 'c'};
unsigned char key[4] = {'k', 'e', 'y', '1'};
unsigned char hash_outbuf[EVP_MAX_MD_SIZE];
unsigned int hash_len;
const EVP_MD *evp_md = EVP_sha256();
HMAC(evp_md, key, 4, input, 3, hash_outbuf, &hash_len);
fprintf(stderr, "msg=abc, key=key1, sha256: ");
dumphex(hash_outbuf, hash_len); // 8ca63d43eee01f745de3fc6c2b2cdf1824b6ef81b1179bc3ca650d4288d3f445
return 0;
}
编译并运行上面程序:
$ cc -I/usr/local/opt/openssl/include -L/usr/local/opt/openssl/lib -lcrypto hmac_example.c -o hmac_example $ ./hmac_example msg=abc, key=key1, sha256: 8ca63d43eee01f745de3fc6c2b2cdf1824b6ef81b1179bc3ca650d4288d3f445
库中的函数 HMAC 是对 HMAC_Init_ex/HMAC_Update/HMAC_Final 等函数的封装,HMAC 的源码如下:
// From https://github.com/openssl/openssl/blob/master/crypto/hmac/hmac.c
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)
{
HMAC_CTX *c = NULL;
static unsigned char m[EVP_MAX_MD_SIZE];
static const unsigned char dummy_key[1] = {'\0'};
if (md == NULL)
md = m;
if ((c = HMAC_CTX_new()) == NULL)
goto err;
/* For HMAC_Init_ex, NULL key signals reuse. */
if (key == NULL && key_len == 0) {
key = dummy_key;
}
if (!HMAC_Init_ex(c, key, key_len, evp_md, NULL))
goto err;
if (!HMAC_Update(c, d, n))
goto err;
if (!HMAC_Final(c, md, md_len))
goto err;
HMAC_CTX_free(c);
return md;
err:
HMAC_CTX_free(c);
return NULL;
}
4. EC(椭圆曲线)
OpenSSL 支持椭圆曲线,下面介绍相关的数据结构:
- EC_GROUP 用于表达某一种椭圆曲线的定义;
- EC_POINT 用于表达椭圆曲线上的一个点;
- EC_KEY 用于表达私钥/公钥对(私钥是一个大整数,即 BIGNUM;而公钥是椭圆曲线上的点,即 EC_POINT)。
下面例子演示了随机生成一个密钥对,并输出其私钥/公钥:
#include <stdio.h>
#include <openssl/ec.h>
#include <openssl/objects.h>
int CURVE_NAME = NID_secp256k1;
void dumpBN(const BIGNUM *bn) {
char *buf;
if ((buf = BN_bn2hex(bn)) == NULL) {
return;
}
fprintf(stderr, "%s\n", buf);
OPENSSL_free(buf); // must free memory allocated by BN_bn2hex
}
void dumpPoint(const EC_POINT *point) {
EC_GROUP *group;
char *hex;
if ((group=EC_GROUP_new_by_curve_name(CURVE_NAME)) == NULL) {
return;
}
hex = EC_POINT_point2hex(group, point, POINT_CONVERSION_UNCOMPRESSED, NULL);
fprintf(stderr, "%s\n", hex);
OPENSSL_free(hex); // must free memory allocated by EC_POINT_point2hex
}
int main() {
EC_KEY *key;
const BIGNUM *privateKey; // 私钥是一个大整数
const EC_POINT *publicKey; // 公钥是椭圆曲线上的一个点
// 创建 key 对象
if (( key = EC_KEY_new_by_curve_name(CURVE_NAME)) == NULL) {
return 1;
}
// 随机生成一个 key
EC_KEY_generate_key(key);
// 从 key 中得到私钥
privateKey = EC_KEY_get0_private_key(key);
dumpBN(privateKey);
// 从 key 中得到公钥
publicKey = EC_KEY_get0_public_key(key);
dumpPoint(publicKey);
return 0;
}
编译并运行上面程序:
$ cc -I/usr/local/opt/openssl/include -L/usr/local/opt/openssl/lib -lcrypto ec_example.c -o ec_example $ ./ec_example # 由于 key 是随机生成,所以每次运行输出都不一样 92E4E014D80DD548161AA01B5C1956DD5B4E2B32DC5566DA79E631AB4A78D741 04F12B5B62EDFA5D2E5376B8B085F58684DE524096A1530EF5419B6DC61962399D93C5B4B7BD959016C01DDEF97C019E0C8E92A05D24E391811AD8B199EDCA5EF6