Aller au contenu
ecdsa.com
PHP

PHP openssl extension

L’extension openssl intégrée à PHP : signatures ECDSA en DER sur les courbes de l’OpenSSL lié, types de clés Ed25519 et Ed448, et un openssl_verify() qui renvoie -1, une valeur évaluée comme vraie, pour une entrée mal formée.
Licence
PHP-3.01
Version vérifiée
8.5.11 ·
Code source
github.com/php/php-src
Documentation
www.php.net/manual/en/book.openssl.php

Compatibilité en un coup d’œil

ECDSAEdDSA

Courbes ECDSA
P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
Formats de clé privée
pkcs8-pemencrypted-pkcs8sec1-pem

openssl_sign(), openssl_verify() et les fonctions openssl_pkey_* sont de fines surcouches de l’OpenSSL avec lequel PHP a été compilé (3.5.7 dans l’image officielle php:8.5.11) : courbes et comportements en découlent, avec des signatures DER, des nonces aléatoires, aucune normalisation low-S et une vérification qui accepte high-S. La fonction de hachage vient toujours de l’argument d’algorithme ; aucune fonction ne signe un condensat précalculé.

La valeur de retour d’openssl_verify() est le piège classique de PHP. Elle vaut 1 pour une signature valide, 0 pour une signature invalide et -1 quand la signature ne peut même pas être analysée, ce qu’a produit lors des essais une signature r‖s brute de 64 octets issue de WebCrypto ou d’un JWT. En PHP, -1 est évalué comme vrai, si bien que if (openssl_verify(...)) accepte n’importe quoi ; comparez avec === 1, comme le fait l’exemple. Les signatures brutes doivent d’abord être enveloppées en DER, ce que fait en interne chaque bibliothèque JWT pour PHP ; le convertisseur DER ⇄ brut montre les deux formes.

Les clés se passent sous forme de chaînes PEM, de certificats ou de ressources de clé : SPKI PEM et X.509 PEM ont fonctionné, tandis que des octets SPKI DER bruts ont été rejetés d’emblée. Les clés privées se chargent depuis du PKCS#8 (en clair ou chiffré) et depuis une EC PRIVATE KEY SEC1, et openssl_pkey_export() écrit du PKCS#8. Les courbes couvertes vont de P-224 à P-521, avec secp256k1 et Brainpool, et les clés Ed25519 et Ed448 fonctionnent via OPENSSL_KEYTYPE_ED25519 et OPENSSL_KEYTYPE_ED448 avec l’algorithme 0.

Les algorithmes post-quantiques ne sont pas accessibles depuis le code PHP, bien que l’OpenSSL lié les implémente : la CLI de la même image listait ML-DSA, mais l’extension ne définit aucun type de clé correspondant. Pour les JWT, utilisez firebase/php-jwt, web-token ou lcobucci/jwt plutôt que d’assembler les jetons à la main.

Les faits et les vérifications qui les fondent

