首页 > 其他分享 >openssl加密之椭圆曲线的学习

openssl加密之椭圆曲线的学习

时间:2024-11-29 16:45:35浏览次数:6  
标签:PKEY 椭圆 加密 X509 openssl PARAM int OSSL EVP

椭圆曲线的参数及属性

我们选择的椭圆曲线是:NID_X9_62_prime256v1
描述一条Z*q上的椭圆曲线,常用到六个参数:
T=(q,a,b,G,order,h)。
q 、a 、b 用来确定一条椭圆曲线,
G为基点,
order为点G的阶,
我们事先指定好了一个国际上通用的安全曲线:NID_X9_62_prime256v1曲线
ECC公钥加密一般使用的是混合加密方式,因为椭圆曲线加密ECC主要用来密钥交换和数字签名,并不用来直接加密数据。因为效率较低,且加密的数据较低,且加密数据的数据长度会非常长。
因此采用的是一种成为椭圆曲线集成加密方案(ECIES)的方法。他结合了对称加密和椭圆曲线加密的优点。
ECIES 的工作原理如下:
1:生成随机对称密钥:使用椭圆曲线 Diffie-Hellman(ECDH)算法生成一个随机的对称密钥。
2:加密数据:使用对称密钥(如 AES)加密数据。
3:加密对称密钥:使用接收者的公钥加密对称密钥。
5:传输密文和加密的对称密钥:发送给接收者。

自定义 BIGNUM *GeneratePriKey(); EC_POINT *GeneratePubKet( BIGNUM *PriKey ); 方法生成公私钥对。

BIGNUM类型是针对密码学专门设计的一个数据类型为大数类型,密码学中的操作多数基于该类型。

我自己通过OPENSSL的底层API来生成BIGNUM类型的私钥和EC_POINT类型的公钥,为了使其可以向上兼容高级的API,需要将其封装成EVP_PKEY类型。
BIGNUM和EC_POINT类型无法直接封装成EVP_PKEY类型,我们需要首先将BIGNUM和EC_POINT类型封装成EC_KEY类型,然后再封装成EVP_KEY类型。

ECCOperator类定义

在该类中,我们根据上述椭圆曲线的属性,定义个相关成员变量。
同时为了更好的椭圆曲线密码学进行理解和操作。我采用的是OPENSSL底层的低级API并结合椭圆曲线密钥学的概念来创建私钥和公钥。

GeneratePriKey()函数用来创建私钥。解析如下:

  1. 创建3个BIGNUM数据类型 k_priv,one,n_minus_one。
  2. 使用BN_set_word函数将one的值设置成1。因为ECC的私钥的选择范围是[1,n),因此我们需要将n_minus_one = n-1。其中n是椭圆曲线的阶。因此我们使用大数减法函数BN_sub(n_minus_one, n_, one),大数无法使用基本数据类型的减法操作。
  3. 使用BN_priv_rand_range( k_priv, n_minus_one ) ,因为这个函数的取值范围是[0,n-1),我们需要的是从[1,n),因此我们还需要使用 BN_add( k_priv, k_priv, one )将1,这是范围就是[1,n)。
  4. 检查产生的私钥是不是属于[1,n)这个范围的。

注:BIGNUM类型需要释放资源,使用BN_free函数释放

EC_POINT *GeneratePubKey( BIGNUM *PriKey )函数是根据私钥来计算公钥。解析如下:

  1. 使用EC_POINT_new() 创建EC_POINT类型的公钥并初始化,
  2. 使用椭圆曲线点乘算法来计算公钥EC_POINT_mul函数来计算公钥。
  3. 使用EC_POINT_is_on_curve函数来检查计算到的公钥是否属于该曲线上的点,是的话则返回该点。

int BN_ECKey_Conv_EC_EVPKey( BIGNUM *k_priv, EC_POINT *k_pub, EC_GROUP *group, EVP_PKEY **out_evp_pkey );这个函数的作用是将上述计算到的公私钥转换为EVP_PKEY类型。

  1. 创建EVP_PKEY_ctx上下文对象,使用函数EVP_PKEY_CTX_new_id( EVP_PKEY_EC, NULL ),这个函数的第一个参数 EVP_PKEY_EC 指定了使用椭圆曲线(Elliptic Curve)加密算法。这表明您创建的上下文是专门为椭圆曲线密钥操作配置的。
  2. 初始化EVP_PKEY_ctx上下文,以便从数据中导入或生成一个EVP_KEY。EVP_PKEY_fromdata_init( pctx ) 这个函数通常与EVP_PKEY_fromdata结合使用,这个函数实际上将数据转换成 EVP_PKEY 结构。
  3. 将传递进来的BIGNUM类型的k_priv 转换成二进制字节序,将公钥参数转换为二进制字节序。
  4. 使用OSSL_PARAM params[] 来设置私钥和公钥的参数。
  5. 使用 EVP_PKEY_fromdata( pctx, out_evp_pkey, EVP_PKEY_KEYPAIR, params ) 从参数构造出EVP_PKEY对象。

