公钥加密,或者非对称加密,它需要两个密钥,一个是公开密钥,另一个是私有密钥;一个用作加密,另一个则用作解密。使用其中一个密钥把明文加密后所得的密文,只能用相对应的另一个密钥才能解密得到原本的明文。虽然两个密钥在数学上相关,但如果知道了其中一个,并不能凭此计算出另外一个。这些密钥的生成依赖于基于数学问题的密码算法来生成单向函数,如果要确保安全,那么只需要保密私钥,公钥可以在不影响安全性的情况下公开分发。
在这样的系统中,任何人都可以使用接收者的公钥加密消息,但是加密的消息只能用接收者的私钥解密。基于公开密钥加密的特性,它还提供数字签名的功能。本文主要介绍公钥加密的功能,而数字签名会在后面的文章介绍。
RSA
RSA
加密是RSA Data Security
开发的公钥加密技术。RSA
算法基于分解非常大的数字的难度。基于此原理,RSA
加密算法使用素数分解作为加密的陷阱门,换言之,对一极大整数做因数分解愈困难,RSA
算法愈可靠。因此,推导RSA
密钥需要大量的时间和处理能力。RSA
是重要数据的标准加密方法,尤其是通过Internet
传输的数据。RSA
代表该技术的创造者,Rivest
,Shamir
和Adelman
。
加密和解密:OpenSSL
命令行
由于RSA
算法的特性,因此一般不加密大型文件。RSA
加密的最有效使用是使用公钥加密随机生成的对称密码,然后使用对称加密密码加密文件。然后我们将加密文件和加密密钥发送给另一方,然后可以用他们的私钥解密密钥,使用该密钥解密大文件。
使用RSA
密钥时,以下命令是相关的:
-
openssl genrsa
:生成RSA
私钥。 -
openssl rsa
:管理RSA
私钥(包括从中生成公钥)。 -
openssl rsautl
:使用RSA
密钥加密和解密文件。
生成RSA
私钥:
openssl genrsa -out privatekey.pem
根据私钥生成证书签名请求:
openssl req -new -key privatekey.pem -out csr.pem
如果是自己做测试,那么证书的申请机构和颁发机构都是自己。就可以用下面这个命令来生成证书certificate.pem
:
openssl req -new -x509 -key privatekey.pem -out certificate.pem -days 1095
把certificate.pem
发送给对方,对方使用如下命令获取公钥:
openssl x509 -pubkey -in certificate.pem -noout > publickey.pem
生成随机密钥:
openssl rand -hex 64 > key.bin
使用随机密钥加密大文件:
openssl enc -aes-256-cbc -salt -in largefile.pdf -out largefile.pdf.enc -pass file:./bin.key
使用公钥文件加密随机密钥:
openssl rsautl -encrypt -inkey publickey.pem -pubin -in key.bin -out key.bin.enc
对方可以安全地发送key.bin.enc
和largefile.pdf.enc
给你。你需要用以下命令和私钥来解密随机密钥:
openssl rsautl -decrypt -inkey privatekey.pem -in key.bin.enc -out key.bin
获得随机密钥后,可以使用解密密钥解密加密文件:
openssl enc -d -aes-256-cbc -in largefile.pdf.enc -out largefile.pdf -pass file:./bin.key
使用OpenSSL API
加密和解密
使用非对称密钥的加密和解密在计算上是非常“昂贵”的。通常,消息不是使用这样的密钥直接加密,而是使用对称的“会话”密钥加密。然后使用公钥对该密钥进行加密。在OpenSSL
中,这种组合称为信封(envelope
)。
加密信封
使用EVP_Seal*
函数集密封信封,操作包括以下步骤:
- 初始化上下文
- 初始化密封操作,提供将要使用的对称密码,以及用于加密会话密钥的公钥集。
- 提供要加密的消息。
- 完成加密操作
下面是示例代码:
int envelope_seal(EVP_PKEY **pub_key, unsigned char *plaintext, int plaintext_len,
unsigned char **encrypted_key, int *encrypted_key_len, unsigned char *iv,
unsigned char *ciphertext)
{
EVP_CIPHER_CTX *ctx;
int ciphertext_len;
int len;
/* Create and initialise the context */
if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors();
/* Initialise the envelope seal operation. This operation generates
* a key for the provided cipher, and then encrypts that key a number
* of times (one for each public key provided in the pub_key array). In
* this example the array size is just one. This operation also
* generates an IV and places it in iv. */
if(1 != EVP_SealInit(ctx, EVP_aes_256_cbc(), encrypted_key,
encrypted_key_len, iv, pub_key, 1))
handleErrors();
/* Provide the message to be encrypted, and obtain the encrypted output.
* EVP_SealUpdate can be called multiple times if necessary
*/
if(1 != EVP_SealUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
handleErrors();
ciphertext_len = len;
/* Finalise the encryption. Further ciphertext bytes may be written at
* this stage.
*/
if(1 != EVP_SealFinal(ctx, ciphertext + len, &len)) handleErrors();
ciphertext_len += len;
/* Clean up */
EVP_CIPHER_CTX_free(ctx);
return ciphertext_len;
}
解密信封
使用以下步骤中的EVP_Open*
函数集打开信封
- 初始化上下文
- 初始化打开操作,提供已使用的对称密码,以及用于解密会话密钥的私钥。
- 使用会话密钥提供要解密和解密的消息
- 完成解密操作
下面是示例代码:
int envelope_open(EVP_PKEY *priv_key, unsigned char *ciphertext, int ciphertext_len,
unsigned char *encrypted_key, int encrypted_key_len, unsigned char *iv,
unsigned char *plaintext)
{
EVP_CIPHER_CTX *ctx;
int len;
int plaintext_len;
/* Create and initialise the context */
if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors();
/* Initialise the decryption operation. The asymmetric private key is
* provided and priv_key, whilst the encrypted session key is held in
* encrypted_key */
if(1 != EVP_OpenInit(ctx, EVP_aes_256_cbc(), encrypted_key,
encrypted_key_len, iv, priv_key))
handleErrors();
/* Provide the message to be decrypted, and obtain the plaintext output.
* EVP_OpenUpdate can be called multiple times if necessary
*/
if(1 != EVP_OpenUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
handleErrors();
plaintext_len = len;
/* Finalise the decryption. Further plaintext bytes may be written at
* this stage.
*/
if(1 != EVP_OpenFinal(ctx, plaintext + len, &len)) handleErrors();
plaintext_len += len;
/* Clean up */
EVP_CIPHER_CTX_free(ctx);
return plaintext_len;
}