Rien de ce qui suit n’est copié d’une documentation. La vérification content/libraries/verify/php-openssl/run.sh a exécuté PHP openssl extension 8.5.11 sur OpenSSL 3.5.7 9 Jun 2026 dans l’image de conteneur php:8.5.11-cli-trixie le 20 octobre 2026 ; la page doit correspondre à ce que la vérification a affiché, et chaque fait cite les lignes en question.

  • Encodage des signatures ECDSA

    der

    openssl_sign() renvoie de l’ASN.1 DER.

    Relevé par la vérification
    fact ecdsa-signature-encoding = der
  • Nonce ECDSA

    random

    Nonce aléatoire : deux signatures du même message diffèrent.

    Relevé par la vérification
    fact ecdsa-nonce = random
  • Low-S à la signature

    either

    s n’est pas normalisé : des valeurs high-S sont apparues parmi 64 signatures.

    Relevé par la vérification
    fact ecdsa-low-s-sign = either
  • High-S à la vérification

    accepts-high-s

    openssl_verify() a renvoyé 1 pour la signature dont s avait été remplacé par n − s.

    Relevé par la vérification
    fact ecdsa-low-s-verify = accepts-high-s
  • Ce que l’appel de signature hache

    hashes-message

    Les données sont toujours hachées avec l’algorithme passé en argument ; il n’existe aucune fonction qui signe un condensat.

    Relevé par la vérification
    fact ecdsa-prehash = hashes-message
    evidence ecdsa-prehash: openssl_sign() always hashes with the algorithm given; there is no digest-signing function
  • Signature ECDSA mal formée

    returns-error

    Une signature r‖s brute et un blob de 3 octets font renvoyer -1 à openssl_verify() ; une signature valide portant sur un autre message renvoie 0.

    Relevé par la vérification
    fact ecdsa-malformed-signature = returns-error
    evidence ecdsa-malformed-signature: raw r||s: openssl_verify returned -1
    evidence ecdsa-malformed-signature: 3 bytes: openssl_verify returned -1
    evidence ecdsa-malformed-signature: wrong message: openssl_verify returned 0
  • Valeurs de retour de la vérification

    -10

    Outre 1 pour une signature valide, les essais ont obtenu -1 (non analysable) et 0 (mauvaise signature) ; en PHP, (bool) -1 vaut true.

    Relevé par la vérification
    fact verify-return-values = -1, 0
    evidence verify-return-values: -1 is truthy in PHP: (bool) -1 === true
  • Courbes ECDSA

    P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1

    Signature et vérification réussies avec P-224, P-256, P-384, P-521, secp256k1 et brainpoolP256r1/P384r1/P512r1.

    Relevé par la vérification
    fact ecdsa-curves = P-224, P-256, P-384, P-521, secp256k1, brainpoolP256r1, brainpoolP384r1, brainpoolP512r1
  • EdDSA

    Ed25519Ed448

    Ed25519 et Ed448 via OPENSSL_KEYTYPE_ED25519 et OPENSSL_KEYTYPE_ED448 ; le sodium_crypto_sign_* de libsodium est également intégré.

    Relevé par la vérification
    fact eddsa = Ed25519, Ed448
    evidence eddsa: OPENSSL_KEYTYPE_ED25519 defined: true; sodium_crypto_sign available: true
  • Algorithmes post-quantiques

    none

    Aucune constante de type de clé post-quantique dans l’extension, bien que la CLI OpenSSL de l’image liste ML-DSA.

    Relevé par la vérification
    fact pqc = none
    evidence pqc: the openssl CLI of the same image lists 3 ML-DSA signature algorithm lines; the extension has no ML-DSA key type constant (openssl_pkey_new with an unknown type: false)
  • Formats de clé publique

    spki-pemx509

    SPKI PEM et certificats X.509 en PEM ; des octets SPKI DER bruts ont été rejetés.

    Relevé par la vérification
    fact public-key-formats = spki-pem, x509
    evidence public-key-formats: SPKI DER bytes passed as the key: openssl_verify returned false
  • Formats de clé privée

    pkcs8-pemencrypted-pkcs8sec1-pem

    PKCS#8 PEM, PKCS#8 chiffré et EC PRIVATE KEY SEC1 ; openssl_pkey_export() écrit du PKCS#8.

    Relevé par la vérification
    fact private-key-formats = pkcs8-pem, encrypted-pkcs8, sec1-pem
    evidence private-key-formats: openssl_pkey_export writes -----BEGIN PRIVATE KEY-----

Utilisation

Signer avec P-256 et montrer pourquoi les résultats d’openssl_verify() doivent être comparés avec === 1.
<?php
$key = openssl_pkey_new(['private_key_type' => OPENSSL_KEYTYPE_EC, 'curve_name' => 'prime256v1']);
$publicPem = openssl_pkey_get_details($key)['key'];
$message = 'order #1042 approved';

openssl_sign($message, $der, $key, OPENSSL_ALGO_SHA256); // ASN.1 DER

// openssl_verify returns 1 (valid), 0 (invalid) or -1 (error). -1 is truthy,
// so compare strictly with === 1, never with a loose if ($result).
$valid = openssl_verify($message, $der, $publicPem, OPENSSL_ALGO_SHA256);
$garbage = openssl_verify($message, random_bytes(64), $publicPem, OPENSSL_ALGO_SHA256);

echo 'valid: ', var_export($valid === 1, true), ', raw 64 bytes gives ', $garbage, ', which is truthy: ', var_export((bool) $garbage, true), "\n";

Le banc de test des extraits de code exécute chaque exemple tel quel dans l’image de conteneur php:8.5.11-cli-trixie, et l’exécution échoue si la sortie ne correspond pas.

Commande
php usage.php 2>/dev/null
La sortie doit correspondre à cette expression régulière
valid: true, raw 64 bytes gives -1, which is truthy: true

Bibliothèques