O OpenSSL fornece libcrypto e libssl para C e, por meio de bindings, para Node.js, Python, PHP e Ruby, além do comando openssl, que é o primeiro contato da maioria das pessoas com ele. Os fatos desta página foram verificados no pacote de distribuição do Alpine 3.24.2, OpenSSL 3.5.9 (a linha 3.5 de suporte de longo prazo); as versões mais recentes do projeto original na época eram a 3.6.5 e a 4.0.3, então o comportamento pode ser diferente nelas.
As assinaturas ECDSA de EVP_DigestSign e de openssl dgst -sign são ASN.1 DER — de 70 a 72 bytes na P-256. Os nonces são aleatórios, a menos que você defina o parâmetro nonce-type como 1 (OpenSSL 3.2+), o que reproduziu o vetor de teste P-256 da RFC 6979; na linha de comando, esse parâmetro só é acessível pelo pkeyutl, como mostra o exemplo. O s não é normalizado, e a verificação aceita high-S. EVP_PKEY_sign assina um digest calculado por você.
Uma entrada malformada é um erro, e não um resultado falso: EVP_DigestVerify retornou -1 para uma assinatura r‖s bruta de 64 bytes, e a CLI deixou isso claro. openssl dgst -verify imprimiu Verified OK para uma assinatura correta, Verification failure para uma assinatura válida sobre outros dados e Error verifying data para 64 bytes que não são DER — então, na 3.5.9, dá para distinguir um problema de formato de uma falsificação pela redação da mensagem. Converta assinaturas brutas com o conversor DER ⇄ bruto ou deixe o construtor de comandos OpenSSL montar a chamada certa.
As curvas incluem de P-224 a P-521, secp256k1 e as curvas Brainpool r1, além de Ed25519 e Ed448. Desde a 3.5, o provedor padrão também implementa ML-KEM, ML-DSA e SLH-DSA e o grupo híbrido de TLS X25519MLKEM768. As chaves vêm como chaves privadas PKCS#8 (simples ou criptografadas) ou SEC1 e como chaves públicas SPKI ou de certificado: genpkey grava -----BEGIN PRIVATE KEY-----, e o comando legado openssl ec grava -----BEGIN EC PRIVATE KEY----- — um arquivo com o rótulo EC é sempre uma chave privada.
A maioria das mensagens de erro que as pessoas pesquisam vem do carregamento de chaves — no start line, header too long, unsupported do decodificador — e está explicada nas páginas de erro vinculadas.