文章有很多相同函数的解释,建议直接跳到需要学习的目录
一、基本用法
1 | // 生成密钥对 |
必要头文件
1 |
二、生成密钥对
2.1、EVP_PKEY_CTX_new_id
用于创建密钥上下文的函数,基于给定的公钥算法标识符创建一个新的密钥上下文对象
1 | EVP_PKEY_CTX *EVP_PKEY_CTX_new_id(int id, ENGINE *e); |
参数解释:
- id : 公钥算法的标识符,用于指定要使用的加密算法,可以使用
EVP_PKEY_XXX常量(例如EVP_PKEY_RSA、EVP_PKEY_EC)指定所需的算法。 - e:可选参数,指定要使用的加密引擎。如果为 NULL,则使用默认的加密引擎
返回值:
- 成功:返回指向新创建的
EVP_PKEY_CTX对象的指针。 - 失败:返回 NULL。
1 | // 创建密钥上下文对象 |
2.2、EVP_PKEY_keygen_init
用于初始化密钥对生成操作的函数,用于初始化设置密钥生成操作的参数和上下文ctx
1 | int EVP_PKEY_keygen_init(EVP_PKEY_CTX *ctx); |
参数解释:
ctx:指向要初始化的密钥对上下文的指针。
返回值:
- 1:成功初始化密钥对生成操作。
- 0 或者 负数:初始化失败。
1 | // 初始化ctx |
2.3、EVP_PKEY_CTX_set_rsa_keygen_bits
用于设置生成的 RSA 密钥对的位数
1 | int EVP_PKEY_CTX_set_rsa_keygen_bits(EVP_PKEY_CTX *ctx, int bits); |
参数解释:
ctx:指向要设置 RSA 密钥对位数的密钥对上下文的指针。bits:要生成的 RSA 密钥对的位数。
返回值:
- 1:成功设置 RSA 密钥生成位数。
- 0: 或者 负数:设置失败。
1 | // 密钥长度,单位为比特 |
2.4、EVP_PKEY_generate
根据提供的密钥对上下文对象,生成一个新的密钥对,生成的密钥对保存在ppkey中
1 | int EVP_PKEY_generate(EVP_PKEY_CTX *ctx, EVP_PKEY **ppkey); |
参数解释:
ctx:指向已初始化的密钥对上下文的指针。ppkey:指向接收生成的密钥对的指针,生成的密钥对保存在ppkey中。
返回值:
- 1:成功生成密钥对。
- 0 或者 负数:生成失败。
1 | EVP_PKEY* m_privateKey = NULL; |
注意:ppkey可以使用私钥,这是因为私钥包含公钥,而公钥只包含公钥。同时,如果在函数中使用new创建一个EVP_PKEY*对象,没有回收会导致内存泄露。
2.5、EVP_PKEY_CTX_free
释放密钥对上下文对象
1 | void EVP_PKEY_CTX_free(EVP_PKEY_CTX *ctx); |
参数解释:
ctx:指向要释放内存的密钥对上下文对象的指针。
1 | // 释放上下文 |
2.6、BIO_new_file
用于创建适用于文件 I/O 的 BIO 对象,它允许将文件与 BIO 抽象接口结合使用,以便进行读取或写入文件的操作。
1 | BIO *BIO_new_file(const char *filename, const char *mode); |
参数解释:
filename:要进行读取或写入的文件的名称或路径。mode:表示文件操作模式的字符串。常见的模式包括:"r":只读模式。"w":写入模式,如果文件不存在则创建,如果文件存在则截断。"a":追加模式,在文件末尾写入数据。"rb":以二进制格式打开文件进行只读操作。"wb":以二进制格式打开文件进行写入操作。"ab":以二进制格式打开文件进行追加操作。
返回值:
- 成功:指向新创建的文件
BIO对象的指针。 - 失败:返回
NULL。
1 | // 将私钥写入文件(私钥里面包含公钥),pri是私钥文件名 |
2.7、BIO_flush
刷新 BIO 对象,将内存中的数据写入磁盘中,跟fflush函数类似
1 | int BIO_flush(BIO *bio); |
参数解释:
bio:指向要刷新的BIO对象的指针。
返回值:
- 成功:返回 1。
- 失败:返回 0。
2.8、BIO_free
释放 BIO 对象
1 | void BIO_free(BIO *bio); |
参数解释:
bio:指向要释放的BIO对象的指针。
完整示例
1 | void RSACrypto::generateRSAKeyPair(KeyLength bits, const QByteArray& pub, const QByteArray& pri) |
三、读取或释放密钥对
3.1、从文件中读取密钥对
3.1.1、BIO_new_file
用于创建适用于文件 I/O 的 BIO 对象,它允许将文件与 BIO 抽象接口结合使用,以便进行读取或写入文件的操作。
1 | BIO *BIO_new_file(const char *filename, const char *mode); |
参数解释:
filename:要进行读取或写入的文件的名称或路径。mode:表示文件操作模式的字符串。常见的模式包括:"r":只读模式。"w":写入模式,如果文件不存在则创建,如果文件存在则截断。"a":追加模式,在文件末尾写入数据。"rb":以二进制格式打开文件进行只读操作。"wb":以二进制格式打开文件进行写入操作。"ab":以二进制格式打开文件进行追加操作。
返回值:
- 成功:指向新创建的文件
BIO对象的指针。 - 失败:返回
NULL。
1 | BIO* bio = BIO_new_file(fileName.data(), "rb"); |
3.1.2、PEM_read_bio_PUBKEY
从一个 BIO 对象(在内存中的数据流)中读取 PEM 编码的公钥,PEM_read_bio_PUBKEY 函数读取 PEM 编码的公钥,并将结果存储在 EVP_PKEY 结构体中。
1 |
|
该函数接受以下参数:
bp:指向 BIO 对象的指针,可用于读取 PEM 文件中的数据或从其他数据源读取 PEM 格式的数据。x:指向 EVP_PKEY 指针的指针,用于接收读取的公钥。cb:一个回调函数,用于处理密码(如果 PEM 文件有密码保护)。u:用户自定义数据,在回调函数中可以使用。
返回值:
- 成功,返回 EVP_PKEY 指针
- 失败,返回 NULL。
1 | if (keyType == PUBLICKEY) |
3.1.3、PEM_read_bio_PrivateKey
用于从一个 BIO 对象(在内存中的数据流)中读取 PEM 编码的私钥,PEM_read_bio_PrivateKey 函数读取 PEM 编码的公钥,并将结果存储在 EVP_PKEY 结构体中。
1 |
|
该函数接受以下参数:
bp:指向 BIO 对象的指针,可用于读取 PEM 文件中的数据或从其他数据源读取 PEM 格式的数据。x:指向 EVP_PKEY 指针的指针,用于接收读取的私钥。cb:一个回调函数,用于处理密码(如果 PEM 文件有密码保护)。u:用户自定义数据,在回调函数中可以使用。
返回值:
- 成功,返回 EVP_PKEY 指针
- 失败,返回 NULL。
1 | else |
记得调用BIO_free(bio);函数释放BIO对象
完整示例:
1 | RSACrypto::RSACrypto(const QByteArray& fileName, KeyType keyType, QObject* parent) |
3.2 释放密钥对对象
3.2.1、EVP_PKEY_free
1 | void EVP_PKEY_free(EVP_PKEY *pkey); |
参数解释:
- pkey: 要释放的私钥或公钥的EVP_PKEY对象
1 | RSACrypto::~RSACrypto() |
四、数据加解密
4.1使用公钥加密
4.1.1、EVP_PKEY_CTX_new
用于创建与给定密钥对象(EVP_PKEY)相关联的密钥上下文(EVP_PKEY_CTX)。
1 | EVP_PKEY_CTX *EVP_PKEY_CTX_new(EVP_PKEY *pkey, ENGINE *e); |
参数解释:
pkey:与上下文关联的密钥对象。这可以是一个公钥、私钥或对称密钥对象,具体取决于使用场景。e:可选参数,与上下文关联的引擎(Engine)。如果不需要使用特定引擎,可以传入NULL。
返回值
EVP_PKEY_CTX类型的指针,即新创建的密钥上下文对象
1 | // 创建加密数据的上下文对象 |
需要注意的是,使用完密钥上下文后应该调用
EVP_PKEY_CTX_free函数来释放相应的资源,以避免内存泄漏。
4.1.2、EVP_PKEY_encrypt_init
用于初始化使用非对称密钥进行加密操作
1 | int EVP_PKEY_encrypt_init(EVP_PKEY_CTX *); |
参数解释:
- ctx:要进行加密操作的密钥上下文
返回值
- 1:成功
- 0:失败
1 | int ret = EVP_PKEY_encrypt_init(ctx); |
4.1.3、EVP_PKEY_CTX_set_rsa_padding
用于设置 RSA 加密或解密操作的填充方式
1 | int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX *ctx, int pad); |
参数解释:
ctx:RSA 加密或解密操作的上下文(EVP_PKEY_CTX)。pad:要设置的加密填充(padding)方式,可以是以下值之一:RSA_PKCS1_PADDING:PKCS#1 填充方式,是最常见的 RSA 填充方式。RSA_PKCS1_OAEP_PADDING:PKCS#1 OAEP 填充方式,带有随机性质的填充方式,安全性更高。RSA_NO_PADDING:不进行填充操作,仅加密或解密数据。
设置签名时,不能使用RSA_PKCS1_OAPE_PADDING这种填充方式,因为OAEP 是一种概率性加密填充,每次加密同一明文会产生不同的密文,但签名需要确定性,这样才能保证相同数据的签名结果可以被验证
返回值
- 1:成功
- 0:失败
1 | ret = EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING); |
4.1.4、EVP_PKEY_encrypt
使用非对称密钥进行加密操作
1 | int EVP_PKEY_encrypt(EVP_PKEY_CTX *ctx, |
参数解释:
ctx:指向 EVP_PKEY_CTX 对象的指针,用于进行加密操作的上下文。out:指向输出缓冲区的指针,用于存储加密后的数据。outlen:指向保存输出数据长度的变量的指针,同时也作为输入来指定输出缓冲区的大小。in:指向输入缓冲区的指针,包含需要加密的数据。inlen:输入数据的长度。
返回值
- 1:成功
- 0:失败
1 | size_t outLen = 0; |
使用公钥加密数据,由于不知道加密数据的大小,所以第一次调用EVP_PKEY_encrypt的目的是获取outLen,这是因为outLen记录着加密后数据的长度,通过这个长度就能创建出合适的内存大小
4.1.5、EVP_PKEY_CTX_free
释放密钥对上下文对象
1 | void EVP_PKEY_CTX_free(EVP_PKEY_CTX *ctx); |
参数解释:
ctx:指向要释放内存的密钥对上下文对象的指针。
1 | // 释放资源 |
完整示例
1 | QByteArray RSACrypto::publicEncrypt(const QByteArray& data) |
4.2 使用私钥解密
4.2.1、EVP_PKEY_CTX_new
用于创建与给定密钥对象(EVP_PKEY)相关联的密钥上下文(EVP_PKEY_CTX)。
1 | EVP_PKEY_CTX *EVP_PKEY_CTX_new(EVP_PKEY *pkey, ENGINE *e); |
参数解释:
pkey:与上下文关联的密钥对象。这可以是一个公钥、私钥或对称密钥对象,具体取决于使用场景。e:可选参数,与上下文关联的引擎(Engine)。如果不需要使用特定引擎,可以传入NULL。
返回值
EVP_PKEY_CTX类型的指针,即新创建的密钥上下文对象
1 | // 创建加密数据的上下文对象 |
需要注意的是,使用完密钥上下文后应该调用
EVP_PKEY_CTX_free函数来释放相应的资源,以避免内存泄漏。
4.2.2、EVP_PKEY_decrypt_init
用于初始化使用非对称密钥进行解密操作
1 | int EVP_PKEY_decrypt_init(EVP_PKEY_CTX *); |
参数解释:
- ctx:要进行加密操作的密钥上下文
返回值
- 1:成功
- 0:失败
1 | int ret = EVP_PKEY_decrypt_init(ctx); |
4.2.3、EVP_PKEY_CTX_set_rsa_padding
用于设置 RSA 加密或解密操作的填充方式
1 | int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX *ctx, int pad); |
参数解释:
ctx:RSA 加密或解密操作的上下文(EVP_PKEY_CTX)。pad:要设置的加密填充(padding)方式,可以是以下值之一:RSA_PKCS1_PADDING:PKCS#1 填充方式,是最常见的 RSA 填充方式。RSA_PKCS1_OAEP_PADDING:PKCS#1 OAEP 填充方式,带有随机性质的填充方式,安全性更高。RSA_NO_PADDING:不进行填充操作,仅加密或解密数据。
设置签名时,不能使用RSA_PKCS1_OAPE_PADDING这种填充方式,因为OAEP 是一种概率性加密填充,每次加密同一明文会产生不同的密文,但签名需要确定性,这样才能保证相同数据的签名结果可以被验证
返回值
- 1:成功
- 0:失败
1 | ret = EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING); |
4.2.4、EVP_PKEY_decrypt
使用非对称密钥进行加密操作
1 | int EVP_PKEY_decrypt(EVP_PKEY_CTX *ctx, |
参数解释:
ctx:指向 EVP_PKEY_CTX 对象的指针,用于进行解密操作的上下文。out:指向输出缓冲区的指针,用于存储解密后的数据。outlen:指向保存输出数据长度的变量的指针,同时也作为输入来指定输出缓冲区的大小。in:指向输入缓冲区的指针,包含需要解密的数据。inlen:输入数据的长度。
返回值
- 1:成功
- 0:失败
1 | size_t outLen = 0; |
使用私钥解密数据,由于不知道解密数据的大小,所以第一次调用EVP_PKEY_decrypt的目的是获取outLen,这是因为outLen记录着解密后数据的长度,通过这个长度就能创建出合适的内存大小
4.2.5、EVP_PKEY_CTX_free
释放密钥对上下文对象
1 | void EVP_PKEY_CTX_free(EVP_PKEY_CTX *ctx); |
参数解释:
ctx:指向要释放内存的密钥对上下文对象的指针。
1 | // 释放资源 |
完整示例
1 | QByteArray RSACrypto::privateDecrypt(const QByteArray& data) |
五、数字签名和校验
数字签名和校验的流程:计算数据的哈希值,然后对哈希值进行数据签名,数据校验时,也是先计算接受到的数据的哈希值,然后对哈希值进行校验
5.1、数据签名
5.1.1、哈希值计算
Qt的QCryptographicHash提供了一系列的加密算法实现,其中就包括哈希值计算
1 | QCryptographicHash::Algorithm hashType; |
5.1.2、EVP_PKEY_CTX_new
用于创建与给定密钥对象(EVP_PKEY)相关联的密钥上下文(EVP_PKEY_CTX)。
1 | EVP_PKEY_CTX *EVP_PKEY_CTX_new(EVP_PKEY *pkey, ENGINE *e); |
参数解释:
pkey:与上下文关联的密钥对象。这可以是一个公钥、私钥或对称密钥对象,具体取决于使用场景。e:可选参数,与上下文关联的引擎(Engine)。如果不需要使用特定引擎,可以传入NULL。
返回值
EVP_PKEY_CTX类型的指针,即新创建的密钥上下文对象
1 | // 创建加密数据的上下文对象 |
需要注意的是,使用完密钥上下文后应该调用
EVP_PKEY_CTX_free函数来释放相应的资源,以避免内存泄漏。
5.1.3、EVP_PKEY_sign_init
用于初始化使用非对称密钥进行签名操作的函数
1 | int EVP_PKEY_sign_init(EVP_PKEY_CTX *); |
参数解释:
- ctx:要进行数字签名操作的密钥上下文
返回值
- 1:成功
- 0:失败
1 | int ret = EVP_PKEY_sign_init(ctx); |
5.1.4、EVP_PKEY_CTX_set_rsa_padding
用于设置 RSA 加密或解密操作的填充方式
1 | int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX *ctx, int pad); |
参数解释:
ctx:RSA 加密或解密操作的上下文(EVP_PKEY_CTX)。pad:要设置的加密填充(padding)方式,可以是以下值之一:RSA_PKCS1_PADDING:PKCS#1 填充方式,是最常见的 RSA 填充方式。RSA_PKCS1_OAEP_PADDING:PKCS#1 OAEP 填充方式,带有随机性质的填充方式,安全性更高。RSA_NO_PADDING:不进行填充操作,仅加密或解密数据。
设置签名时,不能使用RSA_PKCS1_OAPE_PADDING这种填充方式,因为OAEP 是一种概率性加密填充,每次加密同一明文会产生不同的密文,但签名需要确定性,这样才能保证相同数据的签名结果可以被验证
返回值
- 1:成功
- 0:失败
1 | ret = EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING); |
5.1.5、EVP_PKEY_CTX_set_signature_md
用于设置签名算法
1 | int EVP_PKEY_CTX_set_signature_md(EVP_PKEY_CTX *ctx, const EVP_MD *md); |
参数解释:
ctx:指向EVP_PKEY_CTX上下文结构的指针,用于设置签名算法。md:指向EVP_MD结构的指针,表示要使用的签名算法。
签名算法由 OpenSSL 中的 EVP_MD 结构表示,其中包含有关算法的信息,如名称、摘要长度等
返回值
- 1:成功
- 0:失败
1 | const QMap<QCryptographicHash::Algorithm, hashFunc> hashMethods = { |
5.1.6、EVP_PKEY_sign
使用非对称密钥进行签名操作的函数
1 | int EVP_PKEY_sign(EVP_PKEY_CTX *ctx, |
参数解释:
ctx:指向EVP_PKEY_CTX上下文结构的指针,表示签名操作的上下文。sig:指向缓冲区的指针,用于存储签名结果。siglen:指向sig缓冲区长度的指针,表示输入时表示sig缓冲区的长度,输出时表示实际写入sig的字节数。tbs:指向要签名的数据的指针。tbslen:要签名的数据的长度。
返回值:
- 1:成功
- 0:失败
1 | // 数据签名 |
5.1.7、EVP_PKEY_CTX_free
释放密钥对上下文对象
1 | void EVP_PKEY_CTX_free(EVP_PKEY_CTX *ctx); |
参数解释:
ctx:指向要释放内存的密钥对上下文对象的指针。
1 | // 释放上下文 |
完整示例:
1 | QByteArray RSACrypto::sign(const QByteArray& data, QCryptographicHash::Algorithm hashType) |
5.2、数据校验
5.2.1、哈希值计算
Qt的QCryptographicHash提供了一系列的加密算法实现,其中就包括哈希值计算
1 | QCryptographicHash::Algorithm hashType; |
5.2.2、EVP_PKEY_CTX_new
用于创建与给定密钥对象(EVP_PKEY)相关联的密钥上下文(EVP_PKEY_CTX)。
1 | EVP_PKEY_CTX *EVP_PKEY_CTX_new(EVP_PKEY *pkey, ENGINE *e); |
参数解释:
pkey:与上下文关联的密钥对象。这可以是一个公钥、私钥或对称密钥对象,具体取决于使用场景。e:可选参数,与上下文关联的引擎(Engine)。如果不需要使用特定引擎,可以传入NULL。
返回值
EVP_PKEY_CTX类型的指针,即新创建的密钥上下文对象
1 | // 创建加密数据的上下文对象 |
需要注意的是,使用完密钥上下文后应该调用
EVP_PKEY_CTX_free函数来释放相应的资源,以避免内存泄漏。
5.2.3、EVP_PKEY_verify_init
用于使用非对称密钥进行验签操作的函数
1 | int EVP_PKEY_verify_init(EVP_PKEY_CTX *); |
参数解释:
- ctx:要进行数据校验操作的密钥上下文
返回值
- 1:成功
- 0:失败
1 | int ret = EVP_PKEY_sign_init(ctx); |
5.2.4、EVP_PKEY_CTX_set_rsa_padding
用于设置 RSA 加密或解密操作的填充方式
1 | int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX *ctx, int pad); |
参数解释:
ctx:RSA 加密或解密操作的上下文(EVP_PKEY_CTX)。pad:要设置的加密填充(padding)方式,可以是以下值之一:RSA_PKCS1_PADDING:PKCS#1 填充方式,是最常见的 RSA 填充方式。RSA_PKCS1_OAEP_PADDING:PKCS#1 OAEP 填充方式,带有随机性质的填充方式,安全性更高。RSA_NO_PADDING:不进行填充操作,仅加密或解密数据。
设置签名时,不能使用RSA_PKCS1_OAPE_PADDING这种填充方式,因为OAEP 是一种概率性加密填充,每次加密同一明文会产生不同的密文,但签名需要确定性,这样才能保证相同数据的签名结果可以被验证
返回值
- 1:成功
- 0:失败
1 | ret = EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING); |
5.2.5、EVP_PKEY_CTX_set_signature_md
用于设置签名算法
1 | int EVP_PKEY_CTX_set_signature_md(EVP_PKEY_CTX *ctx, const EVP_MD *md); |
参数解释:
ctx:指向EVP_PKEY_CTX上下文结构的指针,用于设置签名算法。md:指向EVP_MD结构的指针,表示要使用的签名算法。
签名算法由 OpenSSL 中的 EVP_MD 结构表示,其中包含有关算法的信息,如名称、摘要长度等
返回值
- 1:成功
- 0:失败
1 | const QMap<QCryptographicHash::Algorithm, hashFunc> hashMethods = { |
5.2.6、EVP_PKEY_verify
使用非对称密钥进行签名操作的函数
1 | int EVP_PKEY_sign(EVP_PKEY_CTX *ctx, |
参数解释:
ctx:指向EVP_PKEY_CTX上下文结构的指针,表示签名操作的上下文。sig:指向缓冲区的指针,用于存储签名结果。siglen:指向sig缓冲区长度的指针,表示输入时表示sig缓冲区的长度,输出时表示实际写入sig的字节数。tbs:指向要签名的数据的指针。tbslen:要签名的数据的长度。
返回值:
- 1:成功
- 0:失败
1 | // 签名校验 |
5.2.7、EVP_PKEY_CTX_free
释放密钥对上下文对象
1 | void EVP_PKEY_CTX_free(EVP_PKEY_CTX *ctx); |
参数解释:
ctx:指向要释放内存的密钥对上下文对象的指针。
1 | // 释放上下文 |
完整示例
1 | bool RSACrypto::varify(const QByteArray& sign, |