openssl基础(二)密码库的使用

  上一部分介绍了openssl的部分命令行用法,但很多时候我么还需要在程序中使用openssl,这里主要介绍了使用openssl的密码库进行对称密钥加密的相关知识。

约定

在没有特殊说明的情况下,本文提到的长度指的是字节数目

1. 数据输出

头文件

#include <openssl/bio.h>

函数

int BIO_dump_fp(FILE *fp, const char *s, int len);

该函数以16进制+字符形式(如下图所示)打印数据到fp中, s为数据所在地址,len为长度。将fp指定为stdio,就可以将数据打印到屏幕上。
BIO_dump_fp输出结果

stdio是定义在头文件stdio.h中的一个FILE*变量,表示标准输出

2. 错误处理

头文件

#include <openssl/err.h>

函数

void ERR_print_errors_fp(FILE *fp);

该函数将openssl的上一条错误信息打印到fp中,将fp指定为stderr,就可以将数据打印到屏幕上。

stderr是定义在头文件stdio.h中的一个FILE*变量,表示标准错误输出

3. 对称加密

虽然c语言没有对象,为了方便描述, 我这里把struct结构体称为对象

对称加密和解密的流程类似,一般有以下几个步骤:

  1. 生成一个记录加密(解密)上下文信息的EVP_CIPHER_CTX对象
  2. 初始化加密(解密)算法,在这一步指定算法和密钥
  3. 加密(解密)数据
  4. 处理尾部数据,结束加密(解密)
  5. 清空并释放加密(解密)上下文对象,清空其他敏感信息
    其中使用的函数以及其他一些相关函数如下:

头文件

#include <openssl/evp.h>

上下文处理

EVP_CIPHER_CTX *EVP_CIPHER_CTX_new(void);

创建新加密上下文EVP_CIPHER_CTX对象, 并将其作为返回值返回

void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *ctx);

清除并释放加密上下文对象(防止数据泄露),参数为需要释放的EVP_CIPHER_CTX对象,在所有加密操作结束后调用该函数

int EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX *ctx);

目前不是很清楚具体作用,可能是重置一个EVP_CIPHER_CTX对象从而可以循环利用避免不必要的内存释放和分配吧

加解密初始化

加密

int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
     ENGINE *impl, const unsigned char *key, const unsigned char *iv);

该函数对加密操作进行初始化,参数描述如下:

参数描述
ctx加密上下文对象
type加密算法类型,在openssl/evp.h中定义了许多以算法命名的函数
这些函数的返回值作为此参数使用,比如EVP_aes_256_cbc()
impl利用硬件加密的接口,本文不讨论,设置为NULL
key用于加密的密钥
iv某些加密模式如cbc需要使用的初始化向量,如果加密模式不需要可以设置为NULL

返回值为1表示成功0表示失败,可以使用上述错误处理中的函数打印错误信息

解密

int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
     ENGINE *impl, const unsigned char *key, const unsigned char *iv);

该函数对解密操作进行初始化,参数与返回值上述加密初始化函数描述相同

执行加解密操作

注意, 输出缓冲区的长度需要比输入缓冲区大一个加密块,否则会出现错误。

注意,如果出现overlap错误,请检查输入和输出缓冲区是否分离,以及是否其长度是否满足第一个注意事项

加密

int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,
     int *outl, const unsigned char *in, int inl);

执行加密的函数,参数描述如下:

参数描述
ctx加密上下文对象
out保存输出结果(密文)的缓冲区
outl接收输出结果长度的指针
in包含输入数据(明文)的缓冲区
inl输入数据的长度

返回值为1表示成功,返回值为0表示失败

解密

int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,
     int *outl, const unsigned char *in, int inl);

执行解密的函数,参数和返回值和上述加密函数类似,只需要注意输入和输出不要混淆

加解密尾部数据处理

加密

int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out,
     int *outl);

该函数处理加密结果的尾部数据(比如填充段块),还可能输出一些密文数据,参数描述如下:

参数描述
ctx加密上下文对象
out保存输出结果(密文)的缓冲区
注意这个指针要指向之前已经保存的加密数据的尾部
outl接收输出结果长度的指针

返回值为1表示成功,0表示失败。

解密

int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm,
     int *outl);

该函数处理解密结果的尾部数据,还可能输出一些明文数据,参数和返回值同上述加密尾部数据处理的函数类似,注意这个函数输出的是明文即可

资源释放

在加解密操作完成后,对可能的密码缓冲区的清空,以及释放上下文对象,一般使用上下文处理中的

void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *ctx);

释放上下文对象即可

4. 口令生成密钥(key derivation)

有时候我们需要使用口令来生成加密密钥,openssl推荐使用PBKDF2算法来进行这个操作,使用到的函数如下。

关于PBKDF2的描述参考维基百科PBKDF或者RFC2898(PBKDF2)

头文件

#include <openssl/evp.h>

函数

