Eigenes Lizenzsystem mit PHP umsetzen

Mehr aus Neugier möchte ich schon länger ein eigenes Lizenzsystem mit PHP umsetzen. Allerdings hatte ich bis heute noch keine Ahnung wie man so etwas überhaupt angeht oder realisiert. Das Problem dabei ist, dass man bei PHP den Code einfach nach der entsprechenden Logik durchsuchen kann und so das System eventuell sehr leicht umgehen könnte.

Anforderungen und Überlegungen

Im Idealfall kann ich im Schlüssel sogar weitere Informationen hinterlegen – dabei denke ich an einen Lizenznehmer, eine IP-Adresse, Domain oder den Namen des Lizenznehmers. Diese Daten sollen natürlich auch wieder entschlüsselt werden können. Das heißt, dass ich nach Eingabe des Schlüssels dann Texte wie „Lizensiert für Max Mustermann“ einblenden kann. So wäre auch ein Schlüssel denkbar, welcher seine Gültigkeit nach einer bestimmten Zeit wieder verliert.

Es wäre natürlich ein leichtes, wenn man einfach eine API einrichtet welche den Schlüssel validiert. So könnte man immerhin die Logik auf ein zentrales System auslagern, auf das Dritte keinen Zugriff haben. Aus meiner Sicht kommt so ein System aber nicht in Frage. Immerhin möchte auch ich nicht, dass beispielsweise jedes PlugIn in meiner WordPress-Instanz „nach Hause telefoniert“.

Da man für ein symmetrisches Verschlüsselungssystem den Schlüssel im Code hinterlegen müsste, kommt so ein System für mich im ersten Schritt nicht in Frage. In dem Fall wäre es so einfach sich einen neuen Schlüssel zu erstellen, dass sich jeder einen eigenen Schlüssel ganz einfach erstellen kann. Dafür lohnt sich meiner Meinung nach der Aufwand nicht, welcher in so ein System gesteckt werden müsste.

Libraries und Frameworks

Nach einger Recherche im Netz und sehr vielen Infos (die ich Teilweise nichtmal komplett verstanden habe, da es sehr Mathematisch wurde), bin ich auf ein paar PHP-Erweiterungen gestoßen. Folgende Erweiterungen muss ich mir also genauer ansehen und verstehen: OpenSSL, MCrypt. MCrypt fällt dabei scheinbar direkt raus, da nur symmetrische Verschlüsselungen unterstützt werden. Für mein Problem also nicht brauchbar.

OpenSSL entpuppte sich aber schnell als eine super Alternative. Hier muss man als erstes zwei Schlüssel (keys) erstellen – einen privaten (private) und einen öffentlichen (public). Der Vorteil daran: Alles was mit dem public key verschlüsselt wird, kann nur mit dem private key entschlüsselt werden und andersrum. Das heißt, dass die Lizenzinformationen mit dem privaten Schlüssel erstellt werden und dann an den Kunden gegeben werden können. In der Software ist dann der öffentliche Schlüssel hinterlegt. Hier können die entsprechenden Daten aus dem Key entschlüsselt werden. Arbeitet man zusätzlich mit serialize und unserialize, kann man komplette Objekte oder Arrays „als Schlüssel“ verwenden. Natürlich wird der Schlüssel dadurch höchstwahrscheinlich entsprechend länger.

Hier mal ein Beispiel-Roundtrip:

<?php

error_reporting(E_ALL);

$privateKeyPath = '/tmp/test_private.key';
$publicKeyPath = '/tmp/test_public.key';

if (!file_exists($privateKeyPath) || !file_exists($publicKeyPath)) {
	$loadedPubKey = openssl_pkey_new(array(
	    'private_key_bits' => 1024,
	    'private_key_type' => OPENSSL_KEYTYPE_RSA,
	));

    // Export the public key
	openssl_pkey_export_to_file($loadedPubKey, $privateKeyPath);

    // Export the private key
	$keyDetails = openssl_pkey_get_details($loadedPubKey);
	$publicKey = $keyDetails['key'];
	file_put_contents($publicKeyPath, $publicKey);

	openssl_free_key($loadedPubKey);
}

// Inhalte mit dem private Key verschlüsseln
$loadedPrivKey = openssl_pkey_get_private(file_get_contents($privateKeyPath));

$plaintext = serialize(array(
    'owner' => 'Matthias Kleine',
    'date' => '20.08.2013',
    'domain' => 'mkleine.de'
));

$encrypted = '';
if (!openssl_private_encrypt($plaintext, $encrypted, $loadedPrivKey)) {
	die('Failed to encrypt data');
}

openssl_free_key($loadedPrivKey);

// Encoded Value
$encryptedStr = base64_encode($encrypted);
print_r($encryptedStr);

// Decrypt with public key
$decryptedStr = base64_decode($encryptedStr);
$loadedPubKey = openssl_pkey_get_public(file_get_contents($publicKeyPath));

$decrypted = '';
if (!openssl_public_decrypt($decryptedStr, $decrypted, $loadedPubKey, OPENSSL_PKCS1_PADDING)) {
    die('Failed to decrypt data');
}

openssl_free_key($loadedPubKey);

// Ausgabe des entschlüsselten Arrays
echo PHP_EOL;
print_r(unserialize($decrypted));

Beispielausgabe:

xjDqV7SIz5WEXyYuGPSmUMZ3SPjmhbdc1Q+10sd+l9SU0JmHeapoWhIAF43KLmQxpdkcqLysiGqjqTeo+BiVVLjYcvCBk5LDvLDyCYtFPy6VEtyrNCONwQtsvnLPUt28YmN8F8qniMjXeyEvzboTkFa9rs9ER8EGKSnqNm8Imxs=
Array
(
    [owner] => Matthias Kleine
    [date] => 20.08.2013
    [domain] => mkleine.de
)

In der ersten Zeile sieht man den Lizenzschlüssel (welcher dann ja doch relativ lang geworden ist). Ich kenne aber genügend Programme, welche ähnlich lange Schlüssel verwenden. Per Copy-Paste ist das ja auch kein Problem. Zusätzlich sollte man sich Gedanken über Fehleingaben machen und sicherheitshalber Funktionen wie trim() vor der Entschlüsselung verwenden.

Nachteil: Durch Einsatz auf dem Kundensystem liegt natürlich immer der PHP-Code vor. Der Schlüssel kann noch so sicher sein – wird der Code einfach abgeändert und durch ein „return true“ eventuell die ganze Logik vernichten. Um das zu vermeiden könnte man versuchen den PHP-Code zu verschleiern und mit Methoden wie gzinflate, base64_decode, str_rot13 und eval auszuführen. Aber auch das macht es nur komplizierter und ist für Menschen mit Know-How eine eher belächelte Hürde.

Ich würde aber eher auf die Ehrlichkeit der Kunden setzen. In dem Fall bräuchte man nichtmal eine Lizenzierung. Das spart einem nicht nur viel Arbeit, sondern macht es dem Kunden auch sehr viel einfacher. So eine Funktionalität könnte dennoch interessant sein, wenn man für eine Erweiterung „Premium-Funktionalitäten“ freischalten möchte, und der Kunde die Software eh schon im Einsatz hat. In der Regel geht es ja nicht um hochpreisige Software.

Trotzdem sollte man darauf achten, dass die entsprechenden Pakete und PHP-Extensions auf dem Kundensystem verfügbar sind. Ansonsten steht man eh im Regen.

Und: Nie den Private-Key verlieren! :)

Disclaimer: Natürlich bin ich kein Fachmann für Verschlüsselungsalgorithmen und das ist das erste Stück Code was ich in dem Bereich je geschrieben habe. Ich garantiere keine Sicherheit dass es nicht doch einen Weg gibt die Daten anders zu erzeugen oder zu Verschlüsseln, sodass man sich eventuell selber Keys erzeugen kann.


Beitrag veröffentlicht

in

, , ,

von

Schlagwörter: