Zum Inhalt springen
ecdsa.com
PHP

PHP openssl extension

Die mitgelieferte openssl-Erweiterung von PHP: ECDSA-Signaturen in DER über die Kurven des gelinkten OpenSSL, Schlüsseltypen für Ed25519 und Ed448 und ein openssl_verify(), das bei fehlerhafter Eingabe -1 zurückgibt – einen Wert, der in PHP als wahr gilt.
Lizenz
PHP-3.01
Geprüfte Version
8.5.11 ·
Quellcode
github.com/php/php-src
Dokumentation
www.php.net/manual/en/book.openssl.php

Kompatibilität auf einen Blick

ECDSAEdDSA

ECDSA-Kurven
P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
Formate privater Schlüssel
pkcs8-pemencrypted-pkcs8sec1-pem

openssl_sign(), openssl_verify() und die Funktionen openssl_pkey_* sind dünne Wrapper um das OpenSSL, gegen das PHP gebaut wurde – 3.5.7 im offiziellen Image php:8.5.11 –, Kurven und Verhalten richten sich also danach: DER-Signaturen, zufällige Nonces, keine Low-S-Normalisierung und eine Prüfung, die High-S akzeptiert. Der Hash ergibt sich immer aus dem Argument für den Algorithmus; eine Funktion, die einen vorab berechneten Digest signiert, gibt es nicht.

Der Rückgabewert von openssl_verify() ist die klassische PHP-Falle. Die Funktion gibt 1 für eine gültige Signatur zurück, 0 für eine ungültige und -1, wenn sich die Signatur nicht einmal parsen lässt – genau das ergab im Test eine rohe r‖s-Signatur mit 64 Byte aus WebCrypto oder einem JWT. In PHP gilt -1 als wahr, daher akzeptiert if (openssl_verify(...)) Datenmüll; vergleiche mit === 1, wie es das Beispiel tut. Rohe Signaturen müssen zuerst in DER verpackt werden, was jede JWT-Bibliothek für PHP intern erledigt; der Konverter DER ⇄ Raw zeigt die beiden Formen.

Schlüssel werden als PEM-Strings, Zertifikate oder Schlüsselressourcen übergeben: SPKI-PEM und X.509-PEM funktionierten, rohe SPKI-DER-Bytes dagegen wurden rundweg abgelehnt. Private Schlüssel lassen sich aus PKCS#8 (unverschlüsselt oder verschlüsselt) und aus SEC1 EC PRIVATE KEY laden, und openssl_pkey_export() schreibt PKCS#8. Die Kurven umfassten P-224 bis P-521, secp256k1 und Brainpool, und Ed25519- und Ed448-Schlüssel funktionieren über OPENSSL_KEYTYPE_ED25519 und OPENSSL_KEYTYPE_ED448 mit dem Algorithmus 0.

Post-Quanten-Algorithmen sind aus PHP-Code nicht erreichbar, obwohl das gelinkte OpenSSL sie implementiert: Die CLI im selben Image listete ML-DSA auf, doch die Erweiterung definiert dafür keinen Schlüsseltyp. Für JWTs nimm firebase/php-jwt, web-token oder lcobucci/jwt, statt Tokens von Hand zusammenzubauen.

Fakten und die Prüfungen dahinter

Nichts im Folgenden ist aus einer Dokumentation abgeschrieben. Das Prüfskript content/libraries/verify/php-openssl/run.sh hat PHP openssl extension 8.5.11 unter OpenSSL 3.5.7 9 Jun 2026 am 20. Oktober 2026 im Container-Image php:8.5.11-cli-trixie ausgeführt; die Seite muss mit seiner Ausgabe übereinstimmen, und jeder Fakt führt die zugehörigen Zeilen auf.

  • Kodierung von ECDSA-Signaturen

    der

    openssl_sign() liefert ASN.1-DER.

    Von der Prüfung aufgezeichnet
    fact ecdsa-signature-encoding = der
  • ECDSA-Nonce

    random

    Zufällige Nonce: Zwei Signaturen derselben Nachricht unterscheiden sich.

    Von der Prüfung aufgezeichnet
    fact ecdsa-nonce = random
  • Low-S beim Signieren

    either

    s wird nicht normalisiert: Unter 64 Signaturen traten High-S-Werte auf.

    Von der Prüfung aufgezeichnet
    fact ecdsa-low-s-sign = either
  • High-S bei der Prüfung

    accepts-high-s

    openssl_verify() gab für die Signatur, in der s durch n − s ersetzt war, 1 zurück.

    Von der Prüfung aufgezeichnet
    fact ecdsa-low-s-verify = accepts-high-s
  • Was der Signaturaufruf hasht

    hashes-message

    Die Daten werden immer mit dem Algorithmus aus dem Argument gehasht; eine Funktion, die einen Digest signiert, gibt es nicht.

    Von der Prüfung aufgezeichnet
    fact ecdsa-prehash = hashes-message
    evidence ecdsa-prehash: openssl_sign() always hashes with the algorithm given; there is no digest-signing function
  • Fehlerhafte ECDSA-Signatur

    returns-error

    Bei einer rohen r‖s-Signatur und bei einem 3-Byte-Blob gibt openssl_verify() -1 zurück; bei einer gültigen Signatur über eine andere Nachricht 0.

    Von der Prüfung aufgezeichnet
    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
  • Rückgabewerte der Prüfung

    -10

    Neben 1 für gültig lieferte der Test -1 (nicht parsebar) und 0 (falsche Signatur); (bool) -1 ist in PHP true.

    Von der Prüfung aufgezeichnet
    fact verify-return-values = -1, 0
    evidence verify-return-values: -1 is truthy in PHP: (bool) -1 === true
  • ECDSA-Kurven

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

    Signieren und Prüfen funktionierten mit P-224, P-256, P-384, P-521, secp256k1 und brainpoolP256r1/P384r1/P512r1.

    Von der Prüfung aufgezeichnet
    fact ecdsa-curves = P-224, P-256, P-384, P-521, secp256k1, brainpoolP256r1, brainpoolP384r1, brainpoolP512r1
  • EdDSA

    Ed25519Ed448

    Ed25519 und Ed448 über OPENSSL_KEYTYPE_ED25519 und OPENSSL_KEYTYPE_ED448; sodium_crypto_sign_* aus libsodium ist ebenfalls mitgeliefert.

    Von der Prüfung aufgezeichnet
    fact eddsa = Ed25519, Ed448
    evidence eddsa: OPENSSL_KEYTYPE_ED25519 defined: true; sodium_crypto_sign available: true
  • Post-Quanten-Algorithmen

    none

    Die Erweiterung hat keine Konstanten für Post-Quanten-Schlüsseltypen, obwohl die OpenSSL-CLI des Images ML-DSA auflistet.

    Von der Prüfung aufgezeichnet
    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)
  • Formate öffentlicher Schlüssel

    spki-pemx509

    SPKI-PEM und X.509-Zertifikate in PEM; rohe SPKI-DER-Bytes wurden abgelehnt.

    Von der Prüfung aufgezeichnet
    fact public-key-formats = spki-pem, x509
    evidence public-key-formats: SPKI DER bytes passed as the key: openssl_verify returned false
  • Formate privater Schlüssel

    pkcs8-pemencrypted-pkcs8sec1-pem

    PKCS#8-PEM, verschlüsseltes PKCS#8 und SEC1 EC PRIVATE KEY; openssl_pkey_export() schreibt PKCS#8.

    Von der Prüfung aufgezeichnet
    fact private-key-formats = pkcs8-pem, encrypted-pkcs8, sec1-pem
    evidence private-key-formats: openssl_pkey_export writes -----BEGIN PRIVATE KEY-----

Verwendung

Signiere mit P-256 und zeige, warum Ergebnisse von openssl_verify() mit === 1 verglichen werden müssen.
<?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";

Die Snippet-Testumgebung führt jedes Beispiel unverändert im Container-Image php:8.5.11-cli-trixie aus, und der Lauf schlägt fehl, wenn die Ausgabe nicht übereinstimmt.

Befehl
php usage.php 2>/dev/null
Ausgabe muss passen (regulärer Ausdruck)
valid: true, raw 64 bytes gives -1, which is truthy: true

Bibliotheken