El entorno OpenSSL, particularmente rico y complejo, permite crear diversas primitivas criptográficas también desde la línea de comandos. Entre ellas se encuentran el cifrado simétrico y la firma digital RSA. Aquí tienes una guía introductoria para familiarizarte con esta poderosa herramienta.
El software de código abierto OpenSSL, junto con algunas de sus bifurcaciones, es una herramienta bien conocida por muchos especialistas para la creación de diversas primitivas criptográficas, aprovechando principalmente las Interfaces de Programación de Aplicaciones (API, Application Programming Interface) que pone a disposición.
Pocos conocen el entorno que permite la ejecución de estas primitivas desde la línea de comandos. El objetivo de este artículo es introducirte al uso de este entorno, que es rico y complejo. Su dominio va más allá de nuestros propósitos aquí; nos limitaremos a presentarlo, proporcionar referencias para su estudio y examinar algunas primitivas criptográficas simples, como el cifrado simétrico, la firma digital y su verificación, las funciones hash, y algunos otros detalles. Proporcionaremos ejemplos de uso, haciendo referencia a OpenSSL versión 3.x.x.
En este artículo asumiremos que el lector está familiarizado con los principales conceptos de criptografía básica. En caso de que falte este requisito, será necesario comprender varios conceptos preliminares.
A través de la terminal o el símbolo del sistema, después de asegurarte de haber instalado OpenSSL e incluirlo en el PATH, puedes obtener la versión en uso con el comando openssl version. A continuación, para mayor claridad, se usará texto en cursiva para los comandos ingresados y texto en negrita para la salida de la computadora.
En este caso:
openssl version
OpenSSL 3.2.0 23 Nov 2023 (Library: OpenSSL 3.2.0 23 Nov 2023)
En la terminal de Linux/macOS, utilizada para invocar OpenSSL, el comando man openssl
proporciona una amplia descripción de cómo usar el comando, mostrando su complejidad y riqueza.
Cifrado Simétrico
El primer caso de uso considerado es el cifrado simétrico de un archivo. Para este propósito, será apropiado crear un archivo para cifrar. Decidimos comenzar con una imagen del logo de esgeeks. El archivo se llama logo.jpg
.
Si queremos cifrar el archivo, la primera pregunta que surge es: ¿con qué algoritmo de cifrado simétrico? Es interesante notar que podemos obtener información sobre los algoritmos compatibles con el simple comando openssl
.
openssl
Este comando mostrará una lista de comandos disponibles, incluyendo algoritmos de resumen de mensajes (digest), comandos de cifrado y otros. Puedes usar estos algoritmos según tus necesidades específicas para la cifra simétrica de archivos.
help:
Standard commands
asn1parse ca ciphers cmp
cms crl crl2pkcs7 dgst
dhparam dsa dsaparam ec
ecparam enc engine errstr
fipsinstall gendsa genpkey genrsa
help info kdf list
mac nseq ocsp passwd
pkcs12 pkcs7 pkcs8 pkey
pkeyparam pkeyutl prime rand
rehash req rsa rsautl
s_client s_server s_time sess_id
smime speed spkac srp
storeutl ts verify version
x509
Message Digest commands (see the `dgst' command for more details)
blake2b512 blake2s256 md4 md5
mdc2 rmd160 sha1 sha224
sha256 sha3-224 sha3-256 sha3-384
sha3-512 sha384 sha512 sha512-224
sha512-256 shake128 shake256 sm3
Cipher commands (see the `enc' command for more details)
aes-128-cbc aes-128-ecb aes-192-cbc aes-192-ecb
aes-256-cbc aes-256-ecb aria-128-cbc aria-128-cfb
aria-128-cfb1 aria-128-cfb8 aria-128-ctr aria-128-ecb
aria-128-ofb aria-192-cbc aria-192-cfb aria-192-cfb1
aria-192-cfb8 aria-192-ctr aria-192-ecb aria-192-ofb
aria-256-cbc aria-256-cfb aria-256-cfb1 aria-256-cfb8
aria-256-ctr aria-256-ecb aria-256-ofb base64
bf bf-cbc bf-cfb bf-ecb
bf-ofb camellia-128-cbc camellia-128-ecb camellia-192-cbc
camellia-192-ecb camellia-256-cbc camellia-256-ecb cast
cast-cbc cast5-cbc cast5-cfb cast5-ecb
cast5-ofb des des-cbc des-cfb
des-ecb des-ede des-ede-cbc des-ede-cfb
des-ede-ofb des-ede3 des-ede3-cbc des-ede3-cfb
des-ede3-ofb des-ofb des3 desx
idea idea-cbc idea-cfb idea-ecb
idea-ofb rc2 rc2-40-cbc rc2-64-cbc
rc2-cbc rc2-cfb rc2-ecb rc2-ofb
rc4 rc4-40 seed seed-cbc
seed-cfb seed-ecb seed-ofb sm4-cbc
sm4-cfb sm4-ctr sm4-ecb sm4-ofb
La parte más interesante en este momento es la introducida por los comandos de cifrado (Cipher commands
). Tenemos una serie de cadenas generalmente compuestas por tres partes, según el esquema xxx-yyy-zzz, donde el carácter “-
” se usa para separar las tres partes. La primera parte es un nombre o acrónimo que identifica el algoritmo utilizado, la segunda describe la longitud (en bits) de la clave y la tercera es el modo operativo seleccionado.
Por ejemplo, AES-128-cbc denota el algoritmo AES con clave de 128 bits y modo operativo CBC. A veces, no todas las cadenas están presentes y se omiten aquellas que son predeterminadas u obligatorias. Elijamos usar, por ejemplo, precisamente el mencionado aes-128-cbc
(estándar del Instituto Nacional de Estándares y Tecnología de los Estados Unidos, NIST).
Para cifrar, por lo tanto, el archivo logo.jpg
y construir su cifrado logo.enc
, podemos usar (existen más formas en OpenSSL de obtener el mismo resultado):
openssl enc -aes-128-cbc -in logo.jpg -out col.enc
enter AES-128-CBC encryption password:
Verifying - enter AES-128-CBC encryption password:
*** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.
La entrada y salida se han copiado y pegado, por lo que se muestra la reproducción fiel de la interacción. Primero, la biblioteca solicita una contraseña para generar internamente una clave de cifrado (a través de una función de derivación de clave, o KDF), luego pide su confirmación; después, advierte que la KDF utilizada, que es la predeterminada, no se recomienda (porque es poco segura) y sugiere alternativas. De hecho, la opción -pbkdf2
permite usar una KDF mejor y más moderna.
Repetimos, entonces, la operación de cifrado usando la palabra “es33^%*
” como contraseña, obteniendo:
openssl enc -aes-128-cbc -pbkdf2 -in logo.jpg -out logo.enc
Ahora el resultado está en el archivo logo.enc
.
Podemos repetir todo agregando la opción -p
, que proporciona una mayor visibilidad de los parámetros utilizados:
openssl enc -aes-128-cbc -pbkdf2 -p -in logo.jpg -out logo2.enc
enter AES-128-CBC encryption password:
Verifying - enter AES-128-CBC encryption password:
salt=D19BCF06E40A8C51
key=D702AE76F45BF69690397FA80FCEB7BB
iv =C7C9909A151952544943064C0F8EEA84
En particular, hemos obtenido la visualización de la sal generada (salt), de la clave construida (key) y del IV creado (iv, necesario para CBC); todos los valores se expresan en hexadecimal. Podemos ver los primeros dieciséis bytes del archivo creado logo2.enc de la siguiente manera:
hexdump -C logo2.enc | head -1
00000000 53 61 6c 74 65 64 5f 5f d1 9b cf 06 e4 0a 8c 51 |Salted__.......Q|
Desde la visualización, podemos verificar que los primeros ocho bytes del resultado codifican la cadena “Salted__
” y los segundos ocho describen la sal (se usan minúsculas en lugar de mayúsculas, pero no hay diferencia real). Estos detalles son necesarios para generar el mismo IV durante la descifración. Sin embargo, se debe mencionar que la decisión de colocar estos dieciséis bytes al principio del archivo cifrado es una característica de OpenSSL, no de AES. Esto significa que otra biblioteca que use AES con claves de 128 bits y CBC podría generar un archivo diferente porque podría insertar esta información auxiliar al final del archivo o en otro archivo.
Ahora tomemos el archivo logo2.enc
y descifrémoslo, colocando el contenido en logo2.jpg
. Lo que esperamos es que logo.jpg
y logo2.jpg
sean idénticos. Puedes verificar esto con el comando cmp
. La línea para descifrar es similar a la de cifrar, con algunas pequeñas variaciones: se ha agregado la opción -d
para la descifración (el valor predeterminado es cifrar), el archivo de entrada se convierte en logo2.enc
y el de salida en logo2.jpg
.
openssl enc -aes-128-cbc -pbkdf2 -d -p -in logo2.enc -out logo2.jpg
La comparación con cmp
confirma la total coincidencia.
cmp logo.jpg logo2.jpg
Si los archivos son idénticos, no habrá ninguna salida del comando. Si hay alguna diferencia, el comando mostrará la ubicación y el valor del primer byte diferente encontrado.
En la página del manual (man
) es posible ver cómo cifrar/descifrar proporcionando sal, IV y/o contraseña arbitrarios directamente desde la línea de comandos.
Firma Digital RSA
Ahora nos ocuparemos del cifrado asimétrico, examinando el uso de RSA con fines de firma digital. Para aplicar una firma mediante RSA, es necesario:
- Tener un par de claves, que posiblemente se deban generar.
- Realizar la operación de firma en sí.
- Saber cómo realizar la verificación.
OpenSSL permite generar fácilmente una clave privada RSA con el siguiente comando:
openssl genpkey -algorithm RSA -out key.pem
...+...........+.+.................+.+......+...+...+...+.....+.+......+..+.............+++++++++++++++++++++++++++++++++++++++*..........+..+.+.....+............+...+............+.+.....+.+...+++++++++++++++++++++++++++++++++++++++*..................+......+.........+......+......++++++
.+.....................+++++++++++++++++++++++++++++++++++++++*....+.........+.+...+......+......+...+..+............+......+......+.+...+..+........................+...+...+...+....+.....+...+....+...+...+........+......+...+...+.......+.....+.......+...+..+++++++++++++++++++++++++++++++++++++++*...+...........+...+.++++++
Este comando crea una clave privada RSA y la guarda en el archivo key.pem. Si intentamos observar el inicio del archivo, podemos usar el siguiente comando:
head key.pem
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC42m/dU+PUlNAI
mImiMDtXpLGVZ+TpqNVFbKyMRr+LCWo17TIxyDU8gQs7HMTKxnH1KYTEMZQlLyeB
lgjDsK6Snw417+nLS5OhrKr+g4ZKsC6jUQ6dk3qhVLVXKBufgVy9u8poZlOop/CK
YjDwtEqBGo1nPHC8AzYdSC5JD49nlBPuxMuTFSxW9/x71HKP4DoYkGwf3v5MDGXT
6ZiXFitcSLEaj+o8r5egJf6ggQK3SjZ9dCQsZUP0nlsx7thb52SaO9CiYxHTG6AE
A pesar de la indicación inicial “BEGIN PRIVATE KEY
“, el archivo contiene toda la información relacionada con ambas claves. Notamos que está uuencoded y de lectura inmediata (plaintext). Esto no es adecuado para una clave privada. Se debe generar su representación cifrada, por ejemplo, usando AES. Además, el tamaño de la clave es el predeterminado (2048 bits); si queremos una clave de 4096 bits, debemos usar la opción -pkeyopt rsa_keygen_bits:4096
.
openssl genpkey -algorithm RSA -out key.pem -aes-128-cbc -pkeyopt rsa_keygen_bits:4096
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
Se generará una contraseña PEM, y al verificarla, podrás observar el inicio del archivo generado key.pem
:
head -6 key.pem
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIJtTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQkjQveqMuhf3anPQk
c+H4ygICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEEnfeobx2p2wESh8
DCyfRbAEgglQNah8FRgO6yjcoc7wlE3CqXl72/dULgXoy8L2uhMEd3slQGRa5Vz2
V0sGejEMNcA+XYXG59kdTXT9UT6FmJCPGBGhuOJ4kUc08uhRK7ImIaFjyFtu4fNg
iQWtwsZAenrVRQHniHI6XOzY79p1TenexlObrFR6QUzate97EpsMF3Ck0DDXoW8f
Para obtener detalles de la clave, puedes usar la opción -text
:
openssl pkey -in key.pem -text
Como podemos observar, hay cinco campos presentes: modulus (el valor N = p·q, producto de los primos p y q), publicExponent, que se establece por defecto en 65537, privateExponent d, el inverso multiplicativo de e mod (p – 1)(q – 1), y algunos detalles que facilitan los cálculos de RSA.
Es posible extraer la clave pública de la privada de la siguiente manera:
openssl rsa -in key.pem -out pub.pem -pubout
Enter pass phrase for key.pem:
writing RSA key
Verifiquemos las primeras dos líneas de pub.pem
:
head -2 pub.pem
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyyLghKTCaIrg9HZ/zrnv
Hemos extraído efectivamente la clave pública.
Para firmar digitalmente el archivo logo.jpg, usemos la función hash SHA-256 (muy utilizada). Ahora surge una pregunta: hay tres estándares para RSA, descritos por PKCS#1 durante sus evoluciones, y podemos referirnos a ellos como PKCS#1 v1.5, PKCS#1 OAEP y PKCS#1 PSS, que describen cómo se debe preprocesar el resumen del archivo antes de realizar la exponenciación RSA.
Las técnicas de uso de RSA admitidas por OpenSSL se refieren a los tres estándares mencionados, con los nombres de pkcs1, oaep y pss. Según la documentación de OpenSSL, solo pkcs1 y pss admiten la firma, mientras que oaep solo admite cifrado y descifrado. Construyamos el hash (binario) del archivo.
openssl dgst -sha256 -binary -out hash logo.jpg
hexdump hash
Centrémonos primero en PKCS#1 v1.5, el más antiguo y único recomendado para casos de retrocompatibilidad. Podemos firmar con:
openssl pkeyutl -sign -in hash -inkey key.pem -out sign1.5 -pkeyopt rsa_padding_mode:pkcs1
Enter pass phrase for key.pem:
El archivo sign1.5 contiene la firma y es binario.
0000000 5eb2 19ff 7363 3f75 5889 5ea2 c451 39fa
0000010 27c0 a2d6 5ca2 a1b7 b3b7 2e27 4fd3 7a2a
0000020 2734 9fe8 4ea2 2e4d aa38 a3fc a95c 642c
La verificación es casi igual, hay que reemplazar ‘sign’ con ‘verify’ y hacer algunas intervenciones simples.
openssl pkeyutl -verify -sigfile sign1.5 -in hash -inkey key.pem -pkeyopt rsa_padding_mode:pkcs1
Signature Verified Successfully
En el caso de PSS, el comando de firma también requiere la especificación de la longitud del salt: el valor especial -1
indica la misma longitud que el digest.
openssl pkeyutl -sign -in hash -inkey key.pem -out signpss -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:-1
Las primeras líneas de la firma obtenida:
hexdump signpss | head -3
0000000 cb4b 7c2d d5bc 51bc ec93 574e 21d3 8b6d
0000010 2aa9 c550 f661 7be1 96fc a353 48c9 7c0d
0000020 bb01 421e 9f14 2b8d fcde ac81 e419 e364
En este sentido, observamos que al realizar la misma firma en el archivo ‘signpssbis’:
openssl pkeyutl -sign -in hash -inkey key.pem -out signpssbis -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:-1
Enter pass phrase for key.pem:
se obtiene un archivo diferente.
hexdump signpssbis | head -3
0000000 bd8c 6ff4 383e 65e7 9ed1 957b 3f8d d2e9
0000010 4158 f4e9 a50e 940c f9f1 a702 feee 85dd
0000020 a32e b63b 9588 319c 7b3b 2a7f 47b4 040c
Esto es lo que esperábamos porque la firma PSS no es determinista. Repitiendo la firma PKCS#1 y creando un archivo ‘sign1.5bis’ será idéntico a ‘sign1.5’ (firma determinista).
En el caso de PSS, la verificación es análoga al caso de PKCS#1.
openssl pkeyutl -verify -sigfile signpss -in hash -inkey key.pem -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:-1
Signature Verified Successfully
Notamos que en ambas verificaciones tuvimos que ingresar la contraseña para acceder a la clave privada; esto se debe a que contiene la información necesaria para la verificación (que encontraríamos en la clave pública).
openssl pkeyutl -verify -sigfile signpss -in hash -pubin -inkey pub.pem -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:-1
Signature Verified Successfully
De hecho, no se solicitó una contraseña.
Te podrías preguntar cuál es la relación entre la firma generada por OpenSSL y las firmas tradicionales utilizadas por muchas personas, como PAdES o CAdES. Desde un punto de vista técnico, las firmas PAdES y CAdES pueden basarse en RSA. Veamos las distinciones más significativas:
- Las firmas PAdES y CAdES son compatibles con certificados X509, que confirman la validez y corrección de las claves y otorgan valor legal a las firmas.
- En la firma PAdES, la firma se inserta en el propio PDF, por lo que no es un archivo separado; en CAdES, la firma (además de otra información) se inserta en un sobre criptográfico llamado P7M, una especie de contenedor que agrupa toda la información de interés para la firma y desde el cual se pueden extraer las componentes con software especializado.
Es relevante señalar que tanto la sección ‘dgst’ como ‘pkeyutl’ permiten aplicar firmas. La primera es a un nivel más alto, mientras que la segunda es a un nivel más bajo y requiere el hash del archivo a firmar. Hay otras diferencias, pero son más técnicas.
El software OpenSSL contiene muchas secciones, llamadas “comandos” en la documentación. En la siguiente tabla, se puede examinar un resumen, directamente obtenido de la documentación oficial; se destaca el alcance masivo de la biblioteca, útil no solo para realizar operaciones criptográficas simples, sino también para trabajar con certificados, asegurar el correo electrónico, trabajar con números aleatorios, generar contraseñas, elegir una KDF, etc.
Nombre | Descripción Original del Comando |
---|---|
asn1parse | Analiza una secuencia ASN.1 |
ca | Gestión de Autoridad de Certificación (CA) |
ciphers | Determinación de Descripción de Conjunto de Cifrado |
cms | Comando de Sintaxis de Criptografía (Cryptographic Syntax) |
crl | Gestión de Lista de Revocación de Certificados (CRL) |
crl2pkcs7 | Conversión de CRL a PKCS#7 |
dgst | Cálculo de Resumen de Mensaje. Los cálculos de MAC son reemplazados por openssl-mac(1) |
dhparam | Generación y Gestión de Parámetros de Diffie-Hellman. Sustituido por openssl-genpkey(1) y openssl-pkeyparam(1) |
dsa | Gestión de Datos de DSA (Algoritmo de Firma Digital) |
dsaparam | Generación y Gestión de Parámetros de DSA. Sustituido por openssl-genpkey(1) y openssl-pkeyparam(1) |
ec | Procesamiento de Clave EC (Curva Elíptica) |
ecparam | Manipulación y Generación de Parámetros EC |
enc | Encriptación, desencriptación y codificación |
engine | Información y manipulación de Módulos Cargables (Engine) |
errstr | Conversión de Número de Error a Cadena de Error |
fipsinstall | Instalación de Configuración FIPS |
gendsa | Generación de Clave Privada DSA a partir de Parámetros. Sustituido por openssl-genpkey(1) y openssl-pkey(1) |
help | Muestra información sobre las opciones de un comando |
info | Muestra información diversa incorporada en las bibliotecas OpenSSL |
kdf | Funciones de Derivación de Clave |
list | Lista algoritmos y características |
mac | Cálculo de Código de Autenticación de Mensaje (MAC) |
nseq | Crea o examina una secuencia de certificados Netscape |
ocsp | Comando del Protocolo de Estado de Certificado en Línea (OCSP) |
passwd | Generación de contraseñas con hash |
pkcs12 | Gestión de Datos PKCS#12 |
pkcs7 | Gestión de Datos PKCS#7 |
pkcs8 | Conversión de formato de clave privada en formato 8 |
pkey | Gestión de claves pública y privada |
pkeyparam | Gestión de parámetros de algoritmo de clave pública |
pkeyutl | Comando criptográfico de algoritmo de clave pública |
prime | Calcula números primos |
rand | Genera bytes pseudoaleatorios |
rehash | Crea enlaces simbólicos a archivos de certificados y CRL nombrados por los valores de hash |
req | Gestión de Solicitud de Firma de Certificado (CSR) PKCS#10 X.509 |
rsa | Gestión de claves RSA |
rsautl | Comando RSA para firmar, verificar, encriptar y desencriptar. Sustituido por openssl-pkeyutl(1) |
s_client | Implementa un cliente SSL/TLS genérico que puede establecer una conexión transparente a un servidor remoto que habla SSL/TLS. Destinado solo para pruebas y proporciona funcionalidad de interfaz rudimentaria, pero internamente utiliza principalmente toda la funcionalidad de la biblioteca SSL de OpenSSL |
s_server | Implementa un servidor SSL/TLS genérico que acepta conexiones de clientes remotos que hablan SSL/TLS. Destinado solo para pruebas y proporciona funcionalidad de interfaz rudimentaria, pero internamente utiliza principalmente toda la funcionalidad de la biblioteca SSL de OpenSSL. Proporciona tanto un protocolo de línea de comandos propio para probar funciones SSL como una sencilla instalación de respuesta HTTP para emular un servidor web consciente de SSL/TLS |
s_time | Temporizador de Conexión SSL |
sess_id | Gestión de Datos de Sesión SSL |
smime | Procesamiento de Correo S/MIME |
speed | Medición de Velocidad de Algoritmo |
spkac | Impresión y generación de comandos SPKAC |
srp | Mantener archivo de contraseñas SRP. Este comando está obsoleto |
storeutl | Comando para listar y mostrar certificados, claves, CRL, etc. |
ts | Comando de Autoridad de Estampado de Tiempo |
verify | Verificación de Certificado X.509. Consulta también la página del manual openssl-verification-options(1) |
version | Información de Versión de OpenSSL |
x509 | Gestión de Datos de Certificado X.509 |
Todos los comandos mencionados pueden ser explorados escribiendo “man openssl-
” seguido del nombre del comando. Por ejemplo, “man openssl-smime
“. A veces, la documentación no refleja con precisión el comportamiento real y es necesario realizar experimentos.
Conclusión
Hemos examinado algunas operaciones que se pueden realizar con OpenSSL, comprendiendo la potencia y versatilidad de la biblioteca.
De entre las muchas funciones compatibles, hemos seleccionado algunas que están más cercanas a las necesidades de un usuario final, como el cifrado y la firma digital.
Hay otras, como ahora es evidente, que son extremadamente más especializadas. En un próximo artículo, se abordará el uso de OpenSSL para probar conexiones TLS y gestionar certificados.