Pular para o conteúdo
ecdsa.com

RSA-PSS vs PKCS#1 v1.5: padding de assinaturas RSA

As mesmas chaves RSA, os mesmos tamanhos, a mesma velocidade. O PSS tem uma prova de segurança e é obrigatório nos handshakes do TLS 1.3; o PKCS#1 v1.5 está em toda parte e não tem nenhuma quebra prática quando verificado corretamente.

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-PSSRSASSA-PKCS1-v1_5
EspecificaçãoRFC 8017 §8.1, codificação EMSA-PSS §9.1RFC 8017 §8.2, codificação EMSA-PKCS1-v1_5 §9.2
CodificaçãoHash com salt, mascarado com MGF10x00 01 FF…FF 00 ‖ DigestInfo
AleatorizadaSim (exceto quando o tamanho do salt é 0)Não: a mesma chave e a mesma mensagem geram a mesma assinatura
Prova de segurançaRedução estrita (tight) ao RSA no modelo do oráculo aleatórioNenhuma prova comparável; nenhum ataque prático contra verificadores corretos
Parâmetros a combinarHash, hash do MGF, tamanho do salt, trailerApenas o hash
Tamanho da assinaturaTamanho do módulo (256 bytes para RSA-2048)Igual
VelocidadeA mesma operação RSAIgual
JOSEPS256, PS384, PS512 (salt = tamanho do hash)RS256, RS384, RS512
TLS 1.3Obrigató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.509id-RSASSA-PSS 1.2.840.113549.1.1.10 com parâmetrossha256WithRSAEncryption 1.2.840.113549.1.1.11
Restrição do FIPS 186-5Tamanho do salt entre 0 e o tamanho do hashVerificaçõ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.

OpenSSL: determinismo e verificação do tamanho do salt no PSS
#!/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"
Saída
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: PS256 quando todos os consumidores o aceitarem, RS256 caso contrário; melhor ainda, considere ES256 (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_256 e RSASSA_PKCS1_V1_5_SHA_256 no 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.

  1. Ensine o PSS aos verificadores primeiro: no JOSE, permita PS256 para a chave (um algoritmo por chave continua sendo a regra, então publique o mesmo material de chave sob um novo kid com alg PS256, ou faça a rotação).
  2. 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.
  3. Passe os signatários para o PSS e fique atento a falhas de verificação que mencionem o tamanho do salt ou o padding.
  4. 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.
  5. 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.

Referências

Todas as comparações