cryptography openssl elliptic-curve

cryptography - Cifrado/descifrado de cadenas de texto utilizando OpenSSL ECC



elliptic-curve (2)

ECC en sí no define realmente ninguna operación de cifrado / descifrado, los algoritmos creados en curvas elípticas sí lo hacen.

Un ejemplo es Elliptic-Curve Diffie-Hellman. Puede cifrar un mensaje usando ECDH de la siguiente manera:

  1. Generando una clave de CE efímera.
  2. Usando esa clave y la clave pública del destinatario, genere un secreto usando ECDH.
  3. Use ese secreto como clave para cifrar el mensaje con un cifrado simétrico, como AES.
  4. Transmita el mensaje cifrado y la clave pública efímera generada en el paso 1.

Para descifrar:

  1. Cargue la clave pública efímera del mensaje.
  2. Use esa clave pública junto con su clave de destinatario para generar un secreto usando ECDH.
  3. Utilice ese secreto como clave para descifrar el mensaje con el cifrado simétrico.

EDITAR: La siguiente es la idea básica para generar un secreto utilizando ECDH. Primero debemos definir una función de derivación de claves, esta utiliza el hash 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); }

Este es el código ECDH para el lado del remitente. Se supone que la clave pública del destinatario ya está en "receta_clave" y que la ha verificado con EC_KEY_check_key (). También omite una verificación de errores muy importante, en aras de la brevedad, que definitivamente querrá incluir en el código de producción.

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);

Después de esto, el búfer ''buf'' contiene 20 bytes de material que puede usar para teclear. Este ejemplo abreviado se basa en el código de "ecdhtest.c" en la distribución de fuentes de openssl. Le sugiero que lo mire.

Deseará enviar la parte de clave pública de ephemeral_key con el mensaje cifrado y descartar de forma segura la parte de clave privada. Una MAC sobre los datos también es una buena idea, y si necesita más de 20 bytes de material de claves, probablemente haya un hash más largo en orden.

El destinatario hace algo similar, excepto que su clave privada ya existe (ya que el remitente tenía que saber la clave pública correspondiente de antemano), y la clave pública se recibe del remitente.

¿Cómo puedo usar el soporte ECC de OpenSSL para cifrar o descifrar una cadena de texto? Puedo generar claves ECC privadas / públicas utilizando las API de OpenSSL, pero no sé cómo cifrar texto sin formato con esas claves.


Ya que es tan difícil encontrar ejemplos que muestren cómo usar ECC para cifrar datos, pensé que publicaría algún código para que otros lo usen. Para la lista completa, revisa mi publicación openssl-dev:

http://www.mail-archive.com/[email protected]/msg28042.html

Básicamente es una versión útil y descargada de cómo usar ECDH para asegurar un bloque de datos. ECDH se utiliza para generar un secreto compartido. El secreto compartido se procesa mediante SHA 512. Los 512 bits resultantes se dividen, con 256 como clave para el cifrado simétrico (AES 256 en mi ejemplo) y los otros 256 bits utilizados como clave para el HMAC. Mi implementación se basa libremente en el estándar ECIES descrito por el grupo de trabajo SECG.

Las funciones clave son ecies_encrypt () que acepta la clave pública en forma hexadecimal y devuelve los datos cifrados:

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; }

Y ecies_decrypt (), que toma la clave privada, nuevamente en forma hexadecimal, y descifra el búfer previamente asegurado:

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; }

Estoy publicando esto porque personalmente no pude encontrar ningún otro ejemplo de cómo proteger archivos usando ECC y la biblioteca OpenSSL. Dicho esto, vale la pena mencionar alternativas que no usan OpenSSL. Uno es seccure que sigue un patrón similar a mi ejemplo, solo que se basa en libgcrypt. Dado que libgcrypt no proporciona todas las funciones ECC subyacentes necesarias, el programa seccure llena los huecos e implementa la lógica ECC que falta en libgcrypt.

Otro programa que vale la pena ver es SKS, que utiliza un proceso de cifrado basado en ECC similar al del ejemplo anterior, pero no tiene dependencias externas (por lo tanto, todo el código de ECC está ahí para que lo vea).