公钥加密,或者非对称加密,它需要两个密钥,一个是公开密钥,另一个是私有密钥;一个用作加密,另一个则用作解密。使用其中一个密钥把明文加密后所得的密文,只能用相对应的另一个密钥才能解密得到原本的明文。虽然两个密钥在数学上相关,但如果知道了其中一个,并不能凭此计算出另外一个。这些密钥的生成依赖于基于数学问题的密码算法来生成单向函数,如果要确保安全,那么只需要保密私钥,公钥可以在不影响安全性的情况下公开分发。

在这样的系统中,任何人都可以使用接收者的公钥加密消息,但是加密的消息只能用接收者的私钥解密。基于公开密钥加密的特性,它还提供数字签名的功能。本文主要介绍公钥加密的功能,而数字签名会在后面的文章介绍。

RSA

RSA加密是RSA Data Security开发的公钥加密技术。RSA算法基于分解非常大的数字的难度。基于此原理,RSA加密算法使用素数分解作为加密的陷阱门,换言之,对一极大整数做因数分解愈困难,RSA算法愈可靠。因此,推导RSA密钥需要大量的时间和处理能力。RSA是重要数据的标准加密方法,尤其是通过Internet传输的数据。RSA代表该技术的创造者,RivestShamirAdelman

加密和解密: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.enclargefile.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;
}