RSA-PSS vs PKCS#1 v1.5 é uma escolha sobre como um hash é codificado antes da operação RSA com a chave privada. Os dois vêm do PKCS #1 (RFC 8017), os dois são aprovados no FIPS 186-5 e os dois funcionam com o mesmo par de chaves RSA. A diferença está nos argumentos de segurança, no determinismo e nos parâmetros que precisam coincidir entre o signatário e o verificador.
Visão geral
| RSASSA-PSS | RSASSA-PKCS1-v1_5 | |
|---|---|---|
| Especificação | RFC 8017 §8.1, codificação EMSA-PSS §9.1 | RFC 8017 §8.2, codificação EMSA-PKCS1-v1_5 §9.2 |
| Codificação | Hash com salt, mascarado com MGF1 | 0x00 01 FF…FF 00 ‖ DigestInfo |
| Aleatorizada | Sim (exceto quando o tamanho do salt é 0) | Não: a mesma chave e a mesma mensagem geram a mesma assinatura |
| Prova de segurança | Redução estrita (tight) ao RSA no modelo do oráculo aleatório | Nenhuma prova comparável; nenhum ataque prático contra verificadores corretos |
| Parâmetros a combinar | Hash, hash do MGF, tamanho do salt, trailer | Apenas o hash |
| Tamanho da assinatura | Tamanho do módulo (256 bytes para RSA-2048) | Igual |
| Velocidade | A mesma operação RSA | Igual |
| JOSE | PS256, PS384, PS512 (salt = tamanho do hash) | RS256, RS384, RS512 |
| TLS 1.3 | Obrigatório no CertificateVerify com RSA (rsa_pss_rsae_sha256 0x0804, rsa_pss_pss_sha256 0x0809) | Apenas para assinaturas dentro de certificados (rsa_pkcs1_sha256 0x0401) |
| X.509 | id-RSASSA-PSS 1.2.840.113549.1.1.10 com parâmetros | sha256WithRSAEncryption 1.2.840.113549.1.1.11 |
| Restrição do FIPS 186-5 | Tamanho do salt entre 0 e o tamanho do hash | Verificações do DigestInfo no §5.4(h) |
Diferenças de segurança
O padding de assinatura PKCS#1 v1.5 é de 1991. Ele é determinístico e nunca teve uma prova de segurança tão limpa quanto a do PSS, mas, depois de três décadas, não existe nenhuma falsificação prática contra verificadores que conferem o bloco codificado inteiro. As quebras no mundo real foram bugs de implementação: o ataque de Bleichenbacher de 2006 falsificava assinaturas com expoente público 3 contra parsers que ignoravam bytes extras depois do DigestInfo, e variantes desse erro reapareceram em bibliotecas como o NSS (BERserk, 2014) e outras. O §8.2.2 da RFC 8017 evita a classe inteira de falhas recodificando o bloco esperado e comparando os bytes, e o §5.4(h) do FIPS 186-5 formaliza essas verificações.
O RSASSA-PSS (Bellare e Rogaway, padronizado no PKCS #1 v2.1, em 2002) calcula o hash de um salt aleatório junto com o hash da mensagem e mascara o resultado com MGF1. Sua segurança se reduz de forma estrita ao problema RSA no modelo do oráculo aleatório, e a aleatorização faz com que uma falha ou um canal lateral em uma assinatura revele menos. Com um salt de tamanho zero, o PSS se torna determinístico, mas mantém sua prova sob hipóteses um pouco diferentes.
Não confunda nenhum dos dois com o padding de criptografia do PKCS #1 v1.5, que é de fato quebrado por ataques de padding oracle (Bleichenbacher 1998, ROBOT 2017) e foi removido do TLS 1.3. O padding de assinatura e o padding de criptografia são esquemas separados que compartilham um nome.
Interoperabilidade: a armadilha do tamanho do salt
O PSS exige que signatário e verificador concordem em três parâmetros: o hash, o hash do MGF1 e o tamanho do salt. O JOSE fixa os três (PS256 significa SHA-256, MGF1-SHA-256 e salt de 32 bytes), e o TLS 1.3 exige que o tamanho do salt seja igual ao tamanho do hash. Em outros contextos, é preciso conferir: o OpenSSL 1.1.1 e o 3.0 assinavam por padrão com o salt máximo (222 bytes para RSA-2048 com SHA-256), enquanto o OpenSSL 3.1 e versões posteriores usam por padrão auto-digestmax, que gera um salt do tamanho do digest. Verificadores configurados com um tamanho de salt fixo, então, rejeitam as assinaturas da outra geração.
O script abaixo, executado com o OpenSSL 3.6.5, mostra os dois comportamentos.
#!/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
Verificadores que aceitam auto detectam o tamanho do salt a partir da assinatura, que é a configuração mais compatível quando você não controla o signatário.
Chaves e certificados
A mesma chave RSA pode produzir os dois tipos de assinatura, e uma chave rsaEncryption em um certificado serve para ambos (o TLS chama isso de rsa_pss_rsae). O X.509 também define chaves id-RSASSA-PSS, cujos parâmetros as restringem ao PSS (rsa_pss_pss no TLS); elas impedem o uso cruzado entre esquemas, mas muitas ferramentas e alguns navegadores lidam mal com elas, então chaves rsaEncryption que assinam com PSS são a escolha prática. As assinaturas de certificados na WebPKI são quase todas PKCS#1 v1.5 ou ECDSA, e os verificadores precisam oferecer suporte ao v1.5 só por esse motivo.
Quando escolher cada um
- Handshakes do TLS 1.3: PSS, obrigatório; sua biblioteca faz isso automaticamente com qualquer certificado RSA.
- JWTs:
PS256quando todos os consumidores o aceitarem,RS256caso contrário; melhor ainda, considereES256(veja ES256 vs RS256). - Certificados X.509 e CRLs que você emite: PKCS#1 v1.5 (
sha256WithRSAEncryption) para máxima compatibilidade, a menos que um ecossistema fechado adote o PSS como padrão. - Novas assinaturas de documentos, firmware ou API com chaves RSA: PSS com tamanho do salt igual ao tamanho do hash.
- Interoperabilidade com HSMs e KMS na nuvem: os dois têm suporte (por exemplo,
RSASSA_PSS_SHA_256eRSASSA_PKCS1_V1_5_SHA_256no AWS KMS); siga o que o verificador espera. - Verificação de qualquer um dos dois: use a função de verificação de alto nível de uma biblioteca, nunca a decodificação manual da mensagem codificada.
Notas de migração
Passar do v1.5 para o PSS não exige chaves novas, o que facilita fazer a mudança em etapas.
- Ensine o PSS aos verificadores primeiro: no JOSE, permita
PS256para a chave (um algoritmo por chave continua sendo a regra, então publique o mesmo material de chave sob um novokidcomalgPS256, ou faça a rotação). - Fixe os parâmetros explicitamente: hash SHA-256, MGF1 SHA-256, tamanho do salt de 32 bytes; evite os padrões de biblioteca que mudam entre versões.
- Passe os signatários para o PSS e fique atento a falhas de verificação que mencionem o tamanho do salt ou o padding.
- Remova a aceitação do v1.5 para essas chaves quando as assinaturas antigas tiverem expirado, mantendo o v1.5 apenas onde certificados ou terceiros o exigirem.
- Se você vai rotacionar as chaves de qualquer forma, considere ECDSA P-256 ou Ed25519, que são menores e mais rápidos para assinar do que qualquer padding RSA.