Navigation
 Startseite
 Fachbücher
 Anzeigenmarkt
 Forum
 Webmaster News
 Script Newsletter
 Push Meldungen
 Kontakt
 Script Installation
 Php
 Php Tutorials
 Lernpfade
 Webhoster Vergleich
 Impressum

Community-Bereich
 kostenlos Registrieren
 Anmelden
 Benutzerliste

Script Datenbank
 Script Archiv
 Script Top 20
 Screenshots
 Testberichte

Suche
 

Unsere Php Scripts
 Counter Script
 Umfrage Script
 Bilder Upload Script
 Terminverwaltung
 Simple PHP Forum
 RSS Grabber

Tools und Generatoren
 .htpasswd Generator
 md5 Generator
 base64 Generator
 Markdown to HTML
 Colorpicker
 Unix timestamp Tool
 Unit Test Generator
 TLD Liste
 Webkatalog‑Verzeichnis

Artfiles.de
Bietet Serviceorientierte...
https://www.Artfiles.de
Hosterplus.de
Bekommen Sie Speicherplatz (Webspace), Domains...
https://www.Hosterplus.de
 
 
 

PHP openssl_pkcs12_read() Function: p12-Container zerlegen

Sie befinden sich: Home > Php Tutorial > PHP openssl_pkcs12_read() F...

PHP openssl_pkcs12_read() Function: p12-Container zerlegen
Veröffentlicht am:
25.09.2026
Hits / Besucher:
12
Sprache:
  Deutsch
Tutorial Art:
eigenes
Eingetragen von:
Merkliste:
 
Beschreibung

Der Kunde schickt eine Datei mit der Endung .pfx, das Passwort kommt in einer zweiten Mail. Darin steckt alles, was die Anfrage an sein Portal braucht, nur kann PHP mit dieser Datei erst einmal nichts anfangen. Sie ist binär, sie ist verschlüsselt, und sie trägt drei verschiedene Dinge auf einmal.

Illustration zum Tutorial: PHP openssl_pkcs12_read() Function: p12-Container zerlegen

Aus der Hülle fallen drei Teile heraus, und genau dafür gibt es PHP openssl_pkcs12_read(). Dieses Tutorial zerlegt den Container. Was danach mit dem Zertifikat und dem Schlüssel geschieht, steht in den Tutorials zum Auslesen von Zertifikaten und zum Entschlüsseln.

Was in einem p12-Container steckt

PKCS#12 ist eine Verpackung und kein Zertifikatsformat. In der Hülle liegen ein Zertifikat, der dazu passende private Schlüssel und meistens noch die Zwischenzertifikate der ausstellenden Stelle, also die Zertifikatskette. Ein Passwort verschlüsselt das Ganze, damit der private Schlüssel den Weg per Mail oder USB-Stick unbeschadet hinter sich bringt.

Die Endungen .p12 und .pfx meinen dasselbe Format. Das pfx stammt aus der Microsoft-Welt, und daher kommen auch die meisten dieser Dateien: Wer im Windows-Zertifikatsspeicher ein Zertifikat samt privatem Schlüssel exportiert, bekommt genau so einen Container. PHP openssl_pkcs12_read() schaut nicht auf die Endung, sondern auf den Inhalt, und liest deshalb beide.

Erzeugt wird an dieser Stelle nichts. Ein frisches Schlüsselpaar entsteht mit openssl_pkey_new() für RSA- und EC-Schlüssel, hier wird ein vorhandener Schlüssel nur aus seiner Verpackung geholt.

Syntax: die Signatur im Original

Zwei Angaben in der englischen Beschreibung von PHP openssl_pkcs12_read() entscheiden darüber, ob der erste Versuch gelingt:

openssl_pkcs12_read(

string $pkcs12,
array &$certificates,
string $passphrase
): bool

/* pkcs12 - the contents of the certificate
store, not its file name
certificates - filled with the parsed data on
success, untouched on failure
passphrase - the password used to unlock the
certificate store
return value - true on success, false on failure */

Der erste Parameter ist der Inhalt der Datei und nicht ihr Pfad. Dieser Irrtum kostet zuverlässig eine halbe Stunde, weil der Fehler danach beim Passwort gesucht wird. Das Ergebnis landet im zweiten Parameter, der per Referenz gefüllt wird, und PHP openssl_pkcs12_read() selbst gibt nur true oder false zurück.

Mit PHP openssl_pkcs12_read() zu drei PEM-Blöcken

Der Ablauf hat genau eine Verzweigung, und die hängt am Passwort.

flowchart TD
  A[p12-Datei einlesen] --> B{Passwort ok?}
  B -- nein --> F[false, Fehler auslesen]
  B -- ja --> C[cert: Zertifikat]
  B -- ja --> D[pkey: privater Schluessel]
  B -- ja --> E[extracerts: Kette]

Binär einlesen, ein Array bereitstellen, Passwort mitgeben, Rückgabewert prüfen. Viel mehr ist es nicht, und der letzte Schritt ist der, den alle vergessen.

<?php

$roh = file_get_contents('/srv/zert/kunde.p12');
$teile = [];
$pass = getenv('P12_PASS') ?: '';

$ok = openssl_pkcs12_read($roh, $teile, $pass);

if ($ok === false) {
exit('Container nicht lesbar');
}

print_r(array_keys($teile));
/* Array ( [0] => cert
[1] => pkey
[2] => extracerts ) */

Gelesen wird die Datei mit file_get_contents(), und zwar unverändert. Jede Verarbeitung, die den Inhalt als Text behandelt und dabei Zeilenenden umschreibt, macht aus dem Container Datensalat. Im Array stehen nach einem erfolgreichen Aufruf drei Schlüssel:

Schlüssel Inhalt Beginnt mit
cert das Zertifikat als PEM-Block -----BEGIN CERTIFICATE-----
pkey der private Schlüssel -----BEGIN PRIVATE KEY-----
extracerts ein Array mit den Zwischenzertifikaten fehlt ganz, wenn der Container keine Kette trägt

Die ersten beiden sind immer vorhanden, sobald PHP openssl_pkcs12_read() true gemeldet hat. extracerts ist der Wackelkandidat und will mit isset() geprüft werden. Bei false bleibt das übergebene Array unverändert, ein anschließender Zugriff auf $teile['cert'] greift also ins Leere.

Das Passwort: leer ist nicht dasselbe wie keines

Für PHP openssl_pkcs12_read() ist ein leerer String ein gesetztes, aber leeres Passwort. Ein Container ganz ohne Passwortschutz ist etwas anderes, und beide Varianten laufen einem im Alltag über den Weg. Scheitert der erste Versuch, ist der zweite deshalb kein Ratespiel: erst das Passwort aus der Mail, dann der leere String, und danach die Frage, ob die Datei vollständig angekommen ist.

Im Quelltext hat das Passwort nichts verloren. Eine Umgebungsvariable oder eine Konfigurationsdatei außerhalb des Web-Verzeichnisses ist allemal besser als eine Konstante, die irgendwann im Repository landet. Der Container kann denselben Weg nehmen, dann liegt er nirgends auf der Platte herum:

<?php

$roh = base64_decode(getenv('CLIENT_P12'), true);

if ($roh === false) {
exit('P12 aus der Umgebung ist unbrauchbar');
}

$teile = [];
$pass = getenv('P12_PASS') ?: '';
$ok = openssl_pkcs12_read($roh, $teile, $pass);

Praxis: Client-Zertifikat für eine cURL-Anfrage

Eine Gegenstelle, die ein Client-Zertifikat verlangt, führt geradewegs hierher. cURL will dafür Dateipfade sehen: CURLOPT_SSLCERT und CURLOPT_SSLKEY nehmen keinen PEM-Block als String entgegen. Die beiden Blöcke müssen also in Dateien, und zwar in solche, die hinterher wieder verschwinden.

<?php

$certDatei = tempnam(sys_get_temp_dir(), 'crt');
$keyDatei = tempnam(sys_get_temp_dir(), 'key');
chmod($certDatei, 0600);
chmod($keyDatei, 0600);
file_put_contents($certDatei, $teile['cert']);
file_put_contents($keyDatei, $teile['pkey']);

$ch = curl_init('https://portal.example.com/api');
curl_setopt($ch, CURLOPT_SSLCERT, $certDatei);
curl_setopt($ch, CURLOPT_SSLKEY, $keyDatei);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

try {
$antwort = curl_exec($ch);
} finally {
curl_close($ch);
unlink($certDatei);
unlink($keyDatei);
}

Drei Kleinigkeiten daran sind keine. Die temporären Dateien bekommen Rechte 0600, bevor irgendein Inhalt hineingeht. Gelöscht wird im finally-Zweig, damit auch eine abgebrochene Anfrage nichts liegen lässt. Und ein privater Schlüssel, der nach dem Lauf noch unter /tmp liegt, ist ein Befund im Sicherheitsaudit und keine Lässlichkeit.

Verlangt die Gegenstelle die vollständige Kette, wandert der dritte Eintrag aus PHP openssl_pkcs12_read() hinter das Zertifikat in dieselbe Datei. CURLOPT_SSLCERT nimmt mehrere PEM-Blöcke hintereinander entgegen und schickt sie zusammen. CURLOPT_CAINFO wäre hier die falsche Option, denn damit prüft cURL das Zertifikat der Gegenstelle und schickt selbst gar nichts. Der folgende Block tritt an die Stelle des einfachen file_put_contents() von oben und läuft damit vor curl_exec():

<?php

$pem = $teile['cert'];

if (isset($teile['extracerts'])) {
$kette = implode(PHP_EOL, $teile['extracerts']);
$pem .= PHP_EOL . $kette;
}

file_put_contents($certDatei, $pem);

Ablaufdatum und Inhaber aus dem Zertifikat lesen

Was PHP openssl_pkcs12_read() unter cert ablegt, ist genau der PEM-Block, mit dem openssl_x509_parse() anfängt. Das Ergebnis ist ein Array mit Aussteller, Inhaber und Gültigkeitszeitraum. validTo_time_t darin ist eine Unix-Zeit und damit sofort rechenbar.

<?php

$info = openssl_x509_parse($teile['cert']);
$ablauf = $info['validTo_time_t'];
$tage = (int) floor(($ablauf - time()) / 86400);

printf(
'%s bis %s, noch %d Tage',
$info['subject']['CN'],
date('d.m.Y', $ablauf),
$tage
);

Damit steht eine Warnung, die dreißig Tage vor Ablauf anspringt, statt am Montagmorgen im Protokoll der Gegenstelle. Welche Felder das Array sonst noch trägt, steht im Tutorial zum Auslesen von TLS-Zertifikaten mit openssl_x509_parse(). Den Block aus pkey nimmt umgekehrt jede Funktion entgegen, die einen privaten Schlüssel braucht, etwa beim Entschlüsseln von RSA-Daten.

Alte Container unter OpenSSL 3 und der Legacy-Provider

Ein Container, der vor einigen Jahren erzeugt wurde, ist oft mit RC2 oder 40-Bit-RC4 verschlüsselt. Seit OpenSSL 3 gelten diese Verfahren als abgekündigt und stecken im Legacy-Provider, der standardmäßig nicht geladen wird. PHP openssl_pkcs12_read() meldet dann false, obwohl das Passwort stimmt und die Datei in Ordnung ist.

Das hängt an der OpenSSL-Version des Servers und nicht an der PHP-Version. Derselbe Code kann auf zwei Maschinen mit gleicher PHP-Version verschieden ausgehen, und genau das macht die Suche so zäh. Welche Version im Spiel ist, sagt PHP selbst:

<?php

echo OPENSSL_VERSION_TEXT, PHP_EOL;
/* OpenSSL 3.5.4 30 Sep 2025 */

Steht dort eine 3 am Anfang, ist der Legacy-Provider die erste Spur. Ihn aktiviert ein Eintrag in der openssl.cnf:

openssl_conf = openssl_init


[openssl_init]
providers = provider_sect

[provider_sect]
default = default_sect
legacy = legacy_sect

[default_sect]
activate = 1

[legacy_sect]
activate = 1

Der zweite Ausweg erzeugt den Container einmalig neu, mit Verfahren von heute:

openssl pkcs12 -legacy -in alt.p12 -out tmp.pem

openssl pkcs12 -export -in tmp.pem -out neu.p12
rm tmp.pem

Der zweite Weg ist der bessere, weil er das Problem beseitigt, statt es zu umgehen. Der Legacy-Provider hält nur einen Container am Leben, dessen Verschlüsselung niemand mehr ernst nimmt. Die Zwischendatei trägt den Schlüssel im Klartext, deshalb steht das rm in derselben Sitzung und nicht auf der Liste für später.

Auf einem Server mit Shell ist dieser Kommandozeilenweg auch sonst brauchbar. Auf Shared Hosting gibt es keine Shell, und dort hat der Weg über PHP openssl_pkcs12_read() den zusätzlichen Vorteil, dass niemals ein entschlüsselter Schlüssel auf der Platte liegt.

Wenn PHP openssl_pkcs12_read() false zurückgibt

PHP selbst schweigt in diesem Fall. Es gibt keine Warnung, keine Zeile im Fehlerprotokoll, nur ein false. Der Grund steht in der Fehlerwarteschlange von OpenSSL, und die muss abgeholt werden:

<?php

$ok = openssl_pkcs12_read($roh, $teile, $pass);

if ($ok === false) {
while ($fehler = openssl_error_string()) {
error_log('p12: ' . $fehler);
}
}

OpenSSL stapelt mehrere Meldungen, und erst die vollständig geleerte Warteschlange ergibt ein brauchbares Bild. Vier Ursachen decken die allermeisten Fälle ab:

Ursache Woran sie zu erkennen ist Abhilfe
falsches Passwort mac verify failure in der Warteschlange Passwort und leeren String nacheinander probieren
Dateiname statt Inhalt asn1 encoding routines::not enough data file_get_contents() davorsetzen
altes Verschlüsselungsverfahren Container ist alt, Server läuft auf OpenSSL 3 Container neu erzeugen, notfalls Legacy-Provider
beschädigte Datei Dateigröße weicht vom Original ab binär neu übertragen, nicht per Copy-Paste

Fazit

PHP openssl_pkcs12_read() macht aus einer undurchsichtigen Binärdatei drei Textblöcke, mit denen der Rest von PHP etwas anfangen kann. Der erste Parameter ist der Dateiinhalt, das Ergebnis kommt per Referenz, und der Rückgabewert ist die einzige Stelle, an der ein Fehlschlag sichtbar wird.

extracerts ist optional und will geprüft sein, bevor jemand darauf zugreift. Und ein false ist keine Sackgasse: Eine Schleife über openssl_error_string() nennt den Grund, und in den meisten Fällen heißt der entweder falsches Passwort oder Container älter als der Server.

 

Tags:

 

 

Kommentare (0)

Noch keine Kommentare. Sei der Erste!

Melde dich an, um einen Kommentar zu schreiben.
Bücherregal mit drei Büchern: 'PHP 4 - Grundlagen und Profiwissen' von Hanser Verlag, 'Webdesign in a Nutshell' von O'Reilly Verlag, und 'Webgestaltung' von Galileo Computing.