Is it possible to use ECC private key for encryption? [closed] - c

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 5 years ago.
Improve this question
I want to use an ECC private key for encryption because of mutual authentication.
But I cannot find a method (or function) in wolfcrypt (wolfSSL) or the micro-ecc library.
Is is okay to use an (ECC) private key for encryption?
And if so, how can I do this?

To address the initial question "Is it possible to use ECC private key for encryption?"
Typically users maintain a private key and share their public key. The public key is mathematically entangled with the private key so messages encrypted with the public key can be decrypted with the private key. In this way the private key remains exactly that, private! It is not recommended to use a private key for encrypting ever. If you can justify your reason for wishing to do this by all means please explain. Happy to hear your reasoning. If however you are learning and just unsure then please avoid doing what you asked.
To address the statement about private key encryption for mutual authentication... I'm not sure how that will help to provide mutual authentication and I am genuinely curious what #comomind was talking about there. Here is a link about how SSL/TLS can solve the problem of mutual authentication:
(https://www.codeproject.com/Articles/326574/An-Introduction-to-Mutual-SSL-Authentication)
For the second part of the question where wolfCrypt is concerned, the method (or function) in wolfSSL's wolfCrypt library is:
wc_ecc_encrypt
Here is the section from the manual describing this API and the arguments:
(https://www.wolfssl.com/wolfSSL/Docs-wolfssl-manual-18-14-wolfcrypt-api-ecc.html)
wc_ecc_encrypt
Synopsis:
include header: wolfssl/wolfcrypt/ecc.h
int wc_ecc_encrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg,
word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx);
Description: This function encrypts the given input message from msg
to out. This function takes an optional ctx object as parameter. When
supplied, encryption proceeds based on the ecEncCtx's encAlgo,
kdfAlgo, and macAlgo. If ctx is not supplied, processing completes
with the default algorithms, ecAES_128_CBC, ecHKDF_SHA256 and
ecHMAC_SHA256.
This function requires that the messages are padded according to the
encryption type specified by ctx.
Return Values: 0: Returned upon successfully encrypting the input
message BAD_FUNC_ARG: Returned if privKey, pubKey, msg, msgSz, out, or
outSz are NULL, or the ctx object specifies an unsupported encryption
type BAD_ENC_STATE_E: Returned if the ctx object given is in a state
that is not appropriate for encryption BUFFER_E: Returned if the
supplied output buffer is too small to store the encrypted ciphertext
MEMORY_E: Returned if there is an error allocating memory for the
shared secret key
Parameters: privKey - pointer to the ecc_key object containing the
private key to use for encryption pubKey - pointer to the ecc_key
object containing the public key of the peer with whom one wishes to
communicate msg- pointer to the buffer holding the message to encrypt
msgSz - size of the buffer to encrypt out - pointer to the buffer in
which to store the encrypted ciphertext outSz - pointer to a word32
object containing the available size in the out buffer. Upon
successfully encrypting the message, holds the number of bytes written
to the output buffer ctx - Optional: pointer to an ecEncCtx object
specifying different encryption algorithms to use
Example:
byte msg[] = { /* initialize with msg to encrypt. Ensure
padded to block size */ };
byte out[sizeof(msg)]; word32 outSz = sizeof(out);
int ret;
ecc_key cli, serv; // initialize cli with
private key // initialize serv with received public key
ecEncCtx* cliCtx, servCtx; // initialize cliCtx and servCtx //
exchange salts
ret = wc_ecc_encrypt(&cli, &serv, msg, sizeof(msg), out, &outSz,
cliCtx);
if(ret != 0) { // error encrypting message }
See Also: wc_ecc_decrypt
Notice this API takes in both a public and private key, where the public key came from the peer and the private key is your own private key. This API will then use ECDH to generate a shared secret. The shared secret is what should be used for encrypting and decrypting. The party you are trying to talk to will also take your public key and his private key to generate his own shared-secret key. The shared secret is never sent over the wire by either party. See more on the topic of shared secret here: (https://crypto.stackexchange.com/questions/21169/how-does-ecdh-arrive-on-a-shared-secret)
TEST CASE:
(https://github.com/wolfSSL/wolfssl/blob/master/wolfcrypt/test/test.c#L8701)
#ifdef HAVE_ECC_ENCRYPT
int ecc_encrypt_test(void)
{
WC_RNG rng;
int ret;
ecc_key userA, userB;
byte msg[48];
byte plain[48];
byte out[80];
word32 outSz = sizeof(out);
word32 plainSz = sizeof(plain);
int i;
ret = wc_InitRng(&rng);
if (ret != 0)
return -3001;
wc_ecc_init(&userA);
wc_ecc_init(&userB);
ret = wc_ecc_make_key(&rng, 32, &userA);
ret += wc_ecc_make_key(&rng, 32, &userB);
if (ret != 0)
return -3002;
for (i = 0; i < 48; i++)
msg[i] = i;
/* encrypt msg to send to userB */
ret = wc_ecc_encrypt(&userA, &userB, msg, sizeof(msg), out, &outSz, NULL);
if (ret != 0)
return -3003;
/* userB decrypt the message received from userA */
ret = wc_ecc_decrypt(&userB, &userA, out, outSz, plain, &plainSz, NULL);
if (ret != 0)
return -3004;
if (XMEMCMP(plain, msg, sizeof(msg)) != 0)
return -3005;
{ /* let's verify message exchange works, A is client, B is server */
ecEncCtx* cliCtx = wc_ecc_ctx_new(REQ_RESP_CLIENT, &rng);
ecEncCtx* srvCtx = wc_ecc_ctx_new(REQ_RESP_SERVER, &rng);
byte cliSalt[EXCHANGE_SALT_SZ];
byte srvSalt[EXCHANGE_SALT_SZ];
const byte* tmpSalt;
if (cliCtx == NULL || srvCtx == NULL)
return -3006;
/* get salt to send to peer */
tmpSalt = wc_ecc_ctx_get_own_salt(cliCtx);
if (tmpSalt == NULL)
return -3007;
XMEMCPY(cliSalt, tmpSalt, EXCHANGE_SALT_SZ);
tmpSalt = wc_ecc_ctx_get_own_salt(srvCtx);
if (tmpSalt == NULL)
return -3007;
XMEMCPY(srvSalt, tmpSalt, EXCHANGE_SALT_SZ);
/* in actual use, we'd get the peer's salt over the transport */
ret = wc_ecc_ctx_set_peer_salt(cliCtx, srvSalt);
ret += wc_ecc_ctx_set_peer_salt(srvCtx, cliSalt);
ret += wc_ecc_ctx_set_info(cliCtx, (byte*)"wolfSSL MSGE", 11);
ret += wc_ecc_ctx_set_info(srvCtx, (byte*)"wolfSSL MSGE", 11);
if (ret != 0)
return -3008;
/* get encrypted msg (request) to send to B */
outSz = sizeof(out);
ret = wc_ecc_encrypt(&userA, &userB, msg, sizeof(msg), out, &outSz,cliCtx);
if (ret != 0)
return -3009;
/* B decrypts msg (request) from A */
plainSz = sizeof(plain);
ret = wc_ecc_decrypt(&userB, &userA, out, outSz, plain, &plainSz, srvCtx);
if (ret != 0)
return -3010;
if (XMEMCMP(plain, msg, sizeof(msg)) != 0)
return -3011;
{
/* msg2 (response) from B to A */
byte msg2[48];
byte plain2[48];
byte out2[80];
word32 outSz2 = sizeof(out2);
word32 plainSz2 = sizeof(plain2);
for (i = 0; i < 48; i++)
msg2[i] = i+48;
/* get encrypted msg (response) to send to B */
ret = wc_ecc_encrypt(&userB, &userA, msg2, sizeof(msg2), out2,
&outSz2, srvCtx);
if (ret != 0)
return -3012;
/* A decrypts msg (response) from B */
ret = wc_ecc_decrypt(&userA, &userB, out2, outSz2, plain2, &plainSz2,
cliCtx);
if (ret != 0)
return -3013;
if (XMEMCMP(plain2, msg2, sizeof(msg2)) != 0)
return -3014;
}
/* cleanup */
wc_ecc_ctx_free(srvCtx);
wc_ecc_ctx_free(cliCtx);
}
/* cleanup */
wc_ecc_free(&userB);
wc_ecc_free(&userA);
wc_FreeRng(&rng);
return 0;
}
#endif /* HAVE_ECC_ENCRYPT */

I want to use an ECC private key for encryption because of mutual authentication. But I cannot find a method (or function) in wolfcrypt (wolfSSL) or the micro-ecc library.
There's a subtle difference in the definitions and operations. "Encrypting with the private key" is not a valid cryptographic operation. Usually what you want is a Signature Scheme with Recovery.
When libraries like OpenSSL or wolfSSL sign a message, they often apply a formatting function and then treat the resulting message as an instance of ciphertext. Then, the private key is used to decrypt the ciphertext.
Is is okay to use an (ECC) private key for encryption?
Probably not, but we need to hear more about what you want to do.
If you want help with designing the scheme, then you should probably visit Cryptography Stack Exchange or Information Security Stack Exchange.
[If its OK] And if so, how can I do this?
Again, it depends on what you are doing. At some point, you will need to treat a formatted message as an instance of ciphertext. I can't say anymore because I am not familiar with wolfSSL.
You might also be interested in Should we sign-then-encrypt, or encrypt-then-sign? on the Crypto.SE. It appears to have some intersection with what you are doing.

Related

OpenSSL ECDSA signature validity

From OpenSSL documentation
Creating an ECDSA signature of a given SHA-256 hash value using the named curve prime256v1 (aka P-256).
Second step: compute the ECDSA signature of a SHA-256 hash value using ECDSA_do_sign():
sig = ECDSA_do_sign(digest, 32, eckey);
if (sig == NULL) {
/* error */
}
or using ECDSA_sign():
unsigned char *buffer, *pp;
int buf_len;
buf_len = ECDSA_size(eckey);
buffer = OPENSSL_malloc(buf_len);
pp = buffer;
if (ECDSA_sign(0, dgst, dgstlen, pp, &buf_len, eckey) == 0) {
/* error */
}
Third step: verify the created ECDSA signature using ECDSA_do_verify():
ret = ECDSA_do_verify(digest, 32, sig, eckey);
or using ECDSA_verify():
ret = ECDSA_verify(0, digest, 32, buffer, buf_len, eckey);
and finally evaluate the return value:
if (ret == 1) {
/* signature ok */
} else if (ret == 0) {
/* incorrect signature */
} else {
/* error */
}
EOF
This brings me to an understanding that I need to verify every signature I create with ECDSA_do_sign or ECDSA_sign, do I? Can it happen that a created signature is not valid?
From NIST PUB 186-4 - Digital Signature Standard section 4.7:
Signature verification may be performed by any party (i.e., the signatory, the intended recipient or any other party) using the signatory’s public key. A signatory may wish to verify that the computed signature is correct, perhaps before sending the signed message to the intended recipient. The intended recipient (or any other party) verifies the signature to determine its
authenticity.
(where the signatory is the signature creator)

How to chain BCryptEncrypt and BCryptDecrypt calls using AES in GCM mode?

Using the Windows CNG API, I am able to encrypt and decrypt individual blocks of data with authentication, using AES in GCM mode. I now want to encrypt and decrypt multiple buffers in a row.
According to documentation for CNG, the following scenario is supported:
If the input to encryption or decryption is scattered across multiple
buffers, then you must chain calls to the BCryptEncrypt and
BCryptDecrypt functions. Chaining is indicated by setting the
BCRYPT_AUTH_MODE_IN_PROGRESS_FLAG flag in the dwFlags member.
If I understand it correctly, this means that I can invoke BCryptEncrypt sequentially on multiple buffers an obtain the authentication tag for the combined buffers at the end. Similarly, I can invoke BCryptDecrypt sequentially on multiple buffers while deferring the actual authentication check until the end. I can not get that to work though, it looks like the value for dwFlags is ignored. Whenever I use BCRYPT_AUTH_MODE_IN_PROGRESS_FLAG, I get a return value of 0xc000a002 , which is equal to STATUS_AUTH_TAG_MISMATCH as defined in ntstatus.h.
Even though the parameter pbIV is marked as in/out, the elements pointed to by the parameter pbIV do not get modified by BCryptEncrypt(). Is that expected? I also looked at the field pbNonce in the BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO structure, pointed to by the pPaddingInfo pointer, but that one does not get modified either. I also tried "manually" advancing the IV, modifying the contents myself according to the counter scheme, but that did not help either.
What is the right procedure to chain the BCryptEncrypt and/or BCryptDecrypt functions successfully?
I managed to get it to work. It seems that the problem is in MSDN, it should mention setting BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG instead of BCRYPT_AUTH_MODE_IN_PROGRESS_FLAG.
#include <windows.h>
#include <assert.h>
#include <vector>
#include <Bcrypt.h>
#pragma comment(lib, "bcrypt.lib")
std::vector<BYTE> MakePatternBytes(size_t a_Length)
{
std::vector<BYTE> result(a_Length);
for (size_t i = 0; i < result.size(); i++)
{
result[i] = (BYTE)i;
}
return result;
}
std::vector<BYTE> MakeRandomBytes(size_t a_Length)
{
std::vector<BYTE> result(a_Length);
for (size_t i = 0; i < result.size(); i++)
{
result[i] = (BYTE)rand();
}
return result;
}
int _tmain(int argc, _TCHAR* argv[])
{
NTSTATUS bcryptResult = 0;
DWORD bytesDone = 0;
BCRYPT_ALG_HANDLE algHandle = 0;
bcryptResult = BCryptOpenAlgorithmProvider(&algHandle, BCRYPT_AES_ALGORITHM, 0, 0);
assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptOpenAlgorithmProvider");
bcryptResult = BCryptSetProperty(algHandle, BCRYPT_CHAINING_MODE, (BYTE*)BCRYPT_CHAIN_MODE_GCM, sizeof(BCRYPT_CHAIN_MODE_GCM), 0);
assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptSetProperty(BCRYPT_CHAINING_MODE)");
BCRYPT_AUTH_TAG_LENGTHS_STRUCT authTagLengths;
bcryptResult = BCryptGetProperty(algHandle, BCRYPT_AUTH_TAG_LENGTH, (BYTE*)&authTagLengths, sizeof(authTagLengths), &bytesDone, 0);
assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptGetProperty(BCRYPT_AUTH_TAG_LENGTH)");
DWORD blockLength = 0;
bcryptResult = BCryptGetProperty(algHandle, BCRYPT_BLOCK_LENGTH, (BYTE*)&blockLength, sizeof(blockLength), &bytesDone, 0);
assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptGetProperty(BCRYPT_BLOCK_LENGTH)");
BCRYPT_KEY_HANDLE keyHandle = 0;
{
const std::vector<BYTE> key = MakeRandomBytes(blockLength);
bcryptResult = BCryptGenerateSymmetricKey(algHandle, &keyHandle, 0, 0, (PUCHAR)&key[0], key.size(), 0);
assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptGenerateSymmetricKey");
}
const size_t GCM_NONCE_SIZE = 12;
const std::vector<BYTE> origNonce = MakeRandomBytes(GCM_NONCE_SIZE);
const std::vector<BYTE> origData = MakePatternBytes(256);
// Encrypt data as a whole
std::vector<BYTE> encrypted = origData;
std::vector<BYTE> authTag(authTagLengths.dwMinLength);
{
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
BCRYPT_INIT_AUTH_MODE_INFO(authInfo);
authInfo.pbNonce = (PUCHAR)&origNonce[0];
authInfo.cbNonce = origNonce.size();
authInfo.pbTag = &authTag[0];
authInfo.cbTag = authTag.size();
bcryptResult = BCryptEncrypt
(
keyHandle,
&encrypted[0], encrypted.size(),
&authInfo,
0, 0,
&encrypted[0], encrypted.size(),
&bytesDone, 0
);
assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptEncrypt");
assert(bytesDone == encrypted.size());
}
// Decrypt data in two parts
std::vector<BYTE> decrypted = encrypted;
{
DWORD partSize = decrypted.size() / 2;
std::vector<BYTE> macContext(authTagLengths.dwMaxLength);
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
BCRYPT_INIT_AUTH_MODE_INFO(authInfo);
authInfo.pbNonce = (PUCHAR)&origNonce[0];
authInfo.cbNonce = origNonce.size();
authInfo.pbTag = &authTag[0];
authInfo.cbTag = authTag.size();
authInfo.pbMacContext = &macContext[0];
authInfo.cbMacContext = macContext.size();
// IV value is ignored on first call to BCryptDecrypt.
// This buffer will be used to keep internal IV used for chaining.
std::vector<BYTE> contextIV(blockLength);
// First part
authInfo.dwFlags = BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG;
bcryptResult = BCryptDecrypt
(
keyHandle,
&decrypted[0*partSize], partSize,
&authInfo,
&contextIV[0], contextIV.size(),
&decrypted[0*partSize], partSize,
&bytesDone, 0
);
assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptDecrypt");
assert(bytesDone == partSize);
// Second part
authInfo.dwFlags &= ~BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG;
bcryptResult = BCryptDecrypt
(
keyHandle,
&decrypted[1*partSize], partSize,
&authInfo,
&contextIV[0], contextIV.size(),
&decrypted[1*partSize], partSize,
&bytesDone, 0
);
assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptDecrypt");
assert(bytesDone == partSize);
}
// Check decryption
assert(decrypted == origData);
// Cleanup
BCryptDestroyKey(keyHandle);
BCryptCloseAlgorithmProvider(algHandle, 0);
return 0;
}
#Codeguard's answer got me through the project I was working on which lead me to find this question/answer in the first place; however, there were still a number of gotchas I struggled with. Below is the process I followed with the tricky parts called out. You can view the actual code at the link above:
Use BCryptOpenAlgorithmProvider to open the algorithm provider using BCRYPT_AES_ALGORITHM.
Use BCryptSetProperty to set the BCRYPT_CHAINING_MODE to BCRYPT_CHAIN_MODE_GCM.
Use BCryptGetProperty to get the BCRYPT_OBJECT_LENGTH to allocate for use by the BCrypt library for the encrypt/decrypt operation. Depending on your implementation, you may also want to:
Use BCryptGetProperty to determine BCRYPT_BLOCK_SIZE and allocate scratch space for the IV. The Windows API updates the IV with each call, and the caller is responsible for providing the memory for that usage.
Use BCryptGetProperty to determine BCRYPT_AUTH_TAG_LENGTH and allocate scratch space for the largest possible tag. Like the IV, the caller is responsible for providing this space, which the API updates each time.
Initialize the BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO struct:
Initialize the structure with BCRYPT_INIT_AUTH_MODE_INFO()
Initialize the pbNonce and cbNonce field. Note that for the first call to BCryptEncrypt/BCryptDecrypt, the IV is ignored as an input and this field is used as the "IV". However, the IV parameter will be updated by that first call and used by subsequent calls, so space for it must still be provided. In addition, the pbNonce and cbNonce fields must remain set (even though they are unused after the first call) for all calls to BCryptEncrypt/BCryptDecrypt or those calls will complain.
Initialize pbAuthData and cbAuthData. In my project, I set these fields just before the first call to BCryptEncrypt/BCryptDecrypt and immediately reset them to NULL/0 immediately afterward. You can pass NULL/0 as the input and output parameters during these calls.
Initialize pbTag and cbTag. pbTag can be NULL until the final call to BCryptEncrypt/BCryptDecrypt when the tag is retrieved or checked, but cbTag must be set or else BCryptEncrypt/BCryptDecrypt will complain.
Initialize pbMacContext and cbMacContext. These point to a scratch space for the BCryptEncrypt/BCryptDecrypt to use to keep track of the current state of the tag/mac.
Initialize cbAAD and cbData to 0. The APIs use these fields, so you can read them at any time, but you should not update them after initially setting them to 0.
Initialize dwFlags to BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG. After initialization, changes to this field should be made by using |= or &=. Windows also sets flags within this field that the caller needs to take care not to alter.
Use BCryptGenerateSymmetricKey to import the key to use for encryption/decryption. Note that you will need to supply the memory associated with BCRYPT_OBJECT_LENGTH to this call for use by BCryptEncrypt/BCryptDecrypt during operation.
Call BCryptEncrypt/BCryptDecrypt with your AAD, if any; no input nor space for output need be supplied for this call. (If the call succeeds, you can see the size of your AAD reflected in the cbAAD field of the BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO structure.)
Set pbAuthData and cbAuthData to reflect the AAD.
Call BCryptEncrypt or BCryptDecrypt.
Set pbAuthData and cbAuthData back to NULL and 0.
Call BCryptEncrypt/BCryptDecrypt "N - 1" times
The amount of data passed to each call must be a multiple of the algorithm's block size.
Do not set the dwFlags parameter of the call to anything other than 0.
The output space must be equal to or greater than the size of the input
Call BCryptEncrypt/BCryptDecrypt one final time (with or without plain/cipher text input/output). The size of the input need not be a multiple of the algorithm's block size for this call. dwFlags is still set to 0.
Set the pbTag field of the BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO structure either to the location at which to store the generated tag or to the location of the tag to verify against, depending on whether the operation is an encryption or decryption.
Remove the BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG from the dwFlags field of the BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO structure using the &= syntax.
Call BCryptDestroyKey
Call BCryptCloseAlgorithmProvider
It would be wise, at this point, to wipe out the space associated with BCRYPT_OBJECT_LENGTH.

How does one access the raw ECDH public key, private key and params inside OpenSSL's EVP_PKEY structure?

I'm using OpenSSL's c library to generate an elliptic curve Diffie-Hellman (ECDH) key pair, following the first code sample here. It glosses over the actual exchange of public keys with this line:
peerkey = get_peerkey(pkey);
The pkey variable and the return value are both of type EVP *. pkey contains the public key, private key, and params generated earlier, and the return value only contains the peer's public key. So this raises three questions:
How would get_peerkey() actually extract just the public key from pkey for sending to the peer?
How would the code extract the private key and params from pKey to store them for later use after the key exchange?
How would get_peerkey() generate a new EVP_PKEY structure from the peer's raw public key?
I've seen the OpenSSL functions EVP_PKEY_print_public(), EVP_PKEY_print_private(), and EVP_PKEY_print_params() but these are for generating human-readable output. And I haven't found any equivalent for converting a human-readable public key back into an EVP_PKEY structure.
To answer my own question, there's a different path for the private key and the public key.
To serialize the public key:
Pass the EVP_PKEY to EVP_PKEY_get1_EC_KEY() to get an EC_KEY.
Pass the EC_KEY to EC_KEY_get0_public_key() to get an EC_POINT.
Pass the EC_POINT to EC_POINT_point2oct() to get octets, which are just unsigned char *.
To deserialize the public key:
Pass the octets to EC_POINT_oct2point() to get an EC_POINT.
Pass the EC_POINT to EC_KEY_set_public_key() to get an EC_KEY.
Pass the EC_KEY to EVP_PKEY_set1_EC_KEY to get an EVP_KEY.
To serialize the private key:
Pass the EVP_PKEY to EVP_PKEY_get1_EC_KEY() to get an EC_KEY.
Pass the EC_KEY to EC_KEY_get0_private_key() to get a BIGNUM.
Pass the BIGNUM to BN_bn2mpi() to get an mpi, which is a format written to
unsigned char *.
To deserialize the private key:
Pass the mpi to BN_mpi2bn() to get a BIGNUM.
Pass the BIGNUM to EC_KEY_set_private_key() to get an EC_KEY.
Pass the EC_KEY to EVP_PKEY_set1_EC_KEY to get an EVP_KEY.
It is also possible to convert the BIGNUM to hex, decimal, or "bin", although I think that mpi used the fewest bytes.
OpenSSL 3.x.x
To serialize the public key:
// We assume the public and private keys have been already generated.
// EVP_PKEY* keyPair...
// Get the serialized public key length.
size_t serializedPublicKeyLen = 0;
if (EVP_PKEY_get_octet_string_param(keyPair, OSSL_PKEY_PARAM_PUB_KEY,
NULL, 0, &serializedPublicKeyLen) != 1) {
return;
}
// Allocate memory for the serialized public key.
unsigned char* serializedPublicKey = (unsigned char*)OPENSSL_malloc(serializedPublicKeyLen);
if (serializedPublicKey == NULL) {
return;
}
// Get the serialized public key.
if (EVP_PKEY_get_octet_string_param(keyPair, OSSL_PKEY_PARAM_PUB_KEY,
serializedPublicKey, serializedPublicKeyLen, &serializedPublicKeyLen) != 1) {
return;
}
// Deallocate the memory when you finish using the serialized public key.
OPENSSL_free(serializedPublicKey);
To deserialize the public key:
// A parameter build for the public key.
OSSL_PARAM_BLD* paramBuild = OSSL_PARAM_BLD_new();
if (paramBuild == NULL) {
return;
}
// This is just an example. Set the curve
// you used to generate the public and private keys.
const char curveName[] = "secp384r1";
// Set the curve name to the parameter build.
if (OSSL_PARAM_BLD_push_utf8_string(paramBuild,
OSSL_PKEY_PARAM_GROUP_NAME, curveName, 0) != 1) {
OSSL_PARAM_BLD_free(paramBuild);
return;
}
// Set the serialized public key.
if (OSSL_PARAM_BLD_push_octet_string(paramBuild, OSSL_PKEY_PARAM_PUB_KEY,
serializedPublicKey, serializedPublicKeyLen) != 1) {
OSSL_PARAM_BLD_free(paramBuild);
return;
}
// Convert the OSSL_PARAM_BLD to an OSSL_PARAM.
OSSL_PARAM* params = OSSL_PARAM_BLD_to_param(paramBuild);
if (params == NULL) {
OSSL_PARAM_BLD_free(paramBuild);
return;
}
// Create a EVP_PKEY context.
EVP_PKEY_CTX* publicKeyCtx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
if (publicKeyCtx == NULL) {
OSSL_PARAM_BLD_free(paramBuild);
OSSL_PARAM_free(params);
return;
}
// Initialize the EVP_PKEY context.
if (EVP_PKEY_fromdata_init(publicKeyCtx) <= 0) {
OSSL_PARAM_BLD_free(paramBuild);
OSSL_PARAM_free(params);
EVP_PKEY_CTX_free(publicKeyCtx);
return;
}
// Create the peer public key object.
EVP_PKEY* publicKey = NULL;
if (EVP_PKEY_fromdata(publicKeyCtx, &publicKey,
EVP_PKEY_PUBLIC_KEY, params) <= 0) {
OSSL_PARAM_BLD_free(paramBuild);
OSSL_PARAM_free(params);
EVP_PKEY_CTX_free(publicKeyCtx);
return;
}
// Free auxiliary things...
OSSL_PARAM_BLD_free(paramBuild);
OSSL_PARAM_free(params);
EVP_PKEY_CTX_free(publicKeyCtx);
// Now you can use publicKey for EVP_PKEY_derive_set_peer.
// Call EVP_PKEY_free when you finish using it.
A full C++ OpenSSL 3 example: ec-diffie-hellman-openssl.h, ec-diffie-hellman-openssl.cpp.
A full C++ LibreSSL 3/OpenSSL 1 example: ec-diffie-hellman-libressl.h, ec-diffie-hellman-libressl.cpp.
To serialize the private key, you get BIGNUM instead:
BIGNUM* privateKey = NULL;
EVP_PKEY_get_bn_param(keyPair, OSSL_PKEY_PARAM_PRIV_KEY, &privateKey);
Then, you use one of the BIGNUM serialization function: https://www.openssl.org/docs/man3.0/man3/BN_bn2bin.html
To deserialize the private key, you use one of the BIGNUM deserialization function from the link above, then push it to the parameter build via OSSL_PARAM_BLD_push_BN with OSSL_PKEY_PARAM_PRIV_KEY.
The implementation above seems too complicated. openssl/evp.h has functions i2d_PublicKey() and d2i_PublicKey() to respectively convert to and from a binary representation of the public key (and there are equivalent functions for the private key - see: https://www.openssl.org/docs/manmaster/man3/d2i_PublicKey.html)
A small code example:
vector<unsigned char> ecdhPubkeyData(EVP_PKEY *key)
{
int len = i2d_PublicKey(key, 0); // with 0 as second arg it gives length
vector<unsigned char> ret(len);
unsigned char *ptr = ret.data();
len = i2d_PublicKey(key, &ptr);
return ret;
}
// Make sure you free the returned pointer when you are done with it
EVP_PKEY *ecdhPubkeyFromData(vector <unsigned char> const &pubkeyData)
{ // You do need to put in in an existing EVP_PKEY that is assigned
// an EC_KEY, because it needs to know what curve you use
EC_KEY *ec_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
EVP_PKEY *ret = EVP_PKEY_new();
EVP_PKEY_assign_EC_KEY(ret, ec_key);
unsigned char const *ptr = pubkeyData.data();
d2i_PublicKey(EVP_PKEY_EC, &ret, &ptr, pubkeyData.size());
return ret;
}
// PS: In a real example you want to check if any of these functions
// return NULL or some error code
I am using C++ vectors to contain the binary data, but of course you could just use C-style arrays too :-)
I am absolutely not an OpenSSL expert, so let me know if I am doing something horribly wrong in this implementation :-p

Erratic openssl/rsa behaviour: RSA_EAY_PRIVATE_DECRYPT:padding check failed

I have written a program that runs permanently using <openssl/rsa> C library.
It basically decrypts a password given in argument. The problem is that sometimes it works flawlessly, and some other times it fails (with the same pubkey/privkey/password, returning this error:
message: error:04065072:rsa routines:RSA_EAY_PRIVATE_DECRYPT:padding check failed
Has anyone ever experienced that?
Why this kind of error is returned, generally?
Some more details
It retrieves the private key at the initialisation of the program with the following:
#define PRIVFILE "<correct-path>/privkey.pem"
EVP_PKEY *privKey;
int size_key;
FILE *fp = fopen(PRIVFILE, "r");
if (!fp)
{
<logs>
return -1;
}
PEM_read_PrivateKey(fp, &privKey, 0, NULL);
fclose (fp);
if (privKey == NULL)
{
ERR_print_errors_fp (stderr);
return -1;
}
size_key = EVP_PKEY_size(privKey);
Later, during a listening loop, a method call the private decryption algorithm
int len_enc = size_key;
unsigned char* enc_pw;
unsigned char* dec_pw;
int len_dec = 8;
char* err = malloc(130);
enc_pw = malloc(len_enc);
dec_pw = malloc(len_dec);
memset(enc_pw, 0, len_enc);
memset(dec_pw, 0, len_dec);
memcpy(enc_pw, value, len_enc); //value being the raw ciphered data to decrypt
ERR_load_crypto_strings();
if (RSA_private_decrypt(len_enc, enc_pw, dec_pw, privKey->pkey.rsa,RSA_PKCS1_OAEP_PADDING) == -1)
{
ERR_error_string(ERR_get_error(), err);
radlog(L_ERR, "message: %s", err);
}
free(enc_pw);
free(dec_pw);
free(err);
I have done encryption on the data with perl using Crypt::OpenSSL::RSA:
my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key( $key_string);
my $ciphertext = $rsa_pub->encrypt( $plaintext);
There is some base64 encoding/decoding that i didn't mention to make it a little bit shorter. The problem does not come from that.
private key and public key are generated with openssl genrsa:
openssl genrsa -out privkey.pem 1024 and openssl rsa -in privkey.pem -pubout > pubkey.pub
It seems to work for some time, but occasionally (during a peak of request, if that matters) i get these errors for ciphered data that seemed valid before:
message: error:04065072:rsa routines:RSA_EAY_PRIVATE_DECRYPT:padding check failed
Is it a multi threaded application?
I was getting the same problem yesterday and, in my case, it was related to more than one thread using the key (one for decript and many others for encript). The problem was solved protecting the key with a mutex semaphore.
The service is up and stable since yesterday.

Reading PEM-formatted RSA keyfile with the OpenSSL C API

I am trying to read in an AES-128-CBC encrypted PEM key file generated using Ruby with the OpenSSL API. The code that generated the PEM key is the following:
OpenSSL::PKey::RSA.new(2048).to_pem(OpenSSL::Cipher::AES.new('128-CBC'), "password")
Here's the code that reads the PEM file:
RSA *rsa;
BIO *pem = BIO_new(BIO_s_mem());
BIO_puts(pem, "-----BEGIN RSA PRIVATE KEY-----\n"
"Proc-Type: 4,ENCRYPTED\n"
"DEK-Info: AES-128-CBC,BB13D39833DD6ED1FF9843644E7981EE\n\n
"Eugt8JZNKQKErsabWkwfm3wQhU/Tmp9T0QaP5HM8VIWpZwKlDmRlSUDptADU6RPD\n"
"5VtG3DPieXcf+6deyARImid9sBBmQ9mK2omkNRcTMemqTOhAuaKBu78TMt9G4YSf\n"
"RjoXWSqu3jMwrlcGkpn7bIum8wImITRZ3p28oSzk9aDUNBrIU/2Si8DM4RYIZ/fK\n"
"Uvvgdok9dgcd0SjvucivX2HaGeg/IUz23q1jg9inDpimZvFJD1FJfGEUWDgyfJfa\n"
"M8JIxTKbWOPEopONDkT7u4dC5VcSjK29MVbfd7iCKFPMh5UN+c96rPxTng/OWyW5\n"
"0tvzHyyyvAG9p0Hx5Lr4pDbv21GHyu43sA6wbs9jWyqO3AB7CaoEEQhumwfLsdjj\n"
"YGrX6bWThpYv/XNBDmmvltHlKFfe01NCybivOb4KwBnvi45x21PBqaZCKDTFdEkL\n"
"iwDMTiG2iTxSUvPFLy30VFozE+pGyMcGDUyZDVqjsaqI/MRj8khnn5nyubXc27G3\n"
"8Kbsnlix2SW2M0VDxqiy9dyjcxXrkFRSnOFYVs1PFlgjFVTG4Mwh6CZxKw8mFVbi\n"
"EmLvUYwzoDZ1ve4VXSPp/vrKEh33JuHhM0vJOpqI6wqw0QR0I2o6etM1ZRJClPcw\n"
"VIcgcvwenEgLOkoHDqOr0IZQAtYWvAuqq822wKt258hc6z8+ALQf5iMroqk7ADd4\n"
"FlRLz4XTwqlg7pPtTde/emI1DT8dQWzq++QI0lr0CS/N1GXJKqTQDvauXLIiI3Qy\n"
"KfFYFpV9jyYfRfTjNtisI/edPtp98auK0mb9o/wS/hruFI9behgv63iW1IwAOXCW\n"
"ZlkWgobUH13gS864rL+AcrAXreo2j4dDQouTeRaJUEG0HoYTP65Zun/VsCi2aSOH\n"
"JwSnnmHz9OxvcGY80WJDN3kqOCBRIJoDKBv6jcOxGVCsVK+WSdGZ7cfb8lwp7aA8\n"
"8ND1bwL9FYkwkeIsoakj91iinqv4o3+3PUPgCU5oe68WYvAFjuU+criyf+EhmXJV\n"
"JQ1vFFZPrGzgntJz19uXXh1h2iwQPggRouJm2RozYwvv1nz4eQ40Y3eT1F9UOYJU\n"
"CKEhOtI2NpLeVOayqo8g9wO2oC+CQVhZhdYBE5o7pM7akFnYLvRg9s1UsWdcvT0G\n"
"IpFmejLSRJ/F954aQMHTUc6vBOJZH/VNC5Qt+ulFXl634Sr9wQQK2qlqSJyA04TR\n"
"1ixbCNOX71esvpFImsrlsO5oTA22T3h2GyJPUM10XhqGtDXtsTnal6smLna9U9B3\n"
"gTVxFWWukQOF5Lm8ZFQipo2loHWjkozTBc4REPYP44SoXJXstv7k4pt1cK7x6/2H\n"
"ElspXzjveqMhcrveWv1KaA2OGd+hGfUiNsCoIdapJjLz1Bd/+oIQ/ZWQeo0nRowE\n"
"R/HlbbED3V+fRIdJpgydFEAw6gK5E9sYJcgF7uf/n2NabFxxEZL3g6MJQ64Dtusg\n"
"DEH/MpvIYDSX4Navh1gTwCtOeG1CzW3diYaqbZK+UZCBLFU7j27YvVPSd6F2+Wud\n"
"WnAqU3S5BCPqk5OD3wqZv+sEcqJgGPGy1Gv0tl8ARJomdKAru03KsRn2eIWqR5/C\n"
"-----END RSA PRIVATE KEY-----\n");
// Retrieve RSA key from PEM file.
rsa = PEM_read_bio_RSAPrivateKey(pem, NULL, pem_password_callback, "password");
And here's a dummy password callback (not sure about this function's purpose, but I think it may return the length of the password):
int pem_password_callback(char *buf, int max_len, int flag, void *pwd)
{
return 8;
}
Currently, the rsa = ... part does not throw an error, but doesn't return a well-formed result either.
And here's a dummy password callback (not sure about this function's
purpose, but I think it may return the length of the password):
int pem_password_callback(char *buf, int max_len, int flag, void *pwd)
{
return 8;
}
No, its not a dummy. In your example, you just returned a buffer with 8 junk characters (whatever happened to be in buf).
The password callback is where you programmatically plug in your password. You are supplied with a buff of max_len, and you need to copy the password into the buffer and return the number of bytes copied.
int pem_password_callback(char *buf, int max_len, int flag, void *ctx)
{
const char* PASSWD = "password";
int len = strlen(PASSWD);
if(len > max_len)
return 0;
memcpy(buf, PASSWD, len);
return len;
}
The flag is a read/write flag to denote if you are reading a key or writing a key. In practice, I have never used it.
You will use it similar to:
FILE* file = ...;
EVP_PKEY* pkey = PEM_read_PrivateKey(file, NULL, pem_password_callback, NULL);
Unlike the write routine (which needs an EVP_* cipher), the read routine knows what you used to encrypt the key with because its encoded in the private key.
In my systems, I actually use the context for a label to ensure the same passwords arrive at different derived keys:
EVP_PKEY* pkey = PEM_read_PrivateKey(file, NULL, pem_password_callback, "Some Context");
Then, in my password callback:
int pem_password_callback(char *buf, int max_len, int flag, void *ctx)
{
// "Some Context" in the example above
char* label = (char*)ctx;
// Hash password and label
// ...
// Copy hash to buffer, return length
...
}

Resources