RSA-PSS frente a PKCS#1 v1.5 es una elección sobre cómo se codifica un hash antes de la operación RSA con la clave privada. Ambos proceden de PKCS #1 (RFC 8017), ambos están aprobados en FIPS 186-5 y ambos funcionan con el mismo par de claves RSA. La diferencia está en sus argumentos de seguridad, en su determinismo y en los parámetros que deben coincidir entre el firmante y el verificador.
De un vistazo
| RSASSA-PSS | RSASSA-PKCS1-v1_5 | |
|---|---|---|
| Especificación | RFC 8017 §8.1, codificación EMSA-PSS §9.1 | RFC 8017 §8.2, codificación EMSA-PKCS1-v1_5 §9.2 |
| Codificación | Hash con sal, enmascarado con MGF1 | 0x00 01 FF…FF 00 ‖ DigestInfo |
| Aleatorizado | Sí (salvo que la longitud de la sal sea 0) | No: la misma clave y el mismo mensaje dan la misma firma |
| Demostración de seguridad | Reducción ajustada a RSA en el modelo del oráculo aleatorio | Ninguna demostración comparable; ningún ataque práctico contra verificadores correctos |
| Parámetros que hay que acordar | Hash, hash de MGF, longitud de la sal, campo final (trailer) | Solo el hash |
| Tamaño de firma | Tamaño del módulo (256 bytes para RSA-2048) | Igual |
| Velocidad | La misma operación RSA | Igual |
| JOSE | PS256, PS384, PS512 (sal = longitud del hash) | RS256, RS384, RS512 |
| TLS 1.3 | Obligatorio para CertificateVerify con RSA (rsa_pss_rsae_sha256 0x0804, rsa_pss_pss_sha256 0x0809) | Solo para las firmas dentro de los certificados (rsa_pkcs1_sha256 0x0401) |
| X.509 | id-RSASSA-PSS 1.2.840.113549.1.1.10 con parámetros | sha256WithRSAEncryption 1.2.840.113549.1.1.11 |
| Restricción de FIPS 186-5 | Longitud de la sal entre 0 y la longitud del hash | Comprobaciones de DigestInfo en §5.4(h) |
Diferencias de seguridad
El relleno de firma de PKCS#1 v1.5 data de 1991. Es determinista y nunca ha tenido una demostración de seguridad tan limpia como la de PSS, pero tras tres décadas no existe ninguna falsificación práctica contra los verificadores que comprueban el bloque codificado completo. Los fallos reales fueron errores de implementación: el ataque de Bleichenbacher de 2006 falsificaba firmas con exponente público 3 frente a analizadores que ignoraban los bytes sobrantes después de DigestInfo, y variantes de ese error reaparecieron en bibliotecas como NSS (BERserk, 2014) y otras. El RFC 8017 §8.2.2 evita toda esa clase de fallos volviendo a codificar el bloque esperado y comparando los bytes, y FIPS 186-5 §5.4(h) fija esas comprobaciones como norma.
RSASSA-PSS (Bellare y Rogaway, estandarizado en PKCS #1 v2.1, 2002) calcula el hash de una sal aleatoria junto con el hash del mensaje y enmascara el resultado con MGF1. Su seguridad se reduce de forma ajustada al problema RSA en el modelo del oráculo aleatorio, y su aleatorización hace que un fallo inducido o un canal lateral en una firma revele menos. Con una sal de longitud cero, PSS pasa a ser determinista, pero conserva su demostración bajo supuestos ligeramente distintos.
No confundas ninguno de los dos con el relleno de cifrado de PKCS #1 v1.5, que sí está roto por los ataques de oráculo de relleno (Bleichenbacher 1998, ROBOT 2017) y se eliminó de TLS 1.3. El relleno de firma y el de cifrado son esquemas distintos que comparten nombre.
Interoperabilidad: la trampa de la longitud de la sal
PSS exige que el firmante y el verificador acuerden tres parámetros: el hash, el hash de MGF1 y la longitud de la sal. JOSE fija los tres (PS256 significa SHA-256, MGF1-SHA-256 y sal de 32 bytes), y TLS 1.3 exige que la longitud de la sal sea igual a la del hash. En los demás casos tienes que comprobarlo: OpenSSL 1.1.1 y 3.0 firmaban por defecto con la sal máxima (222 bytes para RSA-2048 con SHA-256), mientras que OpenSSL 3.1 y posteriores usan por defecto auto-digestmax, que produce una sal del tamaño del resumen. Así, los verificadores configurados con una longitud de sal fija rechazan las firmas de la otra generación.
El script siguiente, ejecutado con OpenSSL 3.6.5, muestra ambos comportamientos.
#!/bin/sh
dir=$(mktemp -d)
cd "$dir" || exit 1
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out key.pem 2>/dev/null
openssl pkey -in key.pem -pubout -out pub.pem
printf 'hello' > msg
openssl dgst -sha256 -sign key.pem -out v15a.sig msg
openssl dgst -sha256 -sign key.pem -out v15b.sig msg
cmp -s v15a.sig v15b.sig && echo "PKCS#1 v1.5: identical signatures" || echo "PKCS#1 v1.5: different signatures"
openssl dgst -sha256 -sign key.pem -sigopt rsa_padding_mode:pss -out pssa.sig msg
openssl dgst -sha256 -sign key.pem -sigopt rsa_padding_mode:pss -out pssb.sig msg
cmp -s pssa.sig pssb.sig && echo "PSS: identical signatures" || echo "PSS: different signatures"
for saltlen in digest max; do
printf 'verify PSS with rsa_pss_saltlen:%s -> ' "$saltlen"
openssl dgst -sha256 -verify pub.pem -sigopt rsa_padding_mode:pss \
-sigopt "rsa_pss_saltlen:$saltlen" -signature pssa.sig msg 2>&1 | grep -o -E "Verified OK|salt length check failed"
done
cd / && rm -rf "$dir"
PKCS#1 v1.5: identical signatures
PSS: different signatures
verify PSS with rsa_pss_saltlen:digest -> Verified OK
verify PSS with rsa_pss_saltlen:max -> salt length check failed
Los verificadores que aceptan auto detectan la longitud de la sal a partir de la firma, que es la opción más compatible cuando no controlas el firmante.
Claves y certificados
La misma clave RSA puede producir los dos tipos de firma, y una clave rsaEncryption de un certificado sirve para ambos (TLS lo llama rsa_pss_rsae). X.509 también define claves id-RSASSA-PSS cuyos parámetros las restringen a PSS (rsa_pss_pss en TLS); impiden el uso cruzado entre esquemas, pero muchas herramientas y algunos navegadores las gestionan mal, así que las claves rsaEncryption que firman con PSS son la opción práctica. Las firmas de certificados de la WebPKI son casi todas PKCS#1 v1.5 o ECDSA, y solo por eso los verificadores tienen que admitir v1.5.
Cuándo elegir cada uno
- Handshakes de TLS 1.3: PSS, obligatorio; tu biblioteca lo hace automáticamente con cualquier certificado RSA.
- JWT:
PS256cuando todos los consumidores lo admitan yRS256en caso contrario; mejor aún, plantéateES256(consulta ES256 frente a RS256). - Certificados X.509 y CRL que emites: PKCS#1 v1.5 (
sha256WithRSAEncryption) para la máxima compatibilidad, salvo que un ecosistema cerrado haya adoptado PSS como estándar. - Firmas nuevas de documentos, firmware o API con claves RSA: PSS con una longitud de sal igual a la del hash.
- Interoperabilidad con HSM y KMS en la nube: se admiten los dos esquemas (por ejemplo,
RSASSA_PSS_SHA_256yRSASSA_PKCS1_V1_5_SHA_256en AWS KMS); ajústate a lo que espera el verificador. - Verificación de cualquiera de los dos: usa la función de verificación de alto nivel de una biblioteca, nunca una decodificación manual del mensaje codificado.
Notas de migración
Pasar de v1.5 a PSS no requiere claves nuevas, lo que facilita hacerlo por etapas.
- Primero, haz que los verificadores acepten PSS: en JOSE, permite
PS256para la clave (la regla sigue siendo un algoritmo por clave, así que publica el mismo material de clave con unkidnuevo yalgPS256, o rota la clave). - Fija los parámetros de forma explícita: hash SHA-256, MGF1 SHA-256, longitud de sal de 32 bytes; evita los valores por defecto de las bibliotecas, que cambian de una versión a otra.
- Cambia los firmantes y vigila los fallos de verificación que mencionen la longitud de la sal o el relleno.
- Deja de aceptar v1.5 para esas claves cuando hayan caducado las firmas antiguas, y mantén v1.5 solo donde lo exijan los certificados o terceros.
- Si de todos modos vas a rotar claves, valora en su lugar ECDSA P-256 o Ed25519, que son más pequeños y firman más rápido que RSA con cualquier relleno.