int PKCS5_PBKDF2_HMAC(const char *pass, int passlen,
                   const unsigned char *salt, int saltlen, int iter,
                   const EVP_MD *digest,
                   int keylen, unsigned char *out);

该函数使用PKKDF2算法利用口令生成指定长度的密钥,其参数描述如下:

参数描述
pass用于生成密钥的口令
passlen口令的长度
salt用于生成密钥的盐值(建议4字节以上),当然也可以设置为NULL表示不使用
saltlen盐值的长度,如果不使用则为0
iter迭代次数(openssl建议设置到1000以上,用于增加暴力破解的难度)
digest单向hash函数,在openssl/evp.h中定义了许多以算法命名的函数
这些函数的返回值作为此参数使用,比如EVP_sha256()
keylen输出的密钥的长度
out保存输出的密钥的缓冲区

返回值为1表示成功,0表示失败。

示例

我写了一个加密和解密文件的小例子,有兴趣的朋友可以看一下, 包含主要加密解密流程的操作在代码中的encryptdecrypt函数中。

#include <stdlib.h>
#include <stdio.h>
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include <string.h>
//#define DEBUG

#define printBytesN(a,b) printBytes(a,b,NULL)
#define handleErrors() handleErrorsP("")

void printBytes(unsigned char* bytes, int len, const char* prompt){
    int i=0;

    if (prompt != NULL) {
        printf("%s:\n",prompt);
    }
    for(i=0; i<len; i++){
        printf("%02x ", *(bytes + i));
        if ((i + 1) % 10 == 0) {
            printf("\n");
        }
    }
    if( i % 10 != 0 ) {
        printf("\n");
    }
}

void handleErrorsP(const char* prompt)
{
    ERR_print_errors_fp(stderr);
    puts(prompt);
    exit(1);
}

// 加密函数,主要部分,包含openssl api
void encrypt(const char* input_file, const char* out_put_file)
{
    EVP_CIPHER_CTX *ctx;

    int len;
    char password[10];
    char salt[PKCS5_SALT_LEN];
    char key_iv[48];
    // output buffer should be a block size longer thant input buffer
    char plain_buffer[4096];
    char cipher_buffer[4112];
    FILE* plain_file = NULL;
    FILE* cipher_file = NULL;
    int imm_len;

    /* Create and initialise the context */
    if(!(ctx = EVP_CIPHER_CTX_new()))
        handleErrors();
    

    // read password
    for(;;){
        printf("please input your password\n");
        scanf("%10s", password);
        if(strlen(password) > 5){
            break;
        }
        printf("password too short, at least six letters.\n");
    }

    // generate salt
    // function in openssl/rand.h, generate random bytes
    RAND_bytes(salt, sizeof(salt));
    #ifdef DEBUG
    printBytes(salt, sizeof(salt), "The salt is");
    #endif
    
    // generate key and iv (generate 48 bytes and the first 32 bytes are for key and the last 16 bytes are for iv)
    if (0 == PKCS5_PBKDF2_HMAC(password, strlen(password), salt, sizeof(salt),
                                       1000, EVP_sha256(), sizeof(key_iv), key_iv)) {
            handleErrors();
    }

    // initialise encryption
    if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key_iv, key_iv + 32))
        handleErrors();

    plain_file = fopen(input_file, "rb");
    cipher_file = fopen(out_put_file, "wb");
    if (plain_file == NULL) {
        printf("file open error\n");
        exit(1);
    }
    if(8 != (len = fwrite(salt, 1, 8, cipher_file))) {
        printf("error write file, size %d\n", len);
        exit(1);
    }

    while ((imm_len = fread(plain_buffer, 1, 4096, plain_file)) != 0) {
        if(1 != EVP_EncryptUpdate(ctx, cipher_buffer, &len, plain_buffer, imm_len)) {
            handleErrors();
        }
#ifdef DEBUG
        printf("imm_len %d\n", imm_len);
        printf("cipher_len:%d\n", len);
#endif
        if(len != fwrite(cipher_buffer, 1, len, cipher_file)) {
            printf("file write error\n");
            exit(0);
        }
    }

    /*
     * Finalise the encryption. Further ciphertext bytes may be written at
     * this stage.
     */
    if(1 != EVP_EncryptFinal_ex(ctx, cipher_buffer, &len))
        handleErrors();
    if(len != fwrite(cipher_buffer, 1, len, cipher_file)) {
            printf("file write error\n");
            exit(0);
    }
#ifdef DEBUG
    printf("cipher final:%d\n", len);
#endif

    /* Clean up */
    EVP_CIPHER_CTX_free(ctx);
    OPENSSL_cleanse(plain_buffer, 4096);
    OPENSSL_cleanse(password, 10);
    fclose(cipher_file);
    fclose(plain_file);
}

// 解密函数,主要部分,包含openssl api
int decrypt(const char* input_file, const char* output_file)
{
    EVP_CIPHER_CTX *ctx;
    int len;
    char password[10];
    char salt[PKCS5_SALT_LEN];
    char key_iv[48];
    // output buffer should be a block size longer thant input buffer
    char plain_buffer[4112];
    char cipher_buffer[4096];
    FILE* plain_file = NULL;
    FILE* cipher_file = NULL;
    int imm_len;

    /* Create and initialise the context */
    if(!(ctx = EVP_CIPHER_CTX_new()))
        handleErrors();

    // read password
    for(;;){
        printf("please input your password\n");
        scanf("%10s", password);
        if(strlen(password) > 5){
            break;
        }
        printf("password too short, at least six letters");
    }


    plain_file = fopen(output_file, "wb");
    cipher_file = fopen(input_file, "rb");
    if (plain_file == NULL || cipher_file == NULL) {
        printf("file open error\n");
        exit(1);
    }
    if(8 != (len = fread(salt, 1, 8, cipher_file))) {
        printf("error write file, size %d\n", len);
        exit(1);
    }

    // generate key and iv
    if (0 == PKCS5_PBKDF2_HMAC(password, strlen(password), salt, sizeof(salt),
                                       1000, EVP_sha256(), sizeof(key_iv), key_iv)) {
            handleErrors();
    }

    #ifdef DEBUG
    printBytesN(salt, sizeof(salt));
    #endif

    // initialise decryption
    if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key_iv, key_iv + 32)){
        handleErrors();
    }

    // decrypt
    while(0 != (imm_len = fread(cipher_buffer, 1, 4096, cipher_file))) {
        if(1 != EVP_DecryptUpdate(ctx, plain_buffer, &len, cipher_buffer, imm_len)) {
            handleErrorsP("error decrypt, maybe password not corret, or the file is corrupted");
        }
#ifdef DEBUG
        printf("imm_len %d\n", imm_len);
        printf("plain_len %d\n", len);
#endif
        fwrite(plain_buffer, 1, len, plain_file);
    }

    // Finalise decryption
    if(1 != EVP_DecryptFinal_ex(ctx, plain_buffer, &len)) {
        handleErrorsP("error decrypt, maybe password not corret, or the file is corrupted");
    }
#ifdef DEBUG
    printf("plain_final %d\n", len);
#endif
    if(len != fwrite(plain_buffer, 1, len, plain_file)) {
            printf("file write error\n");
            exit(0);
    }

    /* Clean up */
    EVP_CIPHER_CTX_free(ctx);
    OPENSSL_cleanse(plain_buffer, 4096);
    OPENSSL_cleanse(password, 10);
    fclose(plain_file);
    fclose(cipher_file);
}

int main() {
    printf("encrypt plain.txt to cipher.txt:\n")
    encrypt("plain.txt", "cipher.txt");
    printf("encrypt plain.txt to new_plain.txt:\n")
    decrypt("cipher.txt", "new_plain.txt");
    return 0;
}

参考资料

openssl manpage

openssl wiki

openssl1.1.1d源代码

OpenSSL 是一个开源的安全套接字层协议,它提供了一系列的加密算法、SSL/TLS 协议实现以及常用的密钥和证书管理工具。下面是 OpenSSL基础使用方法: 1. 生成密钥对 使用 OpenSSL 生成 RSA 密钥对,可以使用以下命令: ``` openssl genrsa -out private_key.pem 2048 ``` 其中,-out 参数指定生成的私钥保存的文件名,2048 表示密钥长度为 2048 位。 2. 生成证书请求 使用 OpenSSL 生成证书请求,可以使用以下命令: ``` openssl req -new -key private_key.pem -out csr.pem ``` 其中,-new 参数表示新建一个证书请求,-key 参数指定私钥文件,-out 参数指定生成的证书请求文件。 3. 生成自签名证书 使用 OpenSSL 生成自签名证书,可以使用以下命令: ``` openssl x509 -req -days 365 -in csr.pem -signkey private_key.pem -out certificate.pem ``` 其中,-req 参数表示使用证书请求生成自签名证书,-days 参数指定证书有效期为 365 天,-in 参数指定证书请求文件,-signkey 参数指定使用的私钥文件,-out 参数指定生成的证书文件。 4. 加密解密 使用 OpenSSL 进行加密和解密,可以使用以下命令: ``` openssl enc -aes-256-cbc -salt -in plaintext.txt -out ciphertext.txt -pass file:./password.txt ``` 其中,-aes-256-cbc 参数表示使用 AES-256-CBC 加密算法,-salt 参数表示使用随机盐值增加安全性,-in 参数指定明文文件,-out 参数指定密文文件,-pass 参数指定密码来源,这里使用了一个文件作为密码。 ``` openssl enc -d -aes-256-cbc -in ciphertext.txt -out plaintext.txt -pass file:./password.txt ``` 其中,-d 参数表示解密,其他参数含义和上面相同。 以上就是 OpenSSL基础使用方法,更多用法可以参考 OpenSSL 的官方文档。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值