OpenSSL es la navaja suiza de los certificados y la criptografía en la terminal. Casi nadie lo usa todos los días, pero cuando hace falta, hace falta ya: el certificado de tu sitio venció, el proveedor te mandó un .pfx y nginx pide un .pem, un programa de facturación no acepta tu e.firma, o curl dice unable to get local issuer certificate y no sabes de quién es la culpa. Esta guía reúne las órdenes que de verdad se usan, con lo que responden y qué significan sus errores.
- Qué es OpenSSL y qué versión tienes
- El laboratorio de esta guía
- Qué archivo es qué: PEM, DER, .crt, .key, .csr y .pfx
- Ver un certificado
- Probar un servidor con s_client
- ¿Cuándo vence? Revisarlo desde un script
- Crear una llave y una CSR
- Un certificado autofirmado para pruebas
- ¿La llave corresponde al certificado?
- Convertir PEM, DER y PFX
- La e.firma del SAT: del .cer y el .key a PEM
- Hashes, HMAC y números aleatorios
- Cifrar un archivo con enc, y por qué no es lo ideal
- Verificar una cadena con verify
- Errores típicos y qué significan
- Resumen de órdenes
Qué es OpenSSL y qué versión tienes
OpenSSL es dos cosas: una biblioteca que usan nginx, Apache, PostgreSQL, Python, curl y medio internet para cifrar conexiones, y la orden openssl, que deja usar esa biblioteca desde la terminal. Cada tarea es una subordem: openssl x509 para certificados, openssl req para solicitudes, openssl s_client para conectarte a un servidor, y así. Lo primero es saber cuál tienes:
openssl version
salida · tu computadoraOpenSSL 3.5.7 9 Jun 2026 (Library: OpenSSL 3.5.7 9 Jun 2026)
En noviembre de 2026 hay tres ramas con soporte: 3.5, la de soporte largo (LTS), que es la que traen Debian 13 y Alpine; 4.0, de abril de 2026, y 4.1, del 23 de octubre de 2026. La rama 3.0 llegó a su fin de soporte el 16 de septiembre de 2026: si tu sistema todavía la trae, es hora de actualizar. La 4.0 quitó los engines, SSLv3 y el script c_rehash (ahora es openssl rehash), pero ninguna de las órdenes de esta guía cambió: las repetimos en una 4.1.0 compilada desde el código oficial y respondieron igual.
- Linux: ya viene instalado; si no,
sudo apt install opensslo el equivalente de tu distribución. - macOS: el
opensslque trae el sistema es en realidad LibreSSL, un derivado con opciones distintas, y algunas de esta guía no existen ahí. Para seguirla sin sorpresas instala el de Homebrew:brew install openssl@3. - Windows: Git para Windows incluye
opensslen su terminal Git Bash; también funciona dentro de WSL.
El laboratorio de esta guía
Para que puedas ver las órdenes en acción sin tocar sitios ajenos, armamos una red privada con dos máquinas: tu computadora y un servidor nginx que responde por tienda.lab, blog.lab y viejo.lab. Esos certificados los firma una autoridad certificadora (CA) nuestra, con una raíz y una intermedia, igual que hacen Let’s Encrypt o DigiCert. Por eso, cuando veas raiz-lab.crt, piensa en «la raíz en la que confío». En tu caso esa raíz ya viene en tu sistema y no tienes que pasarla.
Para que las órdenes corrieran solas, en el laboratorio pasamos las contraseñas con -pass, -passin o -passout. En las órdenes que ves aquí las quitamos: así OpenSSL te las pide sin mostrarlas y no quedan en el historial de la terminal. Las salidas son las mismas.
Los tres sitios están configurados a propósito para mostrar casos distintos: tienda.lab está bien, blog.lab olvidó mandar el certificado intermedio y viejo.lab venció el 30 de octubre. Para el STARTTLS de correo usamos un servidor público, el SMTP de Gmail.
Qué archivo es qué: PEM, DER, .crt, .key, .csr y .pfx
La mitad de los problemas con certificados vienen de no saber qué hay en cada archivo. Hay cuatro cosas distintas, y la extensión no siempre ayuda: un .pem puede ser un certificado, una llave o las dos cosas juntas.
Para saber qué es un archivo PEM, basta ver su primera línea:
head -1 tienda.crt tienda.key cifrada.key tienda.csr file tienda.cer tienda.pfx
salida · tu computadora==> tienda.crt <== -----BEGIN CERTIFICATE----- ==> tienda.key <== -----BEGIN PRIVATE KEY----- ==> cifrada.key <== -----BEGIN ENCRYPTED PRIVATE KEY----- ==> tienda.csr <== -----BEGIN CERTIFICATE REQUEST----- tienda.cer: Certificate, Version=3 tienda.pfx: data
BEGIN ENCRYPTED PRIVATE KEY es una llave protegida con contraseña; BEGIN PRIVATE KEY, una sin protección. Los archivos DER y PFX son binarios: file reconoce el certificado DER, pero del PFX solo puede decir data.
Ver un certificado
Este es el certificado de tienda.lab, el que nos entregó la CA del laboratorio. -noout evita que se imprima el certificado codificado y -text lo muestra completo:
openssl x509 -in tienda.crt -noout -text
salida · tu computadoraCertificate:
Data:
Version: 3 (0x2)
Serial Number:
27:77:1a:c5:fe:48:c6:6f:0b:b6:2c:ad:43:52:9e:14:32:0e:2a:5f
Signature Algorithm: ecdsa-with-SHA256
Issuer: O=Laboratorio decodigo, CN=Intermedia del laboratorio R1
Validity
Not Before: Nov 11 01:36:40 2026 GMT
Not After : Feb 9 01:36:40 2027 GMT
Subject: C=MX, O=Tienda de Ana, CN=tienda.lab
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:cc:f7:80:e8:ad:09:30:59:9f:9a:35:12:57:58:
c3:e0:c2:14:55:30:16:6d:c3:68:74:37:a7:f5:76:
92:74:c5:7c:26:52:34:62:03:73:d9:ed:b9:df:45:
43:68:20:3e:5f:f0:9f:33:6c:07:09:fa:ff:6d:92:
81:2b:2b:f6:dd
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:tienda.lab, DNS:www.tienda.lab
X509v3 Basic Constraints:
CA:FALSE
X509v3 Key Usage: critical
Digital Signature
X509v3 Extended Key Usage:
TLS Web Server Authentication
X509v3 Subject Key Identifier:
FC:4D:6F:34:8D:DA:13:00:83:AA:57:AF:89:17:D3:2D:3B:9B:EE:50
X509v3 Authority Key Identifier:
EB:D6:B4:D9:61:AA:52:94:7F:98:9B:18:C4:2F:BC:B6:E7:32:45:B3
Signature Algorithm: ecdsa-with-SHA256
Signature Value:
30:65:02:30:7e:95:cc:fd:8e:e3:ce:2f:f4:e1:34:01:19:4a:
c1:48:dd:32:82:fc:4d:32:f1:62:9e:5e:34:6c:05:f7:fe:f8:
ab:57:b1:3a:ea:89:bf:f5:3c:27:52:7b:d8:f4:d4:92:02:31:
00:c0:e5:e5:f6:dd:da:ec:4b:05:70:cf:76:46:77:55:0f:b9:
1f:d2:a9:8a:4a:2d:e0:ee:97:2f:49:ed:37:19:61:d5:e5:1a:
e1:67:98:a2:10:72:b6:b1:d2:74:75:c5:a0Lo que importa, de arriba abajo: Issuer es quién lo firmó; Validity, desde cuándo y hasta cuándo sirve (en UTC); Subject, a nombre de quién está; y Subject Alternative Name (SAN), los nombres para los que vale. Hoy los navegadores solo miran el SAN: el CN del Subject ya no cuenta. Casi nunca necesitas todo; pide solo los campos:
openssl x509 -in tienda.crt -noout -subject -issuer -dates -serial
salida · tu computadorasubject=C=MX, O=Tienda de Ana, CN=tienda.lab issuer=O=Laboratorio decodigo, CN=Intermedia del laboratorio R1 notBefore=Nov 11 01:36:40 2026 GMT notAfter=Feb 9 01:36:40 2027 GMT serial=27771AC5FE48C66F0BB62CAD43529E14320E2A5F
openssl x509 -in tienda.crt -noout -ext subjectAltName,keyUsage,extendedKeyUsage
salida · tu computadoraX509v3 Subject Alternative Name:
DNS:tienda.lab, DNS:www.tienda.lab
X509v3 Key Usage: critical
Digital Signature
X509v3 Extended Key Usage:
TLS Web Server AuthenticationLa huella (fingerprint) sirve para comparar un certificado con otro sin leerlo entero, por ejemplo para confirmar que el del servidor es el mismo que tienes en un archivo:
openssl x509 -in tienda.crt -noout -fingerprint -sha256
salida · tu computadorasha256 Fingerprint=CF:CD:94:A0:FF:59:9E:EA:33:23:29:4B:65:16:74:EF:FD:2D:51:ED:66:20:EC:EE:90:0F:B6:9A:A3:62:0E:9B
Un detalle que ahorra tiempo: desde OpenSSL 3, x509 reconoce solo si el archivo viene en PEM o en DER. Pasamos el certificado a DER y lo leímos sin decir nada más:
openssl x509 -in tienda.crt -outform DER -out tienda.der openssl x509 -in tienda.der -noout -subject
salida · tu computadorasubject=C=MX, O=Tienda de Ana, CN=tienda.lab
Otras subórdenes no tienen esa cortesía, como verás con la e.firma. Si prefieres no usar la terminal, nuestra herramienta para leer un certificado o una CSR muestra estos mismos campos en el navegador, sin subir el archivo a ningún lado.
Probar un servidor con s_client
s_client se conecta a un servidor como lo haría un navegador y te enseña todo lo que pasó: qué certificados mandó, si la cadena se pudo verificar, qué versión de TLS y qué cifrado se acordaron. Es la herramienta para responder «¿el problema es del servidor o de mi lado?». El echo | del principio cierra la conexión en cuanto termina el saludo; sin él, s_client se queda esperando a que escribas.
echo | openssl s_client -connect tienda.lab:443 -servername tienda.lab
salida · tu computadoradepth=1 O=Laboratorio decodigo, CN=Intermedia del laboratorio R1 verify error:num=20:unable to get local issuer certificate depth=0 C=MX, O=Tienda de Ana, CN=tienda.lab 0 s:C=MX, O=Tienda de Ana, CN=tienda.lab i:O=Laboratorio decodigo, CN=Intermedia del laboratorio R1 1 s:O=Laboratorio decodigo, CN=Intermedia del laboratorio R1 i:O=Laboratorio decodigo, CN=Raiz del laboratorio Negotiated TLS1.3 group: X25519MLKEM768 New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 Verify return code: 20 (unable to get local issuer certificate)
Recortamos la salida a lo importante. El servidor mandó dos certificados: el 0 es el de tienda.lab (s: es el sujeto, i: quién lo firmó) y el 1 es la intermedia. El error 20 aparece porque nuestra computadora no conoce la raíz del laboratorio. Con un sitio real, la raíz ya está en tu sistema y no verías ese error. Para el laboratorio se la pasamos con -CAfile:
echo | openssl s_client -connect tienda.lab:443 -servername tienda.lab -CAfile raiz-lab.crt
salida · tu computadoradepth=2 O=Laboratorio decodigo, CN=Raiz del laboratorio depth=1 O=Laboratorio decodigo, CN=Intermedia del laboratorio R1 depth=0 C=MX, O=Tienda de Ana, CN=tienda.lab 0 s:C=MX, O=Tienda de Ana, CN=tienda.lab i:O=Laboratorio decodigo, CN=Intermedia del laboratorio R1 1 s:O=Laboratorio decodigo, CN=Intermedia del laboratorio R1 i:O=Laboratorio decodigo, CN=Raiz del laboratorio Negotiated TLS1.3 group: X25519MLKEM768 New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 Verify return code: 0 (ok)
Verify return code: 0 (ok): la cadena llega hasta una raíz de confianza. La línea Negotiated TLS1.3 group: X25519MLKEM768 dice que el intercambio de llaves fue poscuántico híbrido: OpenSSL 3.5 y posteriores lo usan por omisión, igual que OpenSSH con su propio algoritmo.
Con -brief sale un resumen corto, y -verify_hostname comprueba además que el certificado sea para ese nombre, como hace un navegador:
echo | openssl s_client -brief -connect tienda.lab:443 -servername tienda.lab -CAfile raiz-lab.crt -verify_hostname tienda.lab
salida · tu computadoraConnecting to 172.26.0.2 CONNECTION ESTABLISHED Protocol version: TLSv1.3 Ciphersuite: TLS_AES_256_GCM_SHA384 Peer certificate: C=MX, O=Tienda de Ana, CN=tienda.lab Hash used: SHA256 Signature type: ecdsa_secp256r1_sha256 Verification: OK Verified peername: tienda.lab Negotiated TLS1.3 group: X25519MLKEM768 DONE
SNI: por qué hay que poner -servername
Un mismo servidor puede tener muchos sitios en la misma IP. El cliente dice a cuál quiere entrar con el SNI (Server Name Indication), y -servername es lo que lo manda. OpenSSL lo pone solo a partir de -connect, pero conviene escribirlo siempre: si te conectas por IP, o si el nombre del -connect no es el del sitio, el servidor te entregará otro certificado. Pedimos blog.lab sin SNI y con SNI:
echo | openssl s_client -connect blog.lab:443 -noservername 2>/dev/null | openssl x509 -noout -subject
salida · tu computadorasubject=C=MX, O=Tienda de Ana, CN=tienda.lab
echo | openssl s_client -connect blog.lab:443 -servername blog.lab 2>/dev/null | openssl x509 -noout -subject
salida · tu computadorasubject=CN=blog.lab
Sin SNI, nginx contestó con el sitio por omisión, tienda.lab. Es la causa típica de «el certificado es de otro dominio» cuando pruebas por IP.
La versión de TLS
Para comprobar si un servidor acepta TLS 1.2, fuerza esa versión con -tls1_2 (o -tls1_3 para la otra):
echo | openssl s_client -brief -tls1_2 -connect tienda.lab:443 -servername tienda.lab
salida · tu computadoraProtocol version: TLSv1.2 Ciphersuite: ECDHE-ECDSA-AES256-GCM-SHA384
¿Y TLS 1.1? La sorpresa es que el que se niega es tu propio OpenSSL, antes de mandar nada:
echo | openssl s_client -tls1_1 -connect tienda.lab:443 -servername tienda.lab
salida · tu computadora8077C1B51D730000:error:0A0000BF:SSL routines:tls_setup_handshake:no protocols available:../ssl/statem/statem_lib.c:155:
no protocols available significa que el nivel de seguridad por omisión de OpenSSL 3 ya no permite TLS 1.0 ni 1.1. Para saber si el servidor lo aceptaría, hay que bajar ese nivel solo para la prueba con -cipher "DEFAULT:@SECLEVEL=0". Ahora sí responde el servidor, y lo rechaza:
echo | openssl s_client -tls1_1 -cipher "DEFAULT:@SECLEVEL=0" -connect tienda.lab:443 -servername tienda.lab
salida · tu computadora8037A42BC17F0000:error:0A00042E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version:../ssl/record/rec_layer_s3.c:918:SSL alert number 70
Correo: STARTTLS
Los servidores de correo empiezan la conversación sin cifrar y luego la «suben» a TLS con la orden STARTTLS. -starttls hace ese paso por ti; sirve para smtp, imap, pop3, ftp, postgres y otros. Contra el servidor de envío de Gmail:
echo QUIT | openssl s_client -brief -starttls smtp -connect smtp.gmail.com:587 -servername smtp.gmail.com
salida · tu computadoraProtocol version: TLSv1.3 Peer certificate: CN=smtp.gmail.com Verification: OK Negotiated TLS1.3 group: X25519MLKEM768
¿Cuándo vence? Revisarlo desde un script
Para saber cuándo vence el certificado de un sitio, se encadenan dos órdenes: s_client lo descarga y x509 lo lee. El 2>/dev/null calla los mensajes de la conexión:
echo | openssl s_client -connect tienda.lab:443 -servername tienda.lab 2>/dev/null | openssl x509 -noout -subject -enddate -ext subjectAltName
salida · tu computadorasubject=C=MX, O=Tienda de Ana, CN=tienda.lab
notAfter=Feb 9 01:36:40 2027 GMT
X509v3 Subject Alternative Name:
DNS:tienda.lab, DNS:www.tienda.labPara un script, -checkend es mejor: recibe segundos y responde con el código de salida, 0 si el certificado sigue vigente después de ese tiempo y 1 si vence antes. 30 días son 2,592,000 segundos:
openssl x509 -in tienda.crt -noout -checkend $((30*24*3600)); echo "código: $?" openssl x509 -in tienda.crt -noout -checkend $((100*24*3600)); echo "código: $?"
salida · tu computadoraCertificate will not expire código: 0 Certificate will expire código: 1
Vence en 90 días, así que pasa la prueba de 30 y no la de 100. Con eso se arma una revisión de varios sitios:
for s in tienda.lab viejo.lab; do
echo | openssl s_client -connect $s:443 -servername $s 2>/dev/null \
| openssl x509 -noout -checkend $((21*24*3600)) >/dev/null \
&& echo "$s: vigente al menos 21 días más" \
|| echo "$s: vence en menos de 21 días (o ya venció)"
donesalida · tu computadoratienda.lab: vigente al menos 21 días más viejo.lab: vence en menos de 21 días (o ya venció)
Ponla en un temporizador de systemd o en cron, como explicamos en systemd y cron, y que te mande un correo cuando algo falle. Let’s Encrypt renueva solo si usas certbot, pero esta revisión te avisa si la renovación dejó de funcionar.
Crear una llave y una CSR
Para pedir un certificado se necesitan dos cosas: una llave privada, que se queda contigo, y una solicitud de firma (CSR), que lleva tu llave pública y los nombres del sitio y es lo único que mandas a la CA. Si usas Let’s Encrypt con certbot, él hace estos pasos por ti; los necesitas cuando compras un certificado o cuando una CA interna te lo pide.
genpkey es la orden moderna para crear llaves. Hoy la opción más común para un sitio es una llave de curva elíptica P-256: es pequeña, rápida y la aceptan todos los navegadores:
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out tienda.key openssl pkey -in tienda.key -noout -text_pub | head -1; ls -l tienda.key
salida · tu computadoraPublic-Key: (256 bit) -rw------- 1 ana ana 241 Nov 11 01:36 tienda.key
Fíjate en los permisos: OpenSSL la crea como -rw-------, legible solo por ti. Si algún sistema viejo pide RSA, 3072 bits es el tamaño recomendado hoy (2048 todavía se acepta):
openssl genpkey -quiet -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out tienda-rsa.key ls -l tienda-rsa.key && head -1 tienda-rsa.key
salida · tu computadora-rw------- 1 ana ana 2484 Nov 11 01:36 tienda-rsa.key -----BEGIN PRIVATE KEY-----
Existen también las llaves Ed25519: son ideales para firmar archivos y las usa SSH, pero los navegadores no las aceptan en certificados de sitios web, así que no las uses para HTTPS:
openssl genpkey -algorithm ED25519 -out firma-ed25519.key head -1 firma-ed25519.key; wc -c firma-ed25519.key
salida · tu computadora-----BEGIN PRIVATE KEY----- 119 firma-ed25519.key
Si la llave va a viajar o a guardarse en un respaldo, protégela con contraseña añadiendo un cifrado como -aes-256-cbc. Un servidor web necesita la llave sin contraseña para arrancar solo, así que ahí se deja sin cifrar pero con permisos estrictos:
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -aes-256-cbc -out cifrada.key head -1 cifrada.key
salida · tu computadora-----BEGIN ENCRYPTED PRIVATE KEY-----
Ahora la CSR. -subj evita el cuestionario interactivo, y -addext agrega los nombres alternativos, que son los que de verdad cuentan:
openssl req -new -key tienda.key -subj "/C=MX/O=Tienda de Ana/CN=tienda.lab" \ -addext "subjectAltName=DNS:tienda.lab,DNS:www.tienda.lab" -out tienda.csr head -1 tienda.csr
salida · tu computadora-----BEGIN CERTIFICATE REQUEST-----
Antes de mandarla, revisa que diga lo que quieres. -verify comprueba que la CSR esté firmada con la llave que dice traer:
openssl req -in tienda.csr -noout -text -verify | grep -E "verify|Subject:|DNS:|Public Key Algorithm|ASN1 OID"
salida · tu computadoraCertificate request self-signature verify OK
Subject: C=MX, O=Tienda de Ana, CN=tienda.lab
Public Key Algorithm: id-ecPublicKey
ASN1 OID: prime256v1
DNS:tienda.lab, DNS:www.tienda.labEsa CSR es la que firmó la CA del laboratorio para darnos el tienda.crt que usamos en toda la guía. La llave nunca salió de la computadora: a la CA solo le llega la CSR.
Un certificado autofirmado para pruebas
Para un servidor de desarrollo, una red interna o una prueba rápida, no necesitas una CA: puedes firmar tu propio certificado. Con req -x509 se crean la llave y el certificado en un solo paso; -noenc deja la llave sin contraseña (en versiones viejas se llamaba -nodes):
openssl req -x509 -newkey EC -pkeyopt ec_paramgen_curve:P-256 -noenc \ -keyout prueba.key -out prueba.crt -days 30 \ -subj "/CN=prueba.lab" -addext "subjectAltName=DNS:prueba.lab,DNS:localhost" openssl x509 -in prueba.crt -noout -subject -issuer -enddate openssl verify prueba.crt
salida · tu computadorasubject=CN=prueba.lab issuer=CN=prueba.lab notAfter=Dec 11 01:39:47 2026 GMT CN=prueba.lab error 18 at 0 depth lookup: self-signed certificate error prueba.crt: verification failed
El emisor y el sujeto son el mismo, y por eso verify lo rechaza con self-signed certificate: nadie de confianza responde por él. El navegador mostrará la misma advertencia. Para desarrollo está bien; en un sitio público, usa Let’s Encrypt, que es gratis y está explicado en Nginx desde cero.
¿La llave corresponde al certificado?
El error clásico al instalar un certificado es combinarlo con la llave que no es: la de la renovación anterior, la de otro servidor, la RSA cuando el certificado es EC. La forma segura de comprobarlo, que funciona con cualquier tipo de llave, es comparar la llave pública de los dos lados. Si las huellas coinciden, son pareja; la CSR también se puede incluir:
openssl pkey -in tienda.key -pubout | openssl sha256 openssl x509 -in tienda.crt -noout -pubkey | openssl sha256 openssl req -in tienda.csr -noout -pubkey | openssl sha256
salida · tu computadoraSHA2-256(stdin)= 99d074d280f3522ab14ba22c7fcc57eb18747f28ff9f45d8ed9619c37fff1070 SHA2-256(stdin)= 99d074d280f3522ab14ba22c7fcc57eb18747f28ff9f45d8ed9619c37fff1070 SHA2-256(stdin)= 99d074d280f3522ab14ba22c7fcc57eb18747f28ff9f45d8ed9619c37fff1070
openssl pkey -in tienda-rsa.key -pubout | openssl sha256 openssl x509 -in tienda.crt -noout -pubkey | openssl sha256
salida · tu computadoraSHA2-256(stdin)= 4ece927a449f951017980e0d752234dae90a16853a612b52aec56c7707320514 SHA2-256(stdin)= 99d074d280f3522ab14ba22c7fcc57eb18747f28ff9f45d8ed9619c37fff1070
Muchas guías viejas comparan el -modulus, pero eso solo existe en las llaves RSA. Con una llave de curva elíptica no hay módulo que comparar:
openssl x509 -in tienda.crt -noout -modulus
salida · tu computadoraModulus=No modulus for this public key type
Como script, para no comparar las huellas a ojo:
cat > coinciden.sh <<"EOF"
#!/bin/sh
# coinciden.sh llave certificado
a=$(openssl pkey -in "$1" -pubout | openssl sha256)
b=$(openssl x509 -in "$2" -noout -pubkey | openssl sha256)
[ "$a" = "$b" ] && echo "coinciden" || { echo "NO coinciden"; exit 1; }
EOF
chmod +x coinciden.sh
./coinciden.sh tienda.key tienda.crt
./coinciden.sh tienda-rsa.key tienda.crtsalida · tu computadoracoinciden NO coinciden
Con Let’s Encrypt, los archivos son privkey.pem y fullchain.pem: el script funciona igual, porque x509 lee el primer certificado del archivo, que es el tuyo. Y así se ve el error cuando nginx recibe una pareja que no corresponde:
sudo nginx -t
salida · el servidor2026/11/11 01:38:11 [emerg] 89#89: SSL_CTX_use_PrivateKey("/root/pki/blog.key") failed (SSL: error:05800074:x509 certificate routines::key values mismatch)
nginx: configuration file /etc/nginx/nginx.conf test failedConvertir PEM, DER y PFX
PEM y DER
Un PEM es el DER pasado a Base64 con las líneas BEGIN y END, así que la conversión no pierde nada. Del PEM al DER y de regreso:
openssl x509 -in tienda.crt -outform DER -out tienda.cer && ls -l tienda.crt tienda.cer
salida · tu computadora-rw-r--r-- 1 ana ana 578 Nov 11 01:38 tienda.cer -rw-r--r-- 1 ana ana 839 Nov 11 01:36 tienda.crt
openssl x509 -inform DER -in tienda.cer -out de-vuelta.pem && head -1 de-vuelta.pem && cmp de-vuelta.pem tienda.crt && echo idénticos
salida · tu computadora-----BEGIN CERTIFICATE----- idénticos
Con las llaves es igual, pero con pkey en lugar de x509:
openssl pkey -in tienda.key -outform DER -out tienda-llave.der openssl pkey -inform DER -in tienda-llave.der -out tienda-de-vuelta.key cmp tienda.key tienda-de-vuelta.key && echo idénticas
salida · tu computadoraidénticas
PFX o PKCS#12
Windows, IIS, Azure, Java y muchos programas de escritorio piden un solo archivo .pfx (o .p12) que trae la llave, el certificado y la cadena, protegido con una contraseña. Para armarlo, -certfile agrega la intermedia y -name le pone una etiqueta:
openssl pkcs12 -export -inkey tienda.key -in tienda.crt -certfile intermedia.crt \ -name "tienda.lab" -out tienda.pfx ls -l tienda.pfx
salida · tu computadora-rw------- 1 ana ana 1925 Nov 11 01:38 tienda.pfx
Sin -passout, te pide la contraseña del PFX. Para ver qué trae, sin extraer nada:
openssl pkcs12 -in tienda.pfx -info -noout
salida · tu computadoraMAC: sha256, Iteration 2048 MAC length: 32, salt length: 8 PKCS7 Encrypted data: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256 Certificate bag Certificate bag PKCS7 Data Shrouded Keybag: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256
Y para sacar cada pieza por separado, que es lo que pide nginx:
openssl pkcs12 -in tienda.pfx -clcerts -nokeys | openssl x509 -noout -subject
salida · tu computadorasubject=C=MX, O=Tienda de Ana, CN=tienda.lab
openssl pkcs12 -in tienda.pfx -cacerts -nokeys
salida · tu computadorasubject=O=Laboratorio decodigo, CN=Intermedia del laboratorio R1 -----BEGIN CERTIFICATE-----
openssl pkcs12 -in tienda.pfx -nocerts -noenc -out llave-del-pfx.key head -1 llave-del-pfx.key && ./coinciden.sh llave-del-pfx.key tienda.crt
salida · tu computadoraBag Attributes coinciden
-clcerts es el certificado del sitio; -cacerts, la cadena; -nocerts -noenc, la llave sin contraseña. El archivo de la llave empieza con unas líneas Bag Attributes: nginx las ignora, pero puedes limpiarlas pasando el archivo por openssl pkey.
PFX viejos: -legacy
Los PFX que exportan Windows Server antiguos y algunos programas viejos van cifrados con RC2 y 3DES, algoritmos que OpenSSL 3 ya no carga por omisión. Creamos uno así y lo intentamos abrir:
openssl pkcs12 -export -legacy -inkey tienda.key -in tienda.crt -out viejo-windows.pfx openssl pkcs12 -in viejo-windows.pfx -info -noout -legacy
salida · tu computadoraMAC: sha1, Iteration 2048 MAC length: 20, salt length: 8 PKCS7 Encrypted data: pbeWithSHA1And40BitRC2-CBC, Iteration 2048 Certificate bag PKCS7 Data Shrouded Keybag: pbeWithSHA1And3-KeyTripleDES-CBC, Iteration 2048
openssl pkcs12 -in viejo-windows.pfx -nokeys -out /dev/null
salida · tu computadoraError outputting keys and certificates 80F7BA6AC6710000:error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:../crypto/evp/evp_fetch.c:376:Global default library context, Algorithm (RC2-40-CBC : 0), Properties ()
unsupported ... RC2-40-CBC: la contraseña está bien, el problema es el algoritmo. Se resuelve agregando -legacy:
openssl pkcs12 -legacy -in viejo-windows.pfx -clcerts -nokeys | openssl x509 -noout -subject
salida · tu computadorasubject=C=MX, O=Tienda de Ana, CN=tienda.lab
Al revés también sirve: si un programa viejo no abre los PFX nuevos (que van con AES-256), créalos con -export -legacy. Por último, Windows a veces entrega la cadena como .p7b, que trae solo certificados, sin llave:
openssl pkcs7 -inform DER -in cadena.p7b -print_certs | grep subject
salida · tu computadorasubject=C=MX, O=Tienda de Ana, CN=tienda.lab subject=O=Laboratorio decodigo, CN=Intermedia del laboratorio R1
La e.firma del SAT: del .cer y el .key a PEM
La e.firma (antes FIEL) y los certificados de sello digital del SAT llegan como dos archivos: un .cer, que es un certificado X.509 en DER, y un .key, que es tu llave privada en formato PKCS#8 DER cifrado con la contraseña que elegiste al tramitarla. Muchos programas y bibliotecas (PHP con ext-openssl, nginx, varias de facturación) solo leen PEM, y ahí empieza la búsqueda de «cómo convertir la e.firma a PEM».
XAXX010101000 y un número de certificado falso. Tu .key da acceso a trámites ante el SAT a tu nombre: conviértela solo en tu propia computadora, nunca en una página web que te pida subirla, y borra el PEM sin cifrar al terminar.Los dos son binarios. file reconoce el certificado, pero de la llave solo dice data. Con asn1parse se ve qué hay dentro de la llave: está cifrada con PBES2, y con nuestros archivos de prueba el cifrado es 3DES (des-ede3-cbc), el que suelen usar las llaves del SAT:
file efirma-prueba.cer efirma-prueba.key
salida · tu computadoraefirma-prueba.cer: Certificate, Version=3 efirma-prueba.key: data
openssl asn1parse -inform DER -in efirma-prueba.key | grep -E "OBJECT|INTEGER" | head -6
salida · tu computadora 6:d=2 hl=2 l= 9 prim: OBJECT :PBES2 21:d=4 hl=2 l= 9 prim: OBJECT :PBKDF2 52:d=5 hl=2 l= 2 prim: INTEGER :0800 58:d=6 hl=2 l= 8 prim: OBJECT :hmacWithSHA256 72:d=4 hl=2 l= 8 prim: OBJECT :des-ede3-cbc
El .cer
openssl x509 -inform DER -in efirma-prueba.cer -noout -subject -issuer -dates -serial
salida · tu computadorasubject=CN=CONTRIBUYENTE DE PRUEBA, O=CONTRIBUYENTE DE PRUEBA, C=MX, x500UniqueIdentifier=XAXX010101000 issuer=O=Laboratorio decodigo, CN=AC de pruebas (no es el SAT) notBefore=Nov 11 01:38:57 2026 GMT notAfter=Nov 10 01:38:57 2030 GMT serial=3030303031303030303030393939393939393939
Tu RFC viene en el campo x500UniqueIdentifier. El número de serie parece un número hexadecimal sin sentido, pero el SAT guarda ahí tu número de certificado de 20 dígitos, escrito como texto: cada par 3x es un dígito. Así se recupera:
openssl x509 -inform DER -in efirma-prueba.cer -noout -serial | cut -d= -f2 | xxd -r -p; echo
salida · tu computadora00001000000999999999
Pasarlo a PEM es una sola orden:
openssl x509 -inform DER -in efirma-prueba.cer -out efirma-prueba.cer.pem head -1 efirma-prueba.cer.pem
salida · tu computadora-----BEGIN CERTIFICATE-----
El .key
Aquí sí hace falta -inform DER: pkcs8 no adivina el formato, y sin esa opción busca un PEM y falla con un mensaje que no ayuda mucho:
openssl pkcs8 -in efirma-prueba.key -out /dev/null; echo "código: $?"
salida · tu computadoraError reading key 8087A915CC7D0000:error:0480006C:PEM routines:get_name:no start line:../crypto/pem/pem_lib.c:792:Expecting: ENCRYPTED PRIVATE KEY código: 1
Con -inform DER, OpenSSL te pide la contraseña de la e.firma y escribe la llave en PEM, sin cifrar. En el laboratorio pasamos la contraseña con -passin; tú omítelo para que la pida sin mostrarla:
openssl pkcs8 -inform DER -in efirma-prueba.key -out efirma-prueba.key.pem ls -l efirma-prueba.key.pem && head -1 efirma-prueba.key.pem
salida · tu computadora-rw------- 1 ana ana 1704 Nov 11 01:39 efirma-prueba.key.pem -----BEGIN PRIVATE KEY-----
Si el programa acepta una llave PEM con contraseña, mejor: déjala cifrada con -topk8 y un cifrado moderno, con una contraseña nueva:
openssl pkcs8 -inform DER -in efirma-prueba.key -topk8 -v2 aes-256-cbc -out efirma-prueba.cifrada.pem head -1 efirma-prueba.cifrada.pem
salida · tu computadora-----BEGIN ENCRYPTED PRIVATE KEY-----
Con la contraseña equivocada, el error es este. maybe wrong password es literal: casi siempre es eso.
openssl pkcs8 -inform DER -in efirma-prueba.key -out /dev/null
salida · tu computadoraError decrypting key 8047C019C47C0000:error:1C800064:Provider routines:ossl_cipher_unpadblock:bad decrypt:../providers/implementations/ciphers/ciphercommon_block.c:107: 8047C019C47C0000:error:11800074:PKCS12 routines:PKCS12_pbe_crypt_ex:pkcs12 cipherfinal error:../crypto/pkcs12/p12_decr.c:94:maybe wrong password
Comprobar que son pareja y firmar
Es común tener varias e.firmas o sellos y no saber qué .key va con qué .cer. La misma prueba de la llave pública responde:
a=$(openssl pkey -in efirma-prueba.key.pem -pubout | openssl sha256) b=$(openssl x509 -in efirma-prueba.cer.pem -noout -pubkey | openssl sha256) [ "$a" = "$b" ] && echo "la .key y el .cer son pareja"
salida · tu computadorala .key y el .cer son pareja
Y como prueba de que la llave funciona, una firma SHA-256 con RSA, del mismo tipo que el sello de un CFDI, verificada con la llave pública del certificado:
printf "cadena original de prueba" > cadena.txt openssl dgst -sha256 -sign efirma-prueba.key.pem -out firma.bin cadena.txt openssl base64 -A -in firma.bin | cut -c1-60; echo openssl dgst -sha256 -verify <(openssl x509 -in efirma-prueba.cer.pem -noout -pubkey) -signature firma.bin cadena.txt
salida · tu computadoracb/NGqP+RwgDHTm5iHQYmzfw10JVzlv+W3l/BeJfNIgAqcomUnbwLQyW9AkQ Verified OK
Si un programa te pide la e.firma como PFX, júntalas con pkcs12 -export, como en el apartado anterior. Al terminar, borra la llave sin cifrar:
shred -u efirma-prueba.key.pem && ls
salida · tu computadoracadena.txt efirma-prueba.cer efirma-prueba.cer.pem efirma-prueba.cifrada.pem efirma-prueba.key efirma-prueba.pfx firma.bin
Para revisar el XML de una factura está nuestro validador de XML, y para confirmar que un RFC esté bien escrito, el validador de RFC.
Hashes, HMAC y números aleatorios
Un hash es la huella de un archivo: si cambia un solo byte, cambia todo el hash. Sirve para comprobar que una descarga llegó completa o que un respaldo no se alteró. openssl dgst da el mismo resultado que sha256sum:
openssl dgst -sha256 reporte.txt sha256sum reporte.txt
salida · tu computadoraSHA2-256(reporte.txt)= 87b4dd2ec9a7cc68107b8b9e6b0431ff8c8f1473c490cec4dcb34f70c5589e4d 87b4dd2ec9a7cc68107b8b9e6b0431ff8c8f1473c490cec4dcb34f70c5589e4d reporte.txt
Con -r sale en el formato de sha256sum, así que puedes guardar el hash y comprobarlo después con sha256sum -c:
openssl dgst -sha256 -r respaldo.tar > respaldo.tar.sha256 sha256sum -c respaldo.tar.sha256
salida · tu computadorarespaldo.tar: OK
Hay otros algoritmos: -sha512, -sha3-256, -blake2b512. MD5 y SHA-1 siguen ahí para compatibilidad, pero ya no sirven para nada de seguridad. Si lo que quieres es comprobar que un mensaje viene de alguien que comparte un secreto contigo (como hacen los webhooks), lo que necesitas es un HMAC:
openssl dgst -sha256 -hmac "secreto-compartido" reporte.txt
salida · tu computadoraHMAC-SHA2-256(reporte.txt)= ba2701d3c22f35986e0d6ef63c6d73dad4951db94e60e3ac49d768a8680d2b38
Para el hash de un texto sin abrir la terminal, usa nuestra herramienta para generar un hash SHA-256. Y para secretos aleatorios, como la llave de una API o una contraseña de base de datos, rand usa el generador seguro del sistema:
openssl rand -hex 32 openssl rand -base64 32
salida · tu computadorace0af0f41393719171f51bd7241667d834b1e64e88b5e9aebafe86596444ac5d WwlzqaMXoY/5pIDru3rL8/2DpAAQZpwkFo8PXLp3c7U=
Cifrar un archivo con enc, y por qué no es lo ideal
openssl enc cifra un archivo con una contraseña. Funciona, pero tiene trampas, así que primero lo correcto: -pbkdf2 y un -iter alto convierten la contraseña en llave de forma lenta a propósito, para que adivinarla cueste. 600,000 iteraciones tardaron 0.16 segundos en el laboratorio:
openssl enc -aes-256-cbc -pbkdf2 -iter 600000 -salt -in reporte.txt -out reporte.txt.enc ls -l reporte.txt reporte.txt.enc && head -c 8 reporte.txt.enc; echo
salida · tu computadora-rw-r--r-- 1 ana ana 44 Nov 11 01:39 reporte.txt -rw-r--r-- 1 ana ana 64 Nov 11 01:39 reporte.txt.enc Salted__
openssl enc -d -aes-256-cbc -pbkdf2 -iter 600000 -in reporte.txt.enc
salida · tu computadoraReporte de ventas de octubre total: 152,300
Para descifrar hay que repetir exactamente las mismas opciones: el archivo no guarda cuáles usaste, solo la sal (Salted__). Sin -pbkdf2, OpenSSL usa la derivación vieja y lo avisa:
openssl enc -aes-256-cbc -in reporte.txt -out viejo.enc
salida · tu computadora*** WARNING : deprecated key derivation used. Using -iter or -pbkdf2 would be better.
Ahora la trampa grande: enc no detecta si alguien modificó el archivo cifrado. Cambiamos un solo byte del archivo y lo desciframos con la contraseña correcta:
cp reporte.txt.enc alterado.enc
printf "\x01" | dd of=alterado.enc bs=1 seek=20 conv=notrunc status=none
openssl enc -d -aes-256-cbc -pbkdf2 -iter 600000 -in alterado.enc | xxd | head -3; echo "código: ${PIPESTATUS[0]}"salida · tu computadora00000000: 6daf 9e64 26e5 8a56 ac8c 062b 293d e715 m..d&..V...+)=.. 00000010: 7320 6465 866f 6374 7562 7265 0a74 6f74 s de.octubre.tot 00000020: 616c 3a20 3135 322c 3330 300a al: 152,300. código: 0
Código 0, sin un solo aviso: el primer bloque salió como basura y en el segundo cambió una letra. Alguien que conozca el formato puede alterar partes del contenido sin saber la contraseña. Los cifrados que sí detectan cambios (los AEAD, como AES-GCM) existen en la biblioteca, pero enc no los acepta:
openssl enc -aes-256-gcm -pbkdf2 -in reporte.txt -out x.enc
salida · tu computadoraenc: AEAD ciphers not supported enc: Use -help for summary.
Conclusión práctica: para cifrar archivos que vas a guardar o mandar, usa age o GnuPG (gpg --symmetric), que autentican el contenido. openssl enc queda para cuando no hay otra cosa instalada y el riesgo es solo que alguien lo lea, no que lo cambie. Y con la contraseña equivocada, el error de siempre:
openssl enc -d -aes-256-cbc -pbkdf2 -iter 600000 -in reporte.txt.enc -out /dev/null; echo "código: $?"
salida · tu computadorabad decrypt 80779EFDBF780000:error:1C800064:Provider routines:ossl_cipher_unpadblock:bad decrypt:../providers/implementations/ciphers/ciphercommon_block.c:107: código: 1
Verificar una cadena con verify
openssl verify hace con un archivo lo que s_client hace con un servidor: revisa que la cadena llegue a una raíz de confianza. La raíz va en -CAfile y las intermedias en -untrusted, que quiere decir «úsala para armar el camino, pero no confíes en ella por sí sola»:
openssl verify -CAfile raiz-lab.crt -untrusted intermedia.crt tienda.crt
salida · tu computadoratienda.crt: OK
openssl verify -show_chain -CAfile raiz-lab.crt -untrusted intermedia.crt tienda.crt
salida · tu computadoratienda.crt: OK Chain: depth=0: C=MX, O=Tienda de Ana, CN=tienda.lab (untrusted) depth=1: O=Laboratorio decodigo, CN=Intermedia del laboratorio R1 (untrusted) depth=2: O=Laboratorio decodigo, CN=Raiz del laboratorio
Sin la intermedia, falla en la profundidad 0 (no encuentra quién firmó tu certificado); sin la raíz, en la profundidad 1 (no encuentra quién firmó la intermedia):
openssl verify -CAfile raiz-lab.crt tienda.crt
salida · tu computadoraC=MX, O=Tienda de Ana, CN=tienda.lab error 20 at 0 depth lookup: unable to get local issuer certificate error tienda.crt: verification failed
openssl verify -untrusted intermedia.crt tienda.crt
salida · tu computadoraO=Laboratorio decodigo, CN=Intermedia del laboratorio R1 error 20 at 1 depth lookup: unable to get local issuer certificate error tienda.crt: verification failed
El número después de at es la profundidad: 0 es tu certificado, 1 la intermedia, 2 la raíz. Con eso sabes cuál pieza falta. -verify_hostname también funciona aquí, y un certificado vencido falla aunque la cadena esté completa:
openssl verify -CAfile raiz-lab.crt -untrusted intermedia.crt -verify_hostname www.tienda.lab tienda.crt openssl verify -CAfile raiz-lab.crt -untrusted intermedia.crt -verify_hostname tienda.com.mx tienda.crt
salida · tu computadoratienda.crt: OK C=MX, O=Tienda de Ana, CN=tienda.lab error 62 at 0 depth lookup: hostname mismatch error tienda.crt: verification failed
openssl verify -CAfile raiz-lab.crt -untrusted intermedia.crt viejo.crt
salida · tu computadoraCN=viejo.lab error 10 at 0 depth lookup: certificate has expired error viejo.crt: verification failed
Sin -CAfile, verify y s_client usan las raíces del sistema, las de /etc/ssl/certs en Debian y Ubuntu. Cómo llegan ahí y quién decide cuáles son está en cómo sabe tu navegador en qué certificados confiar.
Errores típicos y qué significan
unable to get local issuer certificate (20) o unable to verify the first certificate (21)
Falta un eslabón de la cadena. Si te pasa con un sitio, casi siempre es que el servidor no manda la intermedia. Es lo que hicimos mal a propósito en blog.lab: el navegador a veces lo disimula, porque guarda intermedias de otras visitas, pero curl y las aplicaciones no:
echo | openssl s_client -connect blog.lab:443 -servername blog.lab -CAfile raiz-lab.crt
salida · tu computadoradepth=0 CN=blog.lab verify error:num=20:unable to get local issuer certificate depth=0 CN=blog.lab verify error:num=21:unable to verify the first certificate depth=0 CN=blog.lab 0 s:CN=blog.lab i:O=Laboratorio decodigo, CN=Intermedia del laboratorio R1 Verify return code: 21 (unable to verify the first certificate)
curl -sS --cacert raiz-lab.crt https://blog.lab/ curl -sS --cacert raiz-lab.crt https://tienda.lab/
salida · tu computadoracurl: (60) SSL certificate problem: unable to get local issuer certificate tienda
Solo se manda un certificado (0 s:) y no hay 1 s:. La solución es del lado del servidor: en nginx, ssl_certificate debe apuntar al archivo con la cadena completa (fullchain.pem con Let’s Encrypt), no al certificado solo. Si te pasa con verify, te falta -untrusted o -CAfile.
certificate has expired (10)
echo | openssl s_client -connect viejo.lab:443 -servername viejo.lab -CAfile raiz-lab.crt
salida · tu computadoraverify error:num=10:certificate has expired Verify return code: 10 (certificate has expired)
Venció. Si renovaste y sigue saliendo, el servidor no se recargó o apunta al archivo viejo: compara la huella del archivo con la del servidor, como en Ver un certificado.
hostname mismatch (62)
echo | openssl s_client -brief -connect blog.lab:443 -noservername -CAfile raiz-lab.crt -verify_hostname blog.lab
salida · tu computadoraverify error:num=62:hostname mismatch Verification error: hostname mismatch
El certificado no es para ese nombre. Revisa el SAN (no el CN) y que estés mandando el SNI correcto.
key values mismatch
openssl s_server -accept 8443 -cert tienda.crt -key blog.key
salida · el servidorUsing default temp DH parameters error setting private key 8017F8A7A57D0000:error:05800074:x509 certificate routines:ossl_x509_check_private_key:key values mismatch:../crypto/x509/x509_cmp.c:413:
La llave no corresponde al certificado. Usa la prueba de la llave pública para encontrar la buena.
bad decrypt y maybe wrong password
Contraseña equivocada, tanto en enc como en pkcs8 (lo viste con la e.firma). En un PFX el mensaje es otro: Mac verify error: invalid password?.
openssl pkcs12 -in tienda.pfx -info -noout
salida · tu computadoraMAC: sha256, Iteration 2048 MAC length: 32, salt length: 8 Mac verify error: invalid password?
unsupported y RC2-40-CBC
Un PFX viejo en OpenSSL 3 o 4: agrega -legacy, como en Convertir PEM, DER y PFX.
no start line / Expecting: …
OpenSSL esperaba un PEM y recibió otra cosa: un DER (agrega -inform DER), un archivo con otro contenido (revisa su primera línea) o un archivo pegado de Windows con caracteres raros al principio.
no protocols available
Pediste una versión de TLS que tu propio OpenSSL ya no permite, como TLS 1.0 o 1.1. Lo viste en s_client.
Resumen de órdenes
- ver un certificado:
openssl x509 -in cert.crt -noout -text - fechas y dominios:
openssl x509 -in cert.crt -noout -dates -ext subjectAltName - probar un servidor:
openssl s_client -connect sitio:443 -servername sitio -brief - ¿vence en 30 días?:
openssl x509 -in cert.crt -noout -checkend 2592000 - llave EC:
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out sitio.key - CSR con SAN:
openssl req -new -key sitio.key -subj "/CN=sitio" -addext "subjectAltName=DNS:sitio" -out sitio.csr - autofirmado:
openssl req -x509 -newkey EC -pkeyopt ec_paramgen_curve:P-256 -noenc -keyout p.key -out p.crt -days 30 -subj "/CN=localhost" - ¿son pareja?:
openssl pkey -in sitio.key -pubout | openssl sha256 (y lo mismo con x509 -pubkey) - PEM a DER:
openssl x509 -in cert.crt -outform DER -out cert.cer - crear un PFX:
openssl pkcs12 -export -inkey sitio.key -in cert.crt -certfile intermedia.crt -out sitio.pfx - sacar del PFX:
openssl pkcs12 -in sitio.pfx -noenc -out todo.pem (-legacy si es viejo) - e.firma a PEM:
openssl x509 -inform DER -in archivo.cer -out cer.pem; openssl pkcs8 -inform DER -in archivo.key -out key.pem - hash de un archivo:
openssl dgst -sha256 archivo - un secreto aleatorio:
openssl rand -hex 32 - verificar la cadena:
openssl verify -CAfile raiz.crt -untrusted intermedia.crt cert.crt
Con esto ya puedes revisar y arreglar casi cualquier problema de certificados desde la terminal. Para la parte del servidor sigue con Nginx desde cero, que instala HTTPS con Let’s Encrypt, y con Asegurar SSH en tu servidor. Para entender lo que pasa durante la conexión que s_client te enseña, está HTTPS por dentro.
Fuentes: páginas de manual de OpenSSL (x509, s_client, req, genpkey, pkey, pkcs8, pkcs12, enc, dgst, verify); anuncio de OpenSSL 4.1 y de OpenSSL 4.0; phpcfdi/credentials (formato del .cer y el .key del SAT); manual de FirmaSAT (número de certificado en el serial). Laboratorio: Debian 13.7 con OpenSSL 3.5.7, nginx y curl, y OpenSSL 4.1.0 compilado desde el código oficial; todo en contenedores desechables. Órdenes ejecutadas el 10 de noviembre de 2026 (las horas de las salidas están en UTC).