函数记录:

数据转换函数

  • BN_bn2hex 用于将大数类型转换为可读的十六进制类型。
  • BN_bn2dec(const BIGNUM *a) 用于将大数类型转换为可读的十进制类型。

椭圆曲线参数获取函数

EC_POINT_point2hex( group, point, form, ctx );
用于将EC_POINT对象转换为可读的十六进制字符串。
    1. group: 该参数指向一个 EC_GROUP 对象,代表椭圆曲线的定义。
    2. point: 需要转换的 EC_POINT 对象。
    3. form: 点的表示形式,通常是 POINT_CONVERSION_UNCOMPRESSED、POINT_CONVERSION_COMPRESSED 或 POINT_CONVERSION_HYBRID 中的一种
    4. ctx: 可选的 BN_CTX 用于内部计算,可以是 NULL。
EC_GROUP_new_by_curve_name( NID_X9_62_prime256v1 ) 通过椭圆曲线名来创建椭圆曲线参数群。
EC_GROUP_get_curve( curveAttributes_, q_, a_, b_, ctx_ ) 这个函数用于从椭圆曲线中获取椭圆曲线的参数。
EC_GROUP_get_order( curveAttributes_, n_, ctx_ ) 这个参数用于获取椭圆曲线的阶。
EC_GROUP_get_cofactor( curveAttributes_, h_, ctx_ ) 这个函数用来获取椭圆曲线的辅助因子。辅助因子*椭圆曲线的阶 = 椭圆曲线循环加法群的点的个数。
EC_POINT_new( curveAttributes_ ); 根据椭圆曲线来创建基点。(主要用于初始化)
EC_POINT_copy( P_, EC_GROUP_get0_generator( curveAttributes_ )) :
    EC_GROUP_get0_generator( curveAttributes_ ):用于从椭圆曲线中获取基点。
    EC_POINT_copy,将获取的基点复制给P_。
EC_POINT_get_affine_coordinates( curveAttributes_, P_, XP_, YP_, ctx_ ) 将基点的EC_POINT形式 转换为BIGNUM类型的横坐标和纵坐标的形式。

 EC_POINT_oct2point(const EC_GROUP *group, EC_POINT *p, const unsigned char *buf, size_t len, BN_CTX *ctx)

memcpy函数复制数据到另一个缓冲区。

从EVP_PKEY结构中提取数据的函数

int EVP_PKEY_get_octet_string_param(
    const EVP_PKEY *pkey,
    const char *key_name,
    unsigned char *buf,
    size_t max_buf_sz,
    size_t *out_sz
);

这个函数通常用于从 EVP_PKEY 结构中提取密钥数据,比如私钥或公钥的原始字节序列。
这个函数使用八位字节数组来存放数据,这里里面的数据是二进制数据。
提取的数据可以使用相关函数转换为BIGNUM或EC_POINT类型
**参数:**
- pkey: const EVP_PKEY *,指向 EVP_PKEY 结构的指针,该结构存储了密钥信息。
- key_name: const char *,参数名称,它指定了要从 EVP_PKEY 结构中获取哪个参数。例如,可以是 OSSL_PKEY_PARAM_PRIV_KEY 或 OSSL_PKEY_PARAM_PUB_KEY 等,用于指定是获取私钥还是公钥。
- buf: unsigned char *,用来存储获取的参数值的缓冲区。
- max_buf_sz: size_t,缓冲区 buf 的大小,以字节为单位。
- out_sz: size_t *,一个指针,用来存储写入 buf 的实际字节数。

**返回值**
- 返回 1 表示成功。
- 返回 0 表示失败。

提取密钥参数的函数

int EVP_PKEY_get_bn_param(const EVP_PKEY *pkey, const char *key_name, BIGNUM **bn);
- const EVP_PKEY *pkey: 指向 EVP_PKEY 对象的指针,该对象包含了您希望检索参数的密钥。
- const char *key_name: 参数的名称,这是一个字符串,用于指定你希望检索的密钥参数。
- BIGNUM **bn: 指向 BIGNUM* 的指针的地址,用来存储从 EVP_PKEY 中提取出的大整数值。

EVP_PKEY_get_bn_param 函数常用于提取密钥参数的数值,如:
- 从 RSA 密钥中提取模数 n、公钥指数 e 或私钥指数 d。
- 从椭圆曲线密钥中提取坐标值 x 和 y 或私钥 d。

密钥推导函数HKDF

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);


