使用 OpenSSL ECC 加密/解密文本字符串
Posted
技术标签:
【中文标题】使用 OpenSSL ECC 加密/解密文本字符串【英文标题】:Encrypting/decrypting text strings using OpenSSL ECC 【发布时间】:2010-11-12 06:13:58 【问题描述】:如何使用 OpenSSL 的 ECC 支持来加密或解密文本字符串?我可以使用 OpenSSL API 生成 ECC 私钥/公钥,但我不知道如何使用这些密钥加密纯文本。
【问题讨论】:
【参考方案1】:ECC 本身并没有真正定义任何加密/解密操作 - 建立在椭圆曲线上的算法可以。
一个例子是椭圆曲线 Diffie-Hellman。您可以通过以下方式使用 ECDH 加密消息:
-
生成临时 EC 密钥。
使用该密钥和接收者的公钥,使用 ECDH 生成密钥。
使用该密钥作为密钥,使用对称密码(如 AES)加密消息。
传输加密消息和步骤 1 中生成的临时公钥。
解密:
-
从消息中加载临时公钥。
使用该公钥与您的收件人私钥一起使用 ECDH 生成一个秘密。
使用该密钥作为密钥,使用对称密码解密消息。
编辑: 以下是使用 ECDH 生成密钥的基本思路。首先我们需要定义一个密钥派生函数——这个函数使用 SHA1 哈希。
void *KDF1_SHA1(const void *in, size_t inlen, void *out, size_t *outlen)
if (*outlen < SHA_DIGEST_LENGTH)
return NULL;
else
*outlen = SHA_DIGEST_LENGTH;
return SHA1(in, inlen, out);
这是发送方的 ECDH 代码。它假定收件人的公钥已经在“recip_key”中,并且您已经使用 EC_KEY_check_key() 对其进行了验证。为了简洁起见,它还省略了许多重要的错误检查,您肯定希望将其包含在生产代码中。
EC_KEY *ephemeral_key = NULL;
const EC_GROUP *group = NULL;
unsigned char buf[SHA_DIGEST_LENGTH] = 0 ;
group = EC_KEY_get0_group(recip_key);
ephemeral_key = EC_KEY_new();
EC_KEY_set_group(ephemeral_key, group);
EC_KEY_generate_key(ephemeral_key);
ECDH_compute_key(buf, sizeof buf, EC_KEY_get0_public_key(recip_key), ephemeral_key, KDF1_SHA1);
在此之后,缓冲区“buf”包含 20 个字节的可用于键控的材料。这个简短的示例基于 openssl 源代码分发中“ecdhtest.c”中的代码 - 我建议看一下。
您将希望将 ephemeral_key 的公钥部分与加密消息一起发送,并安全地丢弃私钥部分。数据上的 MAC 也是一个好主意,如果您需要超过 20 字节的密钥材料,则可能需要更长的哈希值。
收件人做了类似的事情,只是它的私钥已经存在(因为发件人必须事先知道相应的公钥),并且公钥是从发件人那里收到的。
【讨论】:
咖啡厅,感谢您的回复。我对你上面提到的步骤很好。我可以利用 AES 使用 ECDH 生成的秘密来加密消息。是否有执行上述步骤的示例程序?如果是这样,请指出我。我尝试了很多寻找这样的示例程序,但没有运气。谢谢 您能否指出更多关于如何在发送方/接收方之间生成秘密的参考资料(可能使用 OpenSSL)。谢谢。【参考方案2】:由于很难找到展示如何使用 ECC 加密数据的示例,我想我会发布一些代码供其他人使用。如需完整列表,请查看我的 openssl-dev 帖子:
http://www.mail-archive.com/openssl-dev@openssl.org/msg28042.html
基本上,它是如何使用 ECDH 保护数据块的可用版本。 ECDH 用于生成共享密钥。然后使用 SHA 512 对共享密钥进行哈希处理。将生成的 512 位拆分,其中 256 位用作对称密码的密钥(在我的示例中为 AES 256),其他 256 位用作 HMAC 的密钥。我的实现大致基于 SECG 工作组概述的 ECIES 标准。
关键函数是 ecies_encrypt(),它接受十六进制形式的公钥并返回加密数据:
secure_t * ecies_encrypt(char *key, unsigned char *data, size_t length)
void *body;
HMAC_CTX hmac;
int body_length;
secure_t *cryptex;
EVP_CIPHER_CTX cipher;
unsigned int mac_length;
EC_KEY *user, *ephemeral;
size_t envelope_length, block_length, key_length;
unsigned char envelope_key[SHA512_DIGEST_LENGTH], iv[EVP_MAX_IV_LENGTH], block[EVP_MAX_BLOCK_LENGTH];
// Simple sanity check.
if (!key || !data || !length)
printf("Invalid parameters passed in.\n");
return NULL;
// Make sure we are generating enough key material for the symmetric ciphers.
if ((key_length = EVP_CIPHER_key_length(ECIES_CIPHER)) * 2 > SHA512_DIGEST_LENGTH)
printf("The key derivation method will not produce enough envelope key material for the chosen ciphers. envelope = %i / required = %zu", SHA512_DIGEST_LENGTH / 8,
(key_length * 2) / 8);
return NULL;
// Convert the user's public key from hex into a full EC_KEY structure.
if (!(user = ecies_key_create_public_hex(key)))
printf("Invalid public key provided.\n");
return NULL;
// Create the ephemeral key used specifically for this block of data.
else if (!(ephemeral = ecies_key_create()))
printf("An error occurred while trying to generate the ephemeral key.\n");
EC_KEY_free(user);
return NULL;
// Use the intersection of the provided keys to generate the envelope data used by the ciphers below. The ecies_key_derivation() function uses
// SHA 512 to ensure we have a sufficient amount of envelope key material and that the material created is sufficiently secure.
else if (ECDH_compute_key(envelope_key, SHA512_DIGEST_LENGTH, EC_KEY_get0_public_key(user), ephemeral, ecies_key_derivation) != SHA512_DIGEST_LENGTH)
printf("An error occurred while trying to compute the envelope key. error = %s\n", ERR_error_string(ERR_get_error(), NULL));
EC_KEY_free(ephemeral);
EC_KEY_free(user);
return NULL;
// Determine the envelope and block lengths so we can allocate a buffer for the result.
else if ((block_length = EVP_CIPHER_block_size(ECIES_CIPHER)) == 0 || block_length > EVP_MAX_BLOCK_LENGTH || (envelope_length = EC_POINT_point2oct(EC_KEY_get0_group(
ephemeral), EC_KEY_get0_public_key(ephemeral), POINT_CONVERSION_COMPRESSED, NULL, 0, NULL)) == 0)
printf("Invalid block or envelope length. block = %zu / envelope = %zu\n", block_length, envelope_length);
EC_KEY_free(ephemeral);
EC_KEY_free(user);
return NULL;
// We use a conditional to pad the length if the input buffer is not evenly divisible by the block size.
else if (!(cryptex = secure_alloc(envelope_length, EVP_MD_size(ECIES_HASHER), length, length + (length % block_length ? (block_length - (length % block_length)) : 0))))
printf("Unable to allocate a secure_t buffer to hold the encrypted result.\n");
EC_KEY_free(ephemeral);
EC_KEY_free(user);
return NULL;
// Store the public key portion of the ephemeral key.
else if (EC_POINT_point2oct(EC_KEY_get0_group(ephemeral), EC_KEY_get0_public_key(ephemeral), POINT_CONVERSION_COMPRESSED, secure_key_data(cryptex), envelope_length,
NULL) != envelope_length)
printf("An error occurred while trying to record the public portion of the envelope key. error = %s\n", ERR_error_string(ERR_get_error(), NULL));
EC_KEY_free(ephemeral);
EC_KEY_free(user);
secure_free(cryptex);
return NULL;
// The envelope key has been stored so we no longer need to keep the keys around.
EC_KEY_free(ephemeral);
EC_KEY_free(user);
// For now we use an empty initialization vector.
memset(iv, 0, EVP_MAX_IV_LENGTH);
// Setup the cipher context, the body length, and store a pointer to the body buffer location.
EVP_CIPHER_CTX_init(&cipher);
body = secure_body_data(cryptex);
body_length = secure_body_length(cryptex);
// Initialize the cipher with the envelope key.
if (EVP_EncryptInit_ex(&cipher, ECIES_CIPHER, NULL, envelope_key, iv) != 1 || EVP_CIPHER_CTX_set_padding(&cipher, 0) != 1 || EVP_EncryptUpdate(&cipher, body,
&body_length, data, length - (length % block_length)) != 1)
printf("An error occurred while trying to secure the data using the chosen symmetric cipher. error = %s\n", ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(&cipher);
secure_free(cryptex);
return NULL;
// Check whether all of the data was encrypted. If they don't match up, we either have a partial block remaining, or an error occurred.
else if (body_length != length)
// Make sure all that remains is a partial block, and their wasn't an error.
if (length - body_length >= block_length)
printf("Unable to secure the data using the chosen symmetric cipher. error = %s\n", ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(&cipher);
secure_free(cryptex);
return NULL;
// Copy the remaining data into our partial block buffer. The memset() call ensures any extra bytes will be zero'ed out.
memset(block, 0, EVP_MAX_BLOCK_LENGTH);
memcpy(block, data + body_length, length - body_length);
// Advance the body pointer to the location of the remaining space, and calculate just how much room is still available.
body += body_length;
if ((body_length = secure_body_length(cryptex) - body_length) < 0)
printf("The symmetric cipher overflowed!\n");
EVP_CIPHER_CTX_cleanup(&cipher);
secure_free(cryptex);
return NULL;
// Pass the final partially filled data block into the cipher as a complete block. The padding will be removed during the decryption process.
else if (EVP_EncryptUpdate(&cipher, body, &body_length, block, block_length) != 1)
printf("Unable to secure the data using the chosen symmetric cipher. error = %s\n", ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(&cipher);
secure_free(cryptex);
return NULL;
// Advance the pointer, then use pointer arithmetic to calculate how much of the body buffer has been used. The complex logic is needed so that we get
// the correct status regardless of whether there was a partial data block.
body += body_length;
if ((body_length = secure_body_length(cryptex) - (body - secure_body_data(cryptex))) < 0)
printf("The symmetric cipher overflowed!\n");
EVP_CIPHER_CTX_cleanup(&cipher);
secure_free(cryptex);
return NULL;
else if (EVP_EncryptFinal_ex(&cipher, body, &body_length) != 1)
printf("Unable to secure the data using the chosen symmetric cipher. error = %s\n", ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(&cipher);
secure_free(cryptex);
return NULL;
EVP_CIPHER_CTX_cleanup(&cipher);
// Generate an authenticated hash which can be used to validate the data during decryption.
HMAC_CTX_init(&hmac);
mac_length = secure_mac_length(cryptex);
// At the moment we are generating the hash using encrypted data. At some point we may want to validate the original text instead.
if (HMAC_Init_ex(&hmac, envelope_key + key_length, key_length, ECIES_HASHER, NULL) != 1 || HMAC_Update(&hmac, secure_body_data(cryptex), secure_body_length(cryptex))
!= 1 || HMAC_Final(&hmac, secure_mac_data(cryptex), &mac_length) != 1)
printf("Unable to generate a data authentication code. error = %s\n", ERR_error_string(ERR_get_error(), NULL));
HMAC_CTX_cleanup(&hmac);
secure_free(cryptex);
return NULL;
HMAC_CTX_cleanup(&hmac);
return cryptex;
而 ecies_decrypt() 再次以十六进制形式获取私钥,并解密先前安全的缓冲区:
unsigned char * ecies_decrypt(char *key, secure_t *cryptex, size_t *length)
HMAC_CTX hmac;
size_t key_length;
int output_length;
EVP_CIPHER_CTX cipher;
EC_KEY *user, *ephemeral;
unsigned int mac_length = EVP_MAX_MD_SIZE;
unsigned char envelope_key[SHA512_DIGEST_LENGTH], iv[EVP_MAX_IV_LENGTH], md[EVP_MAX_MD_SIZE], *block, *output;
// Simple sanity check.
if (!key || !cryptex || !length)
printf("Invalid parameters passed in.\n");
return NULL;
// Make sure we are generating enough key material for the symmetric ciphers.
else if ((key_length = EVP_CIPHER_key_length(ECIES_CIPHER)) * 2 > SHA512_DIGEST_LENGTH)
printf("The key derivation method will not produce enough envelope key material for the chosen ciphers. envelope = %i / required = %zu", SHA512_DIGEST_LENGTH / 8,
(key_length * 2) / 8);
return NULL;
// Convert the user's public key from hex into a full EC_KEY structure.
else if (!(user = ecies_key_create_private_hex(key)))
printf("Invalid private key provided.\n");
return NULL;
// Create the ephemeral key used specifically for this block of data.
else if (!(ephemeral = ecies_key_create_public_octets(secure_key_data(cryptex), secure_key_length(cryptex))))
printf("An error occurred while trying to recreate the ephemeral key.\n");
EC_KEY_free(user);
return NULL;
// Use the intersection of the provided keys to generate the envelope data used by the ciphers below. The ecies_key_derivation() function uses
// SHA 512 to ensure we have a sufficient amount of envelope key material and that the material created is sufficiently secure.
else if (ECDH_compute_key(envelope_key, SHA512_DIGEST_LENGTH, EC_KEY_get0_public_key(ephemeral), user, ecies_key_derivation) != SHA512_DIGEST_LENGTH)
printf("An error occurred while trying to compute the envelope key. error = %s\n", ERR_error_string(ERR_get_error(), NULL));
EC_KEY_free(ephemeral);
EC_KEY_free(user);
return NULL;
// The envelope key material has been extracted, so we no longer need the user and ephemeral keys.
EC_KEY_free(ephemeral);
EC_KEY_free(user);
// Use the authenticated hash of the ciphered data to ensure it was not modified after being encrypted.
HMAC_CTX_init(&hmac);
// At the moment we are generating the hash using encrypted data. At some point we may want to validate the original text instead.
if (HMAC_Init_ex(&hmac, envelope_key + key_length, key_length, ECIES_HASHER, NULL) != 1 || HMAC_Update(&hmac, secure_body_data(cryptex), secure_body_length(cryptex))
!= 1 || HMAC_Final(&hmac, md, &mac_length) != 1)
printf("Unable to generate the authentication code needed for validation. error = %s\n", ERR_error_string(ERR_get_error(), NULL));
HMAC_CTX_cleanup(&hmac);
return NULL;
HMAC_CTX_cleanup(&hmac);
// We can use the generated hash to ensure the encrypted data was not altered after being encrypted.
if (mac_length != secure_mac_length(cryptex) || memcmp(md, secure_mac_data(cryptex), mac_length))
printf("The authentication code was invalid! The ciphered data has been corrupted!\n");
return NULL;
// Create a buffer to hold the result.
output_length = secure_body_length(cryptex);
if (!(block = output = malloc(output_length + 1)))
printf("An error occurred while trying to allocate memory for the decrypted data.\n");
return NULL;
// For now we use an empty initialization vector. We also clear out the result buffer just to be on the safe side.
memset(iv, 0, EVP_MAX_IV_LENGTH);
memset(output, 0, output_length + 1);
EVP_CIPHER_CTX_init(&cipher);
// Decrypt the data using the chosen symmetric cipher.
if (EVP_DecryptInit_ex(&cipher, ECIES_CIPHER, NULL, envelope_key, iv) != 1 || EVP_CIPHER_CTX_set_padding(&cipher, 0) != 1 || EVP_DecryptUpdate(&cipher, block,
&output_length, secure_body_data(cryptex), secure_body_length(cryptex)) != 1)
printf("Unable to decrypt the data using the chosen symmetric cipher. error = %s\n", ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(&cipher);
free(output);
return NULL;
block += output_length;
if ((output_length = secure_body_length(cryptex) - output_length) != 0)
printf("The symmetric cipher failed to properly decrypt the correct amount of data!\n");
EVP_CIPHER_CTX_cleanup(&cipher);
free(output);
return NULL;
if (EVP_DecryptFinal_ex(&cipher, block, &output_length) != 1)
printf("Unable to decrypt the data using the chosen symmetric cipher. error = %s\n", ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(&cipher);
free(output);
return NULL;
EVP_CIPHER_CTX_cleanup(&cipher);
*length = secure_orig_length(cryptex);
return output;
我发布此内容是因为我个人找不到任何其他有关如何使用 ECC 和 OpenSSL 库保护文件的示例。也就是说,值得一提的是不使用 OpenSSL 的替代方案。一个是安全的,它遵循类似于我的示例的模式,只是它依赖于 libgcrypt。由于 libgcrypt 不提供所需的所有底层 ECC 功能,因此安全程序填补了空白并实现了 libgcrypt 中缺少的 ECC 逻辑。
另一个值得一看的程序是 SKS,它使用与上面示例类似的基于 ECC 的加密过程,但没有任何外部依赖项(因此所有 ECC 代码都在那里供您查看)。
【讨论】:
这是用什么语言写的?以上是关于使用 OpenSSL ECC 加密/解密文本字符串的主要内容,如果未能解决你的问题,请参考以下文章