OpenSSL Crypto Library

Table of Contents

1. OpenSSL 简介

OpenSSL 是开源的安全相关工具套件,包含了密码学中常用算法的实现。

OpenSSL 的版本历史如表 1 所示。

Table 1: OpenSSL release history
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

Author: cig01

Created: <2019-03-01 Fri>

Last updated: <2020-07-20 Mon>

Creator: Emacs 27.1 (Org mode 9.4)