参数解释:
- const char *pass: 密码或密钥材料的指针。
- int passlen: 密码的长度,如果为负值,函数会假定 pass 是一个以 NULL 结尾的字符串,并自动计算长度。
- const unsigned hidden char *salt: 盐值的指针,盐值是在密钥推导过程中使用的随机数据。
- int saltlen: 盐值的长度,以字节为单位。
- int iter: 密钥推导中的迭代次数。增加迭代次数可以增强密钥的抗攻击能力,但也会相应增加计算成本。
- const EVP_MD *digest: 使用的哈希算法,通常是通过 EVP_sha256() 等函数获得的指针。
- int keylen: 期望得到的密钥长度,以字节为单位。
- unsigned char *out: 用于存储生成密钥的缓冲区的指针。

盐值的生成

  1. 使用非确定性随机数生成器random_device来生成随机数种子
  2. 使用随机数种子初始化 Mersenne Twister 19937 生成器
  3. 定义生成0到255之间的随机数的分布
  4. 生成并填充盐值。

注:改变指针指向的对象的内容,传递一级指针即可,但是如果想要改变指针的指向就需要传递二级指针。

OSSL_PARAM 结构体及其相关操作

OSSL_PARAM 结构体定义

typedef struct {
    const char *key;
    unsigned int data_type;
    void *data;
    size_t data_size;
    size_t return_size;
} OSSL_PARAM;

成员解释:
1、key:参数的名称(键名),用来标识参数的用途或内容。比如 "private_key"、"public_key" 等。
2、data_type:参数的数据类型,决定了 data 指针指向的数据类型。常见的类型包括OSSL_PARAM_INTEGER、OSSL_PARAM_UNSIGNED_INTEGER、OSSL_PARAM_UTF8_STRING、OSSL_PARAM_OCTET_STRING 等。
3、data:指向参数数据的指针。数据的类型由 data_type 决定。
4、data_size:数据的大小(以字节为单位)。这个字段表示 data 所指向的数据的实际大小。
5、return_size:表示读取或写入操作的实际数据大小。这在执行函数调用后可能会被更新,以反映实际操作的数据量。

数据类型常量:
OSSL_PARAM 的 data_type 字段可以取多个预定义的常量值,常见的包括:
1、OSSL_PARAM_INTEGER:有符号整数。
2、OSSL_PARAM_UNSIGNED_INTEGER:无符号整数。
3、OSSL_PARAM_UTF8_STRING:UTF-8 字符串。
4、OSSL_PARAM_OCTET_STRING:八位字节数组。
5、OSSL_PARAM_UTF8_PTR:指向 UTF-8 字符串的指针。
6、OSSL_PARAM_UTF8_STRING:定长 UTF-8 字符串。

常用的宏和函数:
OpenSSL 提供了一系列宏和函数,用于方便地构造和操作 OSSL_PARAM 结构体。常用的宏包括:
1、OSSL_PARAM_construct_int:构造一个 int 类型的参数。
2、OSSL_PARAM_construct_uint:构造一个 unsigned int 类型的参数。
3、OSSL_PARAM_construct_BN:构造一个 BIGNUM 类型的参数。
4、OSSL_PARAM_construct_utf8_string:构造一个 UTF-8 字符串参数。
5、OSSL_PARAM_construct_octet_string:构造一个八位字节数组参数。
6、OSSL_PARAM_construct_end:构造一个结束参数,用于表示参数数组的结束。

OSSL_PARAM 构建过程使用的函数及理解:
使用OSSL_PARAM构造器来构造OSSL_PARAM对象的过程:

1、创建EVP_PKEY_CTX上下文对象:
    EVP_PKEY_CTX pctx =  EVP_PKEY_CTX_new_from_name( NULL, "EC", NULL );
2、初始化pctx :
    EVP_PKEY_fromdata_init( pctx )
3、创建OSSL_PARAM构造器对象和OSSL_PARAM指针:
    OSSL_PARAM_BLD *param_bld;
    OSSL_PARAM *params = NULL;
4、初始化OSSL_PARAM构造器对象
    param_bld =OSSL_PARAM_BLD_new();
5、将公钥转换为二进制字节数据:
    size_t k_pub_len = EC_POINT_point2oct( curveAttributes_, k_pub, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, ctx_ );
    unsigned char *k_pub_c = ( unsigned char * )OPENSSL_malloc( k_pub_len );
    EC_POINT_point2oct( curveAttributes_, k_pub, POINT_CONVERSION_UNCOMPRESSED, k_pub_c, k_pub_len, ctx_ );
5、使用OSSL_PARAM构造器来构造OSSL_PARAM数组:
    if ( k_priv != NULL && param_bld != NULL
        && OSSL_PARAM_BLD_push_utf8_string( param_bld, OSSL_PKEY_PARAM_GROUP_NAME, "prime256v1", 0 )
        && OSSL_PARAM_BLD_push_BN( param_bld, OSSL_PKEY_PARAM_PRIV_KEY, k_priv )
        && OSSL_PARAM_BLD_push_octet_string( param_bld, OSSL_PKEY_PARAM_PUB_KEY, k_pub_c, k_pub_len ) )
    {
        params = OSSL_PARAM_BLD_to_param( param_bld );
    }
6、使用params来创建EVP_PKEY 对象:
    EVP_PKEY_fromdata( pctx, out_evp_pkey, EVP_PKEY_KEYPAIR, params );

上述过程是官方文档中提供的过程。

Base64编码

Base64 是一种基于 64 个可打印字符来表示二进制数据的表示方法,由于 2^6=64,所以每 6 个比特为一个单元,对应某个可打印字符。
Base64 常用于在通常处理文本数据的场合,表示、传输、存储一些二进制数据,包括 MIME 的电子邮件及 XML 的一些复杂数据。
Base64 编码要求把 3 个 8 位字节(38=24)转化为 4 个 6 位的字节(46=24),之后在 6 位的前面补两个 0,形成 8 位一个字节的形式。 如果剩下的字符不足 3 个字节,则用 0 填充,输出字符使用 =,因此编码后输出的文本末尾可能会出现 1 或 2 个 =。
为了保证所输出的编码位可读字符,Base64 制定了一个编码表,以便进行统一转换。编码表的大小为 2^6=64,这也是 Base64 名称的由来。

Base64 中的可打印字符包括26个大写字母 A-Z、26个小写字母a-z、数字 0-9,这样共有 62 个字符,此外两个可打印符号在不同的系统中而不同。

以下是 Base64 编码的基本步骤:

  • 将数据划分为 3 个字节一组(24位)。
  • 将每个字节转换为 8 位二进制形式。
  • 将 24 位数据按照 6 位一组进行划分,得到 4 个 6 位的组。
  • 将每个 6 位的组转换为对应的 Base64 字符。
  • 如果数据不足 3 字节,进行填充。
  • 将所有转换后的 Base64 字符连接起来,形成最终的编码结果。

解码 Base64 编码的过程与编码相反,将每个 Base64 字符转换为对应的6位二进制值,然后将这些 6 位值组合成原始的二进制数据。

Base64 编码具有以下特点:

  • 编码后的数据长度总是比原始数据长约 1/3。
  • 编码后的数据可以包含 A-Z、a-z、0-9 和两个额外字符的任意组合。
  • Base64 编码是一种可逆的编码方式,可以通过解码还原原始数据。

往过滤器里面写数据,往内存里面读数据。

OpenSSL BIO接口

  • BIO包含了多种接口,用于控制在BIO_METHOD中不同实现函数, 包括6种filter型和8种source/sink型应用场景。
  • BIO_new创建一个BIO对象.
  • 数据源:source/sink类型的BIO是数据源BIO_new(BIO_s_mem()),生存内存是数据源对象
  • 过滤:filter BIO就是把数据从一个BIO转换到另外一个BIO或应用接口 BIO_new(BIO_f_base64())
  • BIO链:一个BIO链通常包括一个source BIO和一个或多个filter BIO BIO_push(b64_bio, mem_bio);
  • 写编码, 读解码 BIO_write BIO_read_ex

Base64编码用于将二进制数据转换位可读的ASCII文本数据。
由于每个字符在大多数字符编码(如 ASCII)中占用一个字节,这样就将原本的24位信息用32位来表示了。

Base64编解码:示例链接:https://blog.csdn.net/maoye/article/details/132994754

X509相关结构

X509_REQ结构

struct X509_req_st {
    X509_REQ_INFO *req_info;  // 证书请求信息
    X509_ALGOR *sig_alg;      // 签名算法
    ASN1_BIT_STRING *signature; // 签名
    CRYPTO_REF_COUNT references; // 引用计数
    CRYPTO_RWLOCK *lock;         // 锁
} /* X509_REQ */;

结构成员详细说明:
1. req_info (X509_REQ_INFO *):
    - 描述: 包含证书请求的所有主要信息。
    - 内容: 包括主体名、主题公钥、属性等。

2. sig_alg (X509_ALGOR *):
    - 描述: 指定签名算法。
    - 内容: 包含用于签名的算法标识符和参数。
3. signature (ASN1_BIT_STRING *):
    - 描述: 请求的签名。
    - 内容: 这是对 req_info 进行签名后生成的签名值,通常由请求者的私钥生成。
4. references (CRYPTO_REF_COUNT):
    - 描述: 引用计数,用于管理对象的内存。
    - 内容: 跟踪结构体的引用次数,确保内存正确分配和释放。
5. lock (CRYPTO_RWLOCK *):
    - 描述: 锁,用于多线程安全。
    - 内容: 保护对结构的并发访问。

X509_REQ_INFO 结构
X509_REQ_INFO 结构是 X509_REQ 中的一个关键成员,它包含了请求的主要信息:

struct X509_req_info_st {
    ASN1_ENCODING enc; // 编码信息
    ASN1_INTEGER *version; // 版本号,通常为0
    X509_NAME *subject; // 主题名(请求者的名称)
    X509_PUBKEY *pubkey; // 主题公钥(请求者的公钥)
    STACK_OF(X509_ATTRIBUTE) *attributes; // 其他可选属性
} /* X509_REQ_INFO */;

成员详细说明
1. enc (ASN1_ENCODING):
    - 描述: 编码信息,用于缓存编码后的数据。
    - 内容: 用于优化编码和解码操作。

2. version (ASN1_INTEGER *):
    - 描述: 请求的版本号。
    - 内容: 通常为 0,表示 X.509 v1。

3. subject (X509_NAME *):
    - 描述: 主题名。
    - 内容: 包含请求者的名称,例如组织名、国家名等。
4. pubkey (X509_PUBKEY *):
    - 描述: 主题公钥。
    - 内容: 包含请求者的公钥。
5. attributes (STACK_OF(X509_ATTRIBUTE) *):
    - 描述: 其他可选属性。
    - 内容: 包含一些可选的属性,如 email 地址等。

X509_NAME 结构

X509_NAME 结构由一系列的 X509_NAME_ENTRY 对象组成,每个 X509_NAME_ENTRY 对象代表一个名称属性(如国家、组织等)。这些名称条目可以包含如下类型的信息:

- C (Country): 国家,使用两个字母的 ISO 国家代码表示,例如 "US" 代表美国。
- ST (State or Province): 州或省份的完整名称,例如 "California"。
- L (Locality): 地区或城市的名称,例如 "San Francisco"。
- O (Organization): 组织名称,如公司名,例如 "Google Inc."。
- OU (Organizational Unit): 组织单位名称,代表组织中的部门或分支,例如 "Human Resources"。
- CN (Common Name): 常用名称,通常用于指定服务器的域名或个人的全名,例如 "www.example.com" 或 "John Doe"。
- E (Email): 电子邮件地址。

X509_NAME 设置完成后,需要填入到X509_NAME *subject; // 主题名(请求者的名称)字段中,使用```X509_REQ_set_subject_name(req, name);

对CRS进行签名后就生成了证书。

X509主体结构

Certificate ::= SEQUENCE {
        
        tbsCertificate       TBSCertificate, -- 证书主体
        
        signatureAlgorithm   AlgorithmIdentifier, -- 证书签名算法标识
        
        signatureValue       BIT STRING --证书签名值,是使用signatureAlgorithm部分指定的签名算法对tbsCertificate证书主题部分签名后的值.
}

TBSCertificate ::= SEQUENCE {
        version         [0] EXPLICIT Version DEFAULT v1, -- 证书版本号
        
        serialNumber         CertificateSerialNumber, -- 证书序列号,对同一CA所颁发的证书,序列号唯一标识证书
        
        signature            AlgorithmIdentifier, --证书签名算法标识
        
        issuer               Name,                --证书发行者名称
        
        validity             Validity,            --证书有效期
        
        subject              Name,                --证书主体名称
        
        subjectPublicKeyInfo SubjectPublicKeyInfo,--证书公钥
        
        issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
                             -- 证书发行者ID(可选),只在证书版本2、3中才有
        
        subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
                             -- 证书主体ID(可选),只在证书版本2、3中才有
        
        extensions      [3] EXPLICIT Extensions OPTIONAL
                             -- 证书扩展段(可选),只在证书版本3中才有
}

从CSR创建证书的必要步骤:

  1. 验证CSR:确保CSR格式正确且可被解析。
  2. 创建新证书:从CSR复制信息到新的X509结构。
  3. 设置序列号和有效期:为新证书设置唯一的序列号和有效期
    • 序列号必须保证唯一,因此可以使用基于时间戳、递增的整数和随机数等方式组合起来,进一步来确保序列号的唯一性和不可预测性。
  4. 签名证书:使用CA的私钥对证书进行签名。

证书相关函数

相关读函数

EVP_PKEY *PEM_read_PrivateKey(FILE *fp, EVP_PKEY **x, pem_password_cb *cb, void *u);
- fp: 指向已打开的文件的指针,该文件应包含 PEM 格式的私钥。
- x: 一个指向 EVP_PKEY 结构的指针的地址。如果 *x 是 NULL,一个新的 EVP_PKEY 结构将被创建并返回。如果 *x 非 NULL,它必须指向一个由用户提前分配的 EVP_PKEY 结构。
- cb: 一个回调函数,用于在需要密码解密私钥时获取密码。如果这个参数为 NULL,并且私钥是加密的,那么会从标准输入请求密码。
- u: 一个用户自定义的参数,会被传递给回调函数 cb。
X509 *PEM_read_X509(FILE *fp, X509 **x, pem_password_cb *cb, void *u);
- FILE *fp: 文件指针,指向包含 PEM 格式证书的文件。如果 fp 为 NULL,则该函数不会从文件读取,而是会根据其他参数读取证书。
- X509 **x: 指向 X509 结构体的指针的指针。如果 *x 为 NULL,PEM_read_X509 会分配一个新的 X509 对象。如果 *x 不为 NULL,则函数会将读取到的证书内容填充到现有的 X509 结构体中。
- pem_password_cb *cb: 回调函数,用于读取 PEM 文件中加密证书的密码。如果证书未加密或密码为空,这个参数可以为 NULL。回调函数的原型为 int cb(char *buf, int size, int rwflag, void *userdata),通常用于读取或提示用户输入密码。
- void *u: 用户数据,用于传递给回调函数 cb。通常是回调函数需要的上下文数据或用户数据。(主要用于传递密钥)

