今天看啥  ›  专栏  ›  BB8

Android Native C/C++ 使用OpenSSL EVP接口

BB8  · 掘金  ·  · 2019-07-31 02:15
阅读 25

Android Native C/C++ 使用OpenSSL EVP接口

version date commit-msg
1.0 2019/7/30 first commit

在日常的开发中,涉及到加解密库的开发总离不开对OpenSSL接口的调用,笔者借对公司内部加解密库进行国密算法扩充的契机,对 OpenSSL进行了一番学习与实践😀。

因此本文将介绍EVP接口的使用,并给出SM2公钥加密的具体实现。

概述

OpenSSL提供了一系列的函数用于特定加解密算法,比如,可以使用#include "rsa.h"中的RSA_public_encrypt()完成RSA公钥加密计算。 但是,OpenSSL还提供了一种统一接口,开发者可通过调用统一接口,使得只需要在初始化参数的时候做很少的改变,就可以使用相同的代码但采用不同的加密算法进行数据的加密和解密[1],这种统一接口就是本文要介绍的EVP接口。

EVP接口

EVP接口封装了摘要算法,密钥生成,对称加解密,非对称加解密,验证等功能,其中比较常见的函数有:

使用EVP接口完成对称加解密

EVP接口提供了对称加解密函数,其中有专门进行加密的函数,在进行加密之前,首先初始化加密上下文,然后调用加密函数进行加密计算。

EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
复制代码
  1. 初始化对称加密
#include "evp.h"
/*
 * @param ctx: 这代表加解密的上下文
 * @param type: type代表需要使用的算法类型。
     比如填写EVP_sm4_cbc()则使用了CBC分组模式的SM4算法。
     在evp中预先定义了多种type可供开发者选用
 * @param impl:ENGINE代表了加解密的引擎。
     OpenSSL是支持自定义加解密引擎的,即自定义加解密算法的具体实现而
     不使用默认的OpenSSL实现。
     一般情况下,我们都使用OpenSSL中的加解密算法,因此可置为NULL
 * @param key: 密钥
 * @param iv: 初始向量,如果选择了ECB,则不需要iv
 */
int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
       ENGINE *impl, const unsigned char *key, const unsigned char *iv);
EVP_EncryptUpdate()
EVP_EncryptFinal_ex()
复制代码
  1. 对明文进行分块加密
#include "evp.h"
/*
 * @param out: 密文
 * @param outl: 密文长度
 * @param in: 明文
 * @param inl: 明文长度
 */
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,
       int *outl, const unsigned char *in, int inl);
复制代码
  1. 对明文块的最后一块进行处理,因为有填充的存在,因此需要对最后一块进行特殊处理。
#include "evp.h"
int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, 
       int *outl);
复制代码

EVP提供了对应的解密函数,参数设置与加密函数类似,用法也很类似,接口如下:

#include "evp.h"
int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
       ENGINE *impl, const unsigned char *key, const unsigned char *iv);
int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, 
       const unsigned char *in, int inl);
int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, 
       int *outl);
复制代码

EVP更是提供了加密和解密的统一接口,该接口唯一的区别就是在初始化的时候,通过enc参数判定是加密模式还是解密模式。

#include "evp.h"
/*
 * @param enc: enc==1代表加密,enc==0代表解密 
 */
int EVP_CipherInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
       ENGINE *impl, const unsigned char *key, const unsigned char *iv, int enc);
int EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,
       int *outl, const unsigned char *in, int inl);
int EVP_CipherFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, 
       int *outl);

复制代码

使用EVP进行非对称密钥生成与公钥加密

进行公钥加密的重点在于设置密钥类型。在OpenSSL中,存在多种类型的密钥和一个统一的EVP_PKEY密钥,需要通过辅助函数将特定密钥转化成统一密钥。

特定密钥有:

  • ec.h头文件中的EC_KEY代表了椭圆曲线的密钥
  • rsa.h头文件中的RSA代表RSA密钥
  • 还有DH密钥和DSA密钥等

这些特定的密钥可以使用辅助函数进行设置:

  • int EVP_PKEY_set1_EC_KEY(EVP_PKEY *pkey, EC_KEY *key);EC_KEY设置到EVP_PKEY
  • int EVP_PKEY_set1_RSA(EVP_PKEY *pkey, RSA *key);RSA设置到EVP_PKEY

使用SM2公钥加密

OpenSSL1.0.*版本中,外部是可以引用#include "sm2.h"的。🤣那是一个美好的田园时光,网上大量的对sm2算法的调用都通过 sm2.h头文件中定义的sm2_*函数进行。当时笔者需要对公共加密库进行sm2扩充的时候,苦苦搜寻sm2.h而不得,最后才发现在OpenSSL1.1*版本已经不再对外暴露sm2.h,因此在OpenSSL1.1*中只能完全通过 evp.h调用了。

首先是密钥的生成,在生成密钥阶段,就需要告诉OpenSSL生成SM2密钥。

EC_KEY* key = EC_KEY_new();
/*
 * OpenSSL内置了许多曲线,因此需要设置使用哪条曲线。NID_sm2
 */
EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_sm2);
EC_KEY_set_group(key, group);
// 根据曲线,生成密钥
EC_KEY_generate_key(key);
// 可以使用该函数对生成的密钥进行检查
EC_KEY_check_key((const EC_KEY*)key)
复制代码

为完成非对称公钥加密,需要依次调用以下这些函数:

// 初始化公钥上下文
1. EVP_PKEY *p_key = EVP_PKEY_new();
// 将EC_KEY结构中存储的密钥保存到EVP_PKEY中,并设置EVP_PKEY的类型为椭圆曲线密钥
2. EVP_PKEY_set1_EC_KEY(p_key, key);
// 设置密钥类型为SM2密钥,而非其他的什么密钥
3. EVP_PKEY_set_alias_type(p_key, EVP_PKEY_SM2);
// 初始化加密上下文,需要传入密钥。 第二个参数为ENGINE,同上,可置为NULL
4. EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(p_key, NULL);
// 初始化加密上下文
5. EVP_PKEY_encrypt_init(ctx);
// 使用公钥进行加密
6. EVP_PKEY_encrypt(ctx, *ciphertext, ciphertext_len, plaintext, plain_len);
复制代码

以上步骤比较重要的是第3步。OpenSSL内置了一些椭圆曲线,因此需要开发者显示的指定使用哪条曲线,因此在密钥中指定使用SM2,最终会在初始化加密上下文的时候,告诉OpenSSL具体的算法,从而在加密阶段使用SM2算法。

使用EVP接口完成摘要函数

摘要函数的调用需要使用到4个函数即可:

// 初始化计算上下文
1. EVP_MD_CTX* ctx = EVP_MD_CTX_new();
// 初始化摘要计算,其中type代表需要使用的摘要算法。比如EVP_sm3()使用sm3摘要
2. int EVP_DigestInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl);
3. int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *d, size_t cnt);
4. int EVP_DigestFinal_ex(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *s);
复制代码

References




原文地址:访问原文地址
快照地址: 访问文章快照