返回值:
- 成功时:返回一个指向 X509 结构体的指针,表示读取到的证书。
- 失败时:返回 NULL,并设置错误代码,可以使用 ERR_print_errors_fp(stderr) 输出错误信息。

相关写函数

int PEM_write_PrivateKey(FILE *fp, EVP_PKEY *x, const EVP_CIPHER *enc, unsigned char *kstr, int klen, pem_password_cb *cb, void *u);

参数解释
FILE *fp:文件指针,指向要写入的文件。私钥将以 PEM 格式写入这个文件。如果你希望输出到标准输出,可以使用 stdout。
EVP_PKEY *x:指向包含要写入的私钥的 EVP_PKEY 结构体的指针。EVP_PKEY 是 OpenSSL 用于存储密钥的通用数据结构,可以表示多种类型的密钥(如 RSA、DSA、EC 等)。
const EVP_CIPHER *enc:指向用于加密私钥的 EVP_CIPHER 结构体的指针。如果设置为 nullptr,则私钥将以未加密形式写入。如果提供了加密算法,私钥将使用该算法进行加密。
unsigned char *kstr:加密私钥时使用的密码。这个参数是一个指向密码的指针。如果使用 cb 参数提供的回调函数来获取密码,可以设置为 nullptr。
int klen:密码的长度。这个参数表示 kstr 所指向的密码的长度。如果 kstr 是 nullptr,则该参数可以忽略。
pem_password_cb *cb:密码回调函数。如果需要动态地从用户获取密码或进行一些自定义密码处理,可以提供一个回调函数。回调函数的类型为 pem_password_cb,它的签名如下:
int pem_password_cb(char *buf, int size, int rwflag, void *userdata);
如果不需要回调函数,可以设置为 nullptr。
void *u:传递给回调函数的用户数据。如果回调函数不需要用户数据,可以设置为 nullptr。

返回值:
成功时返回 1。
失败时返回 0。
int PEM_write_X509(FILE *fp, X509 *x);
参数解释:
- FILE *fp: 指向目标文件的文件指针,证书将被写入这个文件。如果输出到标准输出,可以使用 stdout。
- X509 *x: 指向要写入的 X.509 证书对象的指针。

返回值:
- 成功时返回 1。
- 失败时返回 0。

证书时间函数

ASN1_TIME *X509_getm_notBefore(X509 *x);
ASN1_TIME *X509_getm_notAfter(X509 *x);

ASNI_Time 结构体的结构:

struct asn1_string_st {
    int length;
    int type;
    unsigned char *data;
    /*
     * The value of the following field depends on the type being held.  It
     * is mostly being used for BIT_STRING so if the input data has a
     * non-zero 'unused bits' value, it will be handled correctly
     */
    long flags;
};

时间类型分为:UTCTime和GENERALIZEDTIME
UTCTime时间格式为:YYMMDDHHMMSSZ。
GENERALIZEDTIME时间格式为:YYYYMMDDHHMMSSZ

X509_check_ca( cert )

time( NULL );

EVP_PKEY_base_id( PriKey ) == EVP_PKEY_EC

使用C++流缓冲区迭代器来读取文件。
std::string pemContent((std::istreambuf_iterator(file)), std::istreambuf_iterator());
file是std::ifstream 打开的文件流对象。
std::istreambuf_iterator(file) 指向了文件流的起始位置。它用于逐字节读取文件内容,直至文件结尾。
std::istreambuf_iterator():这是一个默认构造的流缓冲区迭代器,表示文件流的结束位置。它没有指定任何流,表示的是文件流的结尾。

BIO *BIO_new_mem_buf(const void *buf, int len);
OpenSSL 中的一个函数,用于创建一个内存 BIO(Basic Input/Output)对象。BIO 是 OpenSSL 的 I/O 抽象层,提供了各种 I/O 操作接口。内存 BIO 是一种特殊的 BIO,它从内存缓冲区中读取数据,而不是从文件或网络中读取数据。

打印证书名示例:
char *subject = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);
char *issuer = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0);

char *X509_NAME_oneline(X509_NAME *a, char *buf, int size);
用于将 X509_NAME 结构体转换为一个简单的、单行的字符串格式,方便显示和输出。X509_NAME 结构体通常表示 X.509 证书中的主体名称(subject name)或颁发者名称(issuer name)。

参数解释
X509_NAME *a: 这是一个指向 X509_NAME 结构体的指针。X509_NAME 结构体包含证书中的名称信息。
char *buf: 指向一个字符缓冲区,用于存储转换后的字符串。如果传入 NULL,函数会动态分配内存并返回指向新分配的字符串的指针。
int size: 指定缓冲区的大小。如果缓冲区太小,函数会截断字符串。如果 buf 为 NULL,该参数将被忽略。

返回值
返回一个指向转换后字符串的指针。
如果 buf 为 NULL,则函数会动态分配内存并返回该内存的指针,使用完后需要手动释放。
如果 buf 不为 NULL,则返回 buf。

onstexpr 是 C++11 引入的一个关键字,用于指定表达式的值可以在编译时计算。这有助于优化代码,提高效率,并确保某些常量表达式在编译时被评估。

struct evp_md_st 是表示摘要算法(也称为消息摘要算法)的数据结构。它被用于各种加密操作,包括哈希计算和数字签名。在 OpenSSL 的源代码中,这个结构通常被定义为 EVP_MD,是封装不同摘要算法特性的关键结构。

使用EVP_PKEY私钥来生成签名

  1. 需要初始化EVP_MD_CTX *mctx 上下文。

  2. 生成消息摘要,这个摘要相当于消息独一无二的指纹,与私钥配合使用来生成签名
    EVP_DigestSignInit(mctx, NULL, EVP_sha256(), NULL, priKey);

mdctx: 这是一个指向 EVP_MD_CTX 结构的指针,它是一个加密上下文,用于管理摘要和签名操作的状态。你需要在调用 EVP_DigestSignInit 之前创建并初始化这个上下文。

NULL: 第二个参数是指向一个 EVP_PKEY_CTX 的指针的指针,用于接收内部 EVP_PKEY_CTX 结构的地址。传递 NULL 表示你不需要从这个函数获取该结构的指针。

EVP_sha256(): 这是一个函数调用,返回一个指向 EVP_MD 结构的指针,该结构表示使用 SHA-256 哈希算法。这个参数指定了用于摘要操作的哈希算法。

NULL: 第四个参数是一个指向 ENGINE 的指针,用于指定一个加密引擎。如果传递 NULL,表示不使用任何特定的加密引擎,而使用默认的实现。

pkey: 这是一个指向 EVP_PKEY 结构的指针,它包含了用于数字签名的密钥(通常是私钥)。这个密钥将用于签名操作中,以确保数据的完整性和认证性。

  1. 获取签名的数据长度,这一步接收数据的指针部分为空,只用于获取签名的数据长度。
    EVP_DigestSign(mdctx, NULL, &sig_len, (const unsigned char *)data, data_len);

  2. 进行签名
    EVP_DigestSign(mdctx, sig.data(), &sig_len, (const unsigned char *)data, data_len)

ECDSA签名验证算法

  1. 创建EVP_MD_CTX 签名上下文

  2. 初始化验证操作
    EVP_DigestVerityInit函数

  3. 传递原始数据
    EVP_DigestVerifyUpdate(vctx, rawDataArray, rawDataLength)

4.验证签名

下面这个函数是一步完成:
EVP_DigestVerify(EVP_MD_CTX *ctx, const unsigned char *sigret, size_t siglen, const unsigned char *tbs, size_t tbslen)

reinterpret_cast 是 C++ 中一种强大的类型转换运算符,它提供了在不同类型之间进行低级别转换的能力。它主要用于对数据的位模式进行重新解释,而不改变其值。这种转换一般用于处理那些与类型系统的常规操作不兼容的场合,因此使用时需要特别小心,以避免造成难以调试的错误。

使用场景和特性

reinterpret_cast 通常用在以下几种场景中:

  1. 指针类型之间的转换

    • 将任何类型的指针转换为另一个足够宽的类型的指针(如从 int* 转换为 char*)。
    • 将指针转换为足够大的整数类型,或者相反(尽管这通常更适合使用 uintptr_t 这样的整数类型进行)。
    int* p = new int(10);
    char* cp = reinterpret_cast<char*>(p);  // 将 int* 转换为 char*
    
  2. 指向函数的指针之间的转换

    • 如果函数签名不同,可以使用 reinterpret_cast 来转换指向函数的指针,但是调用转换后的函数指针是未定义行为,除非确保转换是安全的。
    void (*funcPtr)(int);
    void (*anotherFuncPtr)() = reinterpret_cast<void(*)()>(funcPtr);
    
  3. 成员指针之间的转换

    • 用于类成员指针的转换,尤其是在不同类之间的成员指针转换。
  4. void* 之间的转换

    • 可以将任何类型的指针转换为 void* 指针,反之亦然。这在与 C 语言兼容的接口中非常常见。
    int* pi = new int;
    void* pv = reinterpret_cast<void*>(pi);  // 将 int* 转换为 void*
    int* pi2 = reinterpret_cast<int*>(pv);   // 将 void* 回转为 int*
    

注意事项

  • 类型安全reinterpret_cast 不提供任何类型安全检查,因此它的使用可能会导致类型系统的破坏,使用者必须非常小心。
  • 移植性:由于 reinterpret_cast 涉及到底层的位操作,所以它的行为可能依赖于平台(如字节序和数据对齐),使用时应考虑代码的移植性。
  • 未定义行为:使用 reinterpret_cast 错误可能导致未定义行为,特别是在指针转换和函数指针调用方面。

由于 reinterpret_cast 的这些特性,建议仅在没有其他安全的类型转换方法可用时才使用它,并且在使用前确保理解其后果。正确使用时,它是一个非常有用的工具,尤其是在需要对数据进行底层操作的系统编程中。

标签:PKEY,椭圆,加密,X509,openssl,PARAM,int,OSSL,EVP
From: https://www.cnblogs.com/wuhaiqiong/p/18577044

相关文章

  • 基于数据可视化+加密算法+Javaweb的图书管理系统设计与实现
    文章目录1.内容见下图2.详细视频演示3.系统运行效果介绍4.技术框架4.1前后端分离架构介绍4.3程序操作流程5.项目推荐6.成品项目7.系统测试7.1系统测试的目的7.2系统功能测试8.代码参考9.为什么选择我?10.获取源码1.内容见下图2.详细视频演示文章......
  • JAVA实现SHA-256加密
    1、实现代码importjava.security.MessageDigest;importjava.security.NoSuchAlgorithmException;publicclassSHA256Example{publicstaticvoidmain(String[]args){Stringinput="Hello,World!";//要计算哈希的输入字符串try{......
  • LUKS加密和解密操作
    LUKS相关操作LUKS开机自动加载分区LUKS配置备份与还原LUKS(LinuxUnifiedKeySetup)是RedhatLinux提供的一种标准的设备加密系统。在文件系统被挂载之前一个被LUKS加密的分区或者卷必须先被解密,否则这个分区或者卷无法被挂载。LUKS相关操作1.创建一个加密的分区我们以虚......
  • Java class 文件安全加密工具对比+ClassFinal实战
    前言常见加密方案对比XJarProGuardClassFinalClassFinal实战纯命令方式maven插件方式最后前言相信不少的同学开发的软件都是用户商业化,对于这些商业运营的项目很多都会直接部署在客户方,这样就可能会导致项目源码泄露。当然,作为Java语言的搬砖人......
  • SKILL脚本的加密与解密及使用
    SKILL脚本一般是用.il和.ile结尾的文件,一般设置为.ile结尾的文件是加密的,调用的时候需要密码。SKILL脚本的加密:用encrypt函数加密脚本,格式如下:encrypt("/apps/SC/skill-script/migrateDesign/MigrateDesign.il" "/apps/SC/skill-script/migrateDesign/MigrateDesign.ile......
  • SMB(Server Message Block)协议 中,SMB 加密和 SMB 签名是确保数据传输安全性的重要手段;N
    Windows11企业版中,SMB(ServerMessageBlock)共享协议确实与安全认证相关,特别是在涉及到网络共享、文件共享和认证时。SMB协议本身提供了多种认证和加密方式,以确保数据传输的安全性。下面是有关SMB登录时证书认证和安全认证的详细信息:1. SMB协议的认证方式SMB协议在Window......
  • 关于RSA加密接口的步骤
    http://www.usey.cn/rsa 先去获取公钥和私钥publicstaticStringencryptStr(StringencryptStr){RSArsa2=newRSA(null,publicKey);returnrsa2.encryptBase64(encryptStr,KeyType.PublicKey);}加密方式publicstaticboolean......
  • 升级部署openssl 1.1.1
    环境:OS:Centos7说明:当前已经升级了openssh的情况下进行升级openssl[root@node2Python-3.12.7]#ssh-VOpenSSH_9.8p1,OpenSSL1.1.1w11Sep2023我之前安装的openssl比较新的版本,比如3.0.14,在编译安装python3.12的时候一直报错误:Couldnotbuildthesslmodule!Pyth......
  • Md5加密
    /***功能:可cacheConsumer*<p>**@authorzlc*@seeSupplier*@since2023-11-14*/@FunctionalInterfacepublicinterfaceCatchableSupplier<T>{/***包装CatchableSupplier*<p>*CatchableSupplier=>Supp......
  • 压缩包技巧:如何加密7-Zip压缩包?
    7z压缩包如何进行加密,之前和大家分享过,今天想和大家分享的是,如何使用7-zip对7z文件进行加密。7z加密教程如下:安装压缩软件,大家可以去官网下载软件安装好软件之后,我们右键点击需要压缩的文件,找到【7-Zip】,选择【添加到压缩包】来到压缩软件界面之后,软件自动默认文件将要压......