{"id":21548703,"url":"https://github.com/nikeee/license-system","last_synced_at":"2025-04-10T05:53:50.919Z","repository":{"id":19628355,"uuid":"22880207","full_name":"nikeee/license-system","owner":"nikeee","description":"Ein Lizenzsystem mit RSA-Signaturen","archived":false,"fork":false,"pushed_at":"2016-01-17T14:58:32.000Z","size":422,"stargazers_count":16,"open_issues_count":0,"forks_count":8,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-24T07:08:13.474Z","etag":null,"topics":["hacktoberfest","php","rsa"],"latest_commit_sha":null,"homepage":"https://nikeee.github.io/license-system","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nikeee.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-08-12T14:24:52.000Z","updated_at":"2023-11-19T20:06:58.000Z","dependencies_parsed_at":"2022-07-31T14:49:02.478Z","dependency_job_id":null,"html_url":"https://github.com/nikeee/license-system","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikeee%2Flicense-system","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikeee%2Flicense-system/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikeee%2Flicense-system/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikeee%2Flicense-system/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nikeee","download_url":"https://codeload.github.com/nikeee/license-system/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248166927,"owners_count":21058480,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hacktoberfest","php","rsa"],"created_at":"2024-11-24T06:19:27.262Z","updated_at":"2025-04-10T05:53:50.898Z","avatar_url":"https://github.com/nikeee.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Lizenzsystem mit RSA-Signaturen\nAufgrund der Nachfrage habe ich mich mal dazu durchgerungen, einen kleinen Beitrag dazu zu verfassen.\n\n## Voraussetzungen\nUm die Schritte in diesem Beitrag nachvollziehen zu können, solltest Du grundlegende Kenntnisse zu Public-Private-Key-Kryptografie haben. Hier geht es speziell um das Signieren von Daten.\nAußerdem solltest Du die grundlegenden Konzepte von C# (bzw. .NET) beherrschen. Ich zeige es hier mit PHP als Server-Backend, weshalb es auch nicht übel wäre, wenn Du PHP-Code zumindest lesen könntest.\n#### Software\nSoftwareseitig benötigst Du zum Nachvollziehen des kompletten Beitrags:\n\n- Visual Studio oder eine andere Möglichkeit, C#-Code zu kompilieren\n- Einen Web-Server mit PHP \u003e= 5.4\n- Einen Editor für PHP-Dateien\n- OpenSSL oder eine andere Möglichkeit, RSA-Schlüsselpaare zu erzeugen (z. B. via .NET und ToXmlString(), siehe unten)\n\nIch verwende in diesem Beitrag auf der Client-Seite nur .NET-Boardmittel. Wenn Du eine externe Crypto-Library (z. B. BouncyCastle) verwenden möchtest, kannst du das natürlich auch gerne tun.\n\n\n## Verbesser mich!\nDer ganze Beitrag inklusive Quelltext befindet sich auf GitHub und kann dort von Jedem verbessert werden:\n[nikeee/license-system](https://github.com/nikeee/license-system)\nFalls Dir etwas auffällt oder du ein anderes Anliegen hast, kannst Du mir gerne eine Issue hinterlassen oder mich kontaktieren.\n\n## Was ist das Ziel?\nZiel ist es, einen Namen und zusätzliche, beliebige Daten mit einer Signatur zu versehen, sodass auf dieser Grundlage ein Lizenzsystem implementiert werden kann. Bei diesem Lizenzsystem gibt es einen Client und einen Server. Die Aufgabe des Servers ist es, Lizenzschlüssel auszustellen, die am Client mittels RSA-Signatur validiert werden können. So kann der Client die Lizenz auf Gültigkeit prüfen, ohne den Server zu kontaktieren.\n\n## Okay, dann mal los!\nDie Vorgehensweise bei der Methode, wie ich sie hier zeige, lässt sich in folgende Schritte unterteilen:\n\n1. Einlesen der Lizenz\n    1. Auftrennung der Lizenz in einzelne Datenparameter (Name, Typ, Signatur)\n2. Standardisierung der übergebenen Daten in einheitliches Format\n3. Validierung der Daten mittels überprüfung der RSA-Signatur\n\n### Aufbau der Lizenz\nEine Lizenz ist wie folgt aufgebaut:\n```\n----------BEGIN LICENSE----------\n\u003cVorname\u003e \u003cNachname\u003e\n\u003cLizenztyp\u003e\n\u003cSignatur\u003e\n-----------END LICENSE-----------\n```\n\n- `\u003cVorname\u003e \u003cNachname\u003e`: stehen für den Lizenznehmer. Das kann auch eine E-Mail-Adresse oder irgendein beliebiger String sein. Ich verwende hier Vor- und Nachname.\n- `\u003cLizenztyp\u003e`: Um noch zu zeigen, dass man im Prinzip alles in so eine Lizenz stecken kann, habe ich dieses Feld hinzugefügt. Es steht für die Art, um die es sich bei der Lizenz handelt. Z. B. `\"Free\"`, `\"Trial\"` oder `\"Pro\"`. Ich habe hier `SingleUser`, `Commercial` und `OpenSource` verwendet.\n- `\u003cSignatur\u003e`: Im Prinzip würde es ausreichen, die ersten beiden Parameter zu lesen und zu wissen, um was für eine Lizenz es sich bei was für einem Lizenznehmer handelt. Leider ist sie dann nicht geschützt vor Manipulation. Aus diesem Grund benötigt man etwas, um die anderen Daten der Lizenz zu validieren. Hierfür wird diese RSA-SHA1-Signatur verwendet. Du musst natürlich nicht RSA nehmen.\n\nLass' Deiner Kreativität oder Ansprüchen freien Lauf! Es wäre z. B. noch möglich, ein Ablaufdatum oder eine E-Mail-Adresse hinzuzufügen. Der Einfachheit halber habe ich mich aber auf 2 Eigenschaften beschränkt.\nWie Du die Lizenzdaten letztendlich aufbaust, ist Dir überlassen. Man könnte hierbei auch mit XML oder JSON arbeiten, um die Verarbeitung etwas zu vereinfachen.\n\nEine Lizenz sieht dann z. B. so aus:\n```\n----------BEGIN LICENSE----------\nErika Mustermann\n2\nDEADBEEFCAFEBABEC001D00DEBEEFEA7E5\nDEADBEEFCAFEBABEC001D00DEBEEFEA7E5\nDEADBEEFCAFEBABEC001D00DEBEEFEA7E5\nDEADBEEFCAFEBABEC001D00DEBEEFEA7E5\nDEADBEEFCAFEBABEC001D00DEBEEFEA7E5\n-----------END LICENSE-----------\n```\n(Die Signaturdaten sind nicht gültig)\n\n### 0. Die Lizenz-Klasse\nUm den Lizenzkram besser vom restlichen Code der Anwendung zu trennen, legen wir eine Klasse für eine Lizenz an. Diese sieht bei mir jetzt so aus:\n\n```C#\nclass License\n{\n    private const string _publicKey = \"\"; // TODO\n\n    public bool IsValid { get; }\n    public string Licensee { get; }\n    public LicenseType Type { get; }\n\n    protected License(string licensee, LicenseType type, byte[] verificationData)\n    {\n        if (string.IsNullOrEmpty(licensee))\n            throw new ArgumentNullException(nameof(licensee));\n        if (verificationData == null)\n            throw new ArgumentNullException(nameof(verificationData));\n\n        Licensee = licensee;\n        Type = type;\n        IsValid = ValidateLicense(verificationData);\n    }\n\n    private bool ValidateLicense(byte[] signature) { /* TODO */ }\n\n    public static License Parse(string licenseData) { /* TODO */ }\n\n    private static string GeneralizeDataString(string someString) { /* TODO */ }\n}\n```\n\nAußerdem habe ich noch 3 verschiedene Lizenztypen gewählt, um zu zeigen, dass man noch weitere Daten in die Lizenz packen kann:\n```C#\nenum LicenseType\n{\n    SingleUser = 1,\n    Commercial,\n    OpenSource\n}\n```\n\nDie Stellen, die mit \"TODO\" gekennzeichnet sind, werden wir in den nächsten Schritten behandeln.\n\n#### 0.5 Verwendung der Lizenz-Klasse\nDie Lizenzklasse kann am Ende so verwendet werden:\n```C#\nvar license = License.Parse(\"----BEGIN LICENSE-----...\");\nif(license.IsValid)\n{\n    Console.WriteLine(\"Gültige Lizenz!\");\n    Console.WriteLine(\"Lizenztyp: \" + license.Type);\n}\nelse\n{\n    Console.WriteLine(\"Ungültige Lizenz!\");\n}\n```\n\nDer Konstruktor ist `protected`. Ich habe das in diesem Fall so gewählt, da ich möchte, dass man eine Instanz von License nur mit der Parse-Methode erstellen kann. Natürlich könnte man den Konstruktor auch `public` machen.\n\n### 1. Einlesen der Lizenz\nDieser Teil hat eigentlich noch nichts mit Kryptografie zu tun. Es geht nur um das einfache Einlesen der Daten aus dem Lizenzstring, um diese dann an den Konstruktor der License-Klasse zu übergeben.\nDer Parse-Teil sieht bei mir so aus:\n\n```C#\npublic static License Parse(string licenseData)\n{\n    // Pattern, um an die Daten zwischen BEGIN und END zu kommen\n    const string pattern = \"^\\\\s*-+BEGIN LICENSE-+(?\u003cdata\u003e(\\\\s|.)*?)-+END LICENSE-+\\\\s*$\";\n\n    var match = Regex.Match(licenseData, pattern, RegexOptions.IgnoreCase); // string auf Muster prüfen\n    if (!match.Success) // Wenn das Muster nicht gematched wurde, ist der Lizenz-String nicht lesbar und somit ungültig.\n        throw new FormatException();\n\n    var rawStringData = match.Groups[\"data\"].Value;\n    if (string.IsNullOrWhiteSpace(rawStringData)) // Wenn die Daten zwischen BEGIN und END leer bzw nur WhiteSpace sind -\u003e ungültig\n        throw new FormatException();\n    rawStringData = rawStringData.Trim(); // sonstiges whitespace trimmen (links udn rechts)\n\n    var splitData = rawStringData.Split('\\n'); // Splitten beim Zeilenumbruch\n    if (splitData.Length \u003c 3) // Wenn es weniger als 3 Zeilen (Name, Typ, Signatur) waren -\u003e ungültig\n        throw new FormatException();\n\n    // Ab hier findet auch Schirtt 1.1 statt:\n    // 1.1. Auftrennung der Lizenz in einzelne Datenparameter (Name, Typ, Signatur)\n\n    var licenseeRaw = splitData[0].Trim(); // Name des Lizenznehmers in 1. Zeile\n    var licenseTypeRaw = splitData[1].Trim(); // Integer-Wert des Enum-Members von LicenseType in 2. Zeile\n\n    var type = (LicenseType)int.Parse(licenseTypeRaw); // Integer-Wert in LicenseType umwandeln\n\n    if (type != LicenseType.SingleUser\n        \u0026\u0026 type != LicenseType.Commercial\n        \u0026\u0026 type != LicenseType.OpenSource)\n    {\n        // Enums könenn auch Werte annehmen, die nicht im Enum definiert sind, z. B. durch einen Cast.\n        // Falls dies bei LicenseType der Fall ist -\u003e ungültig\n        throw new FormatException();\n    }\n\n    // Die Signatur besteht aus allen verbleibenden Zeilen\n    var verificationDataRaw = string.Join(string.Empty, splitData.Skip(2)).Trim();\n\n    // Dekodierung des Strings zu Binärdaten (byte[]).\n    var verificationData = DecodeDataFromString(verificationDataRaw);\n\n    // Bis hier hin konnte alles erfolgreich eingelesen werden\n    // Ob die Daten aber gültig (== Signatur ist korrekt) sind, wird später überprüft.\n\n    return new License(licenseeRaw, type, verificationData); // Rückgabe des Lizenz-Objektes mit den eingelesenen Daten\n}\n\n\n// Zum Dekodieren der Signaturdaten wird diese Funkton verwendet.\n// Wir könnten auch base64 verwenden, dabei hat man jedoch wieder Groß- und Kleinschreibung, was doof ist, sollte sich jemand die Mühe machen, alles in kleinbuchstaben abzutippen.\n// Wenn man das durch Convert.FromBase64String() ersetzt, muss man auf der Server-Seite evenfalls die funktion ersetzen.\nprivate static byte[] DecodeDataFromString(string value)\n{\n    // Hexadezimaen String zurück in Byte-Daten umwandeln\n    // macht das gleiche wie PHPs hex2bin; kehrt das bin2hex um.\n\n    if (value == null)\n        return new byte[0];\n\n    if ((value.Length \u0026 1) != 0) // Länge der Daten ist nicht durch 2 teilbar -\u003e kein gültiger hexadezimaler string\n        throw new FormatException();\n\n    if (string.IsNullOrWhiteSpace(value))\n        return new byte[0];\n\n    value = value.ToUpperInvariant();\n\n    byte[] ab = new byte[value.Length \u003e\u003e 1];\n    for (int i = 0; i \u003c value.Length; i++)\n    {\n        int b = value[i];\n        b = (b - '0') + ((('9' - b) \u003e\u003e 31) \u0026 -7);\n        ab[i \u003e\u003e 1] |= (byte)(b \u003c\u003c 4 * ((i \u0026 1) ^ 1));\n    }\n    return ab;\n}\n```\n\nWie gesagt. Ich verwende hier ein Format, das ich von Sublime Text abgeschaut habe. Du kannst Dir auch ein eigenes ausdenken, das auf z. B. XML oder JSON basiert, um Dir das Auslesen zu vereinfachen.\n\n\n### 2. Standardisierung der übergebenen Daten in einheitliches Format\n\nDa wir nicht sicher ein können, dass unser Benutzer seinen Namen leicht abgeändert hat, müssen wir das Gane in ein Standard-Format bringen. Dies ist sinnvoll, da z. B. \"Erika Mustermann\" und \"erika Mustermann\" den gleichen Namen bezeichnen, aber ansich unterschiedliche Strings sind.\nHierfür habe ich folgende Funktion angelegt:\n```C#\nprivate static string GeneralizeDataString(string someString)\n{\n    return someString.StripWhiteSpace().ToUpperInvariant();\n}\n```\nDie StripWhiteSpace-Funktion ist als String-Extension wie folgt definiert:\n```C#\ninternal static class StringExtensions\n{\n    public static string StripWhiteSpace(this string value)\n    {\n        if (value == null)\n            return null;\n        if (value.Length == 0 || value.Trim().Length == 0)\n            return string.Empty;\n        var sb = new StringBuilder(value.Length);\n        for (int i = 0; i \u003c value.Length; ++i)\n            if (!char.IsWhiteSpace(value[i]))\n                sb.Append(value[i]);\n        return sb.ToString();\n    }\n}\n```\n\nDiese funktion entfernt sämtlichen Whitespace aus dem String und konvertiert anschließend alle Buchstaben in Großbuchstaben.\nSo wird:\n`\"Erika Mustermann\"` zu `\"ErikaMustermann\"` zu `\"ERIKAMUSTERMANN\"`\n...und dementsprechend\n`\"erika Mustermann\"` zu `\"erikaMustermann\"` zu `\"ERIKAMUSTERMANN\"`\nAuch `\"eRikA musStermAnN\"` wird zu `\"ERIKAMUSTERMANN\"`.\nDadurch erreichen wir, dass die Lizenz weniger anfällig für Änderungen ist, die ein unerfahrener Benutzer eventuell vornehmen könnte (Änderung des String-Casings).\n\nDieser Schritt ist für alle Daten nötig, die für soetwas anfällig wären. In diesem Beispiel sind das aber keine weiteren.\n\n### 3. Validierung der Daten mittels überprüfung der RSA-Signatur\nNun kommt der eigentlich kryptografische Teil und auch die letzte Funktion der License-Klasse.\n\n```C#\nprivate bool ValidateLicense(byte[] signature)\n{\n    // Um die Lizenz auf Gültigkeit zu prüfen müssen alle zu prüfenden Parameter (Name, Typ) in einen Buffer gepackt werden\n    // Dies kann man wie folgt umsetzen:\n\n    // Standardisierung des Namens des Lizenznehmers\n    var licenseeGen = GeneralizeDataString(Licensee); // \"ERIKAMUSTERMANN\"\n\n    // Zusammenfüren des Namens \"ERIKAMUSTERMANN\" mit dem Int-Wert des Lizenztyps (z. B. 2 für \"Commercial\").\n    var dataStr = licenseeGen + (int)Type; //ERIKAMUSTERMANN2\n\n    // Erstellen eines Byte-Arrays aus dem zusammengefügten String\n    var dataBuffer = System.Text.Encoding.UTF8.GetBytes(dataStr);\n\n    // Crypto-Provider erstellen\n    using (var provider = new RSACryptoServiceProvider())\n    {\n        // Den Public Key festlegen\n        provider.FromXmlString(_publicKey);\n        provider.PersistKeyInCsp = false;\n\n        // Daten mit VerifyData überprüfen\n        // Übergeben wird hier der Datenpuffer, das Hashing-Verfahren für die Signatur und Signatur selbst\n        // In diesem Fall verwende ich SHA1\n        return provider.VerifyData(dataBuffer, new SHA1CryptoServiceProvider(), signature);\n        // Wenn die Daten gültig sind, sind die Lizenzdaten ebenfalls gültig. Wenn nicht, dann nicht.\n    }\n}\n```\nDas war's schon fast! Wir benötigen nun noch ein Schlüsselpaar. Dieses kann man z. B. mit OpenSSL erzeugen. Ich nehme hier jetzt mal ein Beispiel-Schlüsselpaar, welches Du nicht improduktiven Einsatz verwenden solltest!(!)\n\nMein Private Key in dem Fall:\n```XML\n\u003cRSAKeyValue\u003e\u003cModulus\u003e8CKn78RI6h7vNOPMeMCeRCHegEgG1nR+X84B8b3sOZF6hAjDXF80ag1Zw1T0E+NVHmbPB8aLgRPmQPA351ZR8D+BCHooDlGqstLLHiqTu9bbqRVPti46XBeju3Fbi47euO+omH0sq7LCuIZ5s1WBmTc9ejkkfc/0rk3fAYaIRuE=\u003c/Modulus\u003e\u003cExponent\u003eAQAB\u003c/Exponent\u003e\u003cP\u003e/m1FEqol/KKhxOyGsK4GVuansBXhrAgpwMlYLT+vF0gy1jzYQDNNQXzeQFYH6gZY66RTYFl3JPNL8KXLyhwDLQ==\u003c/P\u003e\u003cQ\u003e8Z7DrGQsGhiLgg70j40/+AgfNKJB4SXY7FmyBmLPRiHkT2d3AyvzuNNf/hkHA2UMLQT4xewmkxK9MU2nDitzBQ==\u003c/Q\u003e\u003cDP\u003euRVOSSyjo6u/WJzjwoVmMTNryymv2FC75vXRgmEwgxRPfxAWFGX9jmVC3LR432KsrwcEbDPI+4VNugsyO52zJQ==\u003c/DP\u003e\u003cDQ\u003eAJFY8FzD5cPNAB883+F7FwAd4qfG89p86gFD89PjnMyTlsQteWpvBi4o+ZXheFaScsCiPQTTCmFu5GDEVbowaQ==\u003c/DQ\u003e\u003cInverseQ\u003e7gQ8MGqjjrCAfOzrrC9ZuVdGRfEjUEdHMqiF+js7XNBvnT5lBznUOd+eta6CGo7S5hjU7D3CEzmVGQfxUsRZ1w==\u003c/InverseQ\u003e\u003cD\u003e6YSaERSs31dTwPghV+/gOFtDVzYzyAqi9iGMTHwnotfw70LiUAqZGuR+vO/5Jvn0RUsu2t3dvZkPWWkAxCtyIzALk8Brx1r8n76VHVWMzkZvOoqMa1/HdZCXM0TVlpnYVJjyUA8wzi4tzPIPv08lAGwYJzHcoMlFHkQ2npqflxE=\u003c/D\u003e\u003c/RSAKeyValue\u003e\n```\n(Anmerkung: Beim XML-Format ist hier der Public Key mit dabei)\n\nDer dazugehörige Public Key:\n```XML\n\u003cRSAKeyValue\u003e\u003cModulus\u003e8CKn78RI6h7vNOPMeMCeRCHegEgG1nR+X84B8b3sOZF6hAjDXF80ag1Zw1T0E+NVHmbPB8aLgRPmQPA351ZR8D+BCHooDlGqstLLHiqTu9bbqRVPti46XBeju3Fbi47euO+omH0sq7LCuIZ5s1WBmTc9ejkkfc/0rk3fAYaIRuE=\u003c/Modulus\u003e\u003cExponent\u003eAQAB\u003c/Exponent\u003e\u003c/RSAKeyValue\u003e\n```\n\nIch habe beide Schlüssel jetzt im XML-Format. Wenn Du andere Formate bevorzugst, kannst Du diese auch verwenden. Ich nehme jetzt dieses, da dieses Format von Haus aus mit .NET kompatibel ist und die PHP-Library PHPSecLib es ebenfalls unterstützt.\n\nDer Private Key wird zum erstellen einer Lizenzdatei verwendet. Dieser darf niemals preisgegeben werden. Sobald jemand im Besitz dieses Schlüssels ist, kann derjenige sich so viele Lizenzen erstellen, wie er will!(!). Der Private Key darf auch keinesfalls irgendwo im Quelltext der Anwendung stehen, die an die Benutzer rausgeht!\n\n#### ...weiter im Text.\n\nDen Public Key fügen wir einfach oben als String-Wert der Konstante ein.\n\n```C#\nprivate const string _publicKey = @\"\u003cRSAKeyValue\u003e\u003cModulus\u003e8CKn78RI6h7vNOPMeMCeRCHegEgG1nR+X84B8b3sOZF6hAjDXF80ag1Zw1T0E+NVHmbPB8aLgRPmQPA351ZR8D+BCHooDlGqstLLHiqTu9bbqRVPti46XBeju3Fbi47euO+omH0sq7LCuIZ5s1WBmTc9ejkkfc/0rk3fAYaIRuE=\u003c/Modulus\u003e\u003cExponent\u003eAQAB\u003c/Exponent\u003e\u003c/RSAKeyValue\u003e\";\n```\n\nSoweit sind wir fertig! Der Client kann nun eine Lizenz parsen, sie in eine Klasse stecken und mittels RSA-Signatur validieren.\n\n### Lizenzen ausstellen\n\nUm Lizenzen auszustellen benötigen wir den Private Key. Bitte achte darauf, dass _niemand_ außer Dir Zugriff auf diesen Schlüssel haben darf.\n\nUm dies zu tun bietet sich ein Server an.\n\nMit PHP und der PHPSecLib könnte es wie folgt gehen. Die PHPSecLib bekommst du [hier](http://phpseclib.sourceforge.net). Falls du das GitHub-Repo geklont hast, ist es dort ebenfalls dabei.\n```PHP\n// Abbildung des Enums, das wir auch in der Client-Anwendung haben\nclass LicenseType\n{\n    const Personal = 1;\n    const Commercial = 2;\n    const OpenSource = 3;\n}\n```\n\n```PHP\n// Generierung von Lizenzen in einer separaten Klasse\nclass LicenseCreator\n{\n    // Niemals anderen Leuten zugänglich machen!\n    const privateKey = '\u003cRSAKeyValue\u003e\u003cModulus\u003e8CKn78RI6h7vNOPMeMCeRCHegEgG1nR+X84B8b3sOZF6hAjDXF80ag1Zw1T0E+NVHmbPB8aLgRPmQPA351ZR8D+BCHooDlGqstLLHiqTu9bbqRVPti46XBeju3Fbi47euO+omH0sq7LCuIZ5s1WBmTc9ejkkfc/0rk3fAYaIRuE=\u003c/Modulus\u003e\u003cExponent\u003eAQAB\u003c/Exponent\u003e\u003cP\u003e/m1FEqol/KKhxOyGsK4GVuansBXhrAgpwMlYLT+vF0gy1jzYQDNNQXzeQFYH6gZY66RTYFl3JPNL8KXLyhwDLQ==\u003c/P\u003e\u003cQ\u003e8Z7DrGQsGhiLgg70j40/+AgfNKJB4SXY7FmyBmLPRiHkT2d3AyvzuNNf/hkHA2UMLQT4xewmkxK9MU2nDitzBQ==\u003c/Q\u003e\u003cDP\u003euRVOSSyjo6u/WJzjwoVmMTNryymv2FC75vXRgmEwgxRPfxAWFGX9jmVC3LR432KsrwcEbDPI+4VNugsyO52zJQ==\u003c/DP\u003e\u003cDQ\u003eAJFY8FzD5cPNAB883+F7FwAd4qfG89p86gFD89PjnMyTlsQteWpvBi4o+ZXheFaScsCiPQTTCmFu5GDEVbowaQ==\u003c/DQ\u003e\u003cInverseQ\u003e7gQ8MGqjjrCAfOzrrC9ZuVdGRfEjUEdHMqiF+js7XNBvnT5lBznUOd+eta6CGo7S5hjU7D3CEzmVGQfxUsRZ1w==\u003c/InverseQ\u003e\u003cD\u003e6YSaERSs31dTwPghV+/gOFtDVzYzyAqi9iGMTHwnotfw70LiUAqZGuR+vO/5Jvn0RUsu2t3dvZkPWWkAxCtyIzALk8Brx1r8n76VHVWMzkZvOoqMa1/HdZCXM0TVlpnYVJjyUA8wzi4tzPIPv08lAGwYJzHcoMlFHkQ2npqflxE=\u003c/D\u003e\u003c/RSAKeyValue\u003e';\n\n    public static function CreateLicense($licensee, $type)\n    {\n        // Gleiche Generalisierung wie am Client:\n        $licenseeGen = self::GeneralizeDataString($licensee);\n        $dataStr = $licenseeGen . (int)$type; // \"ERIKAMUSTERMANN2\"\n\n        $rsa = new Crypt_RSA(); // Neue RSA-Klasse erstellen\n\n        // Setzen der RSA-Optionen auf die, die auch am Client verwendet werden:\n        $rsa-\u003esetPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_XML);\n        $rsa-\u003esetHash('SHA1');\n        $rsa-\u003esetSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);\n\n        // privaten Schlüssel laden\n        $rsa-\u003eloadKey(self::privateKey);\n\n        // Erstellen der Signatur\n        $signature = $rsa-\u003esign($dataStr);\n\n        // Formatierte Lizenzdaten zurückgeben\n        return self::FormatLicense($licensee, $type, $signature);\n    }\n\n    private static function FormatLicense($licensee, $type, $signature)\n    {\n        // Binärdaten aus $signature in hexadezimal kodierten String umwandeln\n        $formattedSignature = self::EncodeDataToHexString($signature);\n\n        // Signatur in 29-Zeichen-Blöcke aufteilen (sieht schöner aus)\n        $formattedSignature = chunk_split($formattedSignature, 29);\n\n        $l = \"--------BEGIN LICENSE--------\\n\"; // Unser Anfangsblock\n        $l .= $licensee . \"\\n\"; // Der Name des Lizenznehmers\n        $l .= (int)$type . \"\\n\"; // Der Lizenztyp als Int\n        $l .= trim($formattedSignature) . \"\\n\"; // die in mehrere Zeilen aufgeteilte, kodierte Signatur\n        $l .= \"---------END LICENSE---------\"; // Ende der Lizenz\n\n        return $l;\n    }\n\n    private static function EncodeDataToHexString($data)\n    {\n        return strtoupper(bin2hex($data));\n    }\n\n    private static function GeneralizeDataString($someString)\n    {\n        // Gleiche Funktion wie am Client\n        return strtoupper(self::StripWhiteSpace($someString));\n    }\n\n    private static function StripWhiteSpace($someString)\n    {\n        // Gleiche Funktion wie am Client, nur mit RegEx\n        return preg_replace('/\\s+/', '', $someString);\n    }\n}\n```\n\n### Abschluss\n\nDas war's.\n\nAuf der Serverseite können wir nun mit Hilfe der LicenseCreator-Klasse eine Lizenz erstellen:\n```PHP\n$license = LicenseCreator::CreateLicense(\"Erika Mustermann\", LicenseType::Commercial);\n```\nHeraus kommt sowas:\n```\n--------BEGIN LICENSE--------\nErika Mustermann\n2\n0D0E9D62B80195C9C867CF451C312\n80593BFAEE80450BDD46A2CEAFFED\n6D378CD9408B328B05AC2C8D9A7AE\nD8B8B69D44DBF66EA0F814A800393\n7AD16197EF4DB28FDD27CFF58B1FC\n14DF3CD7912C41C2573BB0A0D59AD\n94BE0EFCD804D8A809875F13CAC70\n137F24E30478AE8DFD3B94025A38D\n80D636637F725887869ED77E\n---------END LICENSE---------\n```\n\nDieser String kann am Client validiert werden.\n```C#\nvar license = License.Parse(lizenzString);\nConsole.WriteLine(\"Lizenz gültig? \" + license.IsValid);\nif(license.IsValid)\n    Console.WriteLine(\"Lizenztyp: \" + license.Type);\n```\n\n#### Was es zu beachten gibt\n- Die Schlüssel sollten lang genug gewühlt werden (mindestens 2048 Bit sollten ausreichen)\n- Niemand anders sollte auf den Private Key Zugriff haben, da das komplette System sonst hinfällig ist.\n\n#### Erstellen von Schlüsseln\nDafür kannst Du OpenSSL oder andere Kryptosoftware verwenden. Wichtig ist nur, dass Du die Schlüssel später auch in der Anwendung verwenden kannst. Du kannst aber auch rein bei .NET bleiben. Ich mache es z. B. so:\n```C#\nconst int KeyLength = 2048;\nvar rsa = new RSACryptoServiceProvider(KeyLength);\nFile.WriteAllText(\"private_key.xml\", rsa.ToXmlString(true));\nFile.WriteAllText(\"public_key.xml\", rsa.ToXmlString(false));\n```\n\n#### Vorteile und Nachteile dieser Methode\n##### Vorteile\n- Keine Internetverbindung zum Validieren der Lizenz notwendig\n- Key-Generatoren sind so gut wie unmöglich, solange der Schlüssel lang genug gewählt wurde und der Private key privat bleibt\n- Geringe Fehleranfälligkeit, da man nicht auf Firewall-/Firmen-Umgebungen, die UAC oder ähnliches Rücksicht nehmen muss.\n\n##### Nachteile\n- Sehr einfach zu cracken\n\n#### Was noch gemacht werden muss\n- License.TryParse(), bei der keine Exception geworfen wird\n- Server-Beispiel mit Node.js\n\n#### \"Einfach zu Cracken\" vs \"Keygens unmöglich\":\nDas hört sich im ersten Moment recht widersprüchlich an, aber so ist es. Jemand könnte die Anwendung leicht cracken, indem an der entsprechenden Stelle einfach ein \"return true;\" eingefügt wird. Um dies zu tun, muss derjenige allerdings die Anwendung bearbeiten. Das hat den \"Nachteil\", dass wenn ein Update der Anwendung erscheint, er dies erneut machen muss. Das bringt einen zusätzlichen Aufwand mit sich.\n\n#### Meine Meinung zu dem Thema:\nIch finde, man sollte sein Programm nicht mit irgendwelchem \"unknackbaren\" Lizenzkram verwurschteln, was es am Ende nur fehleranfälliger und unbenutzbarer macht. Wirklich viel mehr geschützt ist es dadurch auch nicht.\nGenerell sollte man IMO die Zeit lieber in die Funktionalität des Programms statt in ein komplexes Lizenzsystem stecken.\nDas hier gezeigte System ähnelt stark dem, welches u. A. bei Sublime Text zum Einsatz kommt.\nIch finde diese Herangehensweise noch vertretbar, da sie recht simpel gehalten ist und trotzdem noch eine (kleine) Hürde bietet.\nDieser Beitrag soll nicht bedeuten, dass man in allen Programmen so ein System einbauen soll. Nein, ganz und gar nicht. Ich bin ein freund von freier und offener Software und will lediglich zeigen, wie man diese Problemstellung angehen kann.\nAußerdem habe ich auf meiner Arbeit oft mit schlecht programmierter Software zu tun, die häufig wegen ignorant implementiertem Lizenzkram die Funktion verweigert.\nBeispiel?\n[Hier](https://stackoverflow.com/q/25486319/785210) will Jemand irgendwelchen Lizenzkram mit Festplattenseriennummern hinfriemeln. Aus irgendeinem Grund funktioniert das aber nicht immer, weshalb seine Software wahrscheinlich unrechtmäßig die Funktion verweigern wird. Sowas nervt absolut jeden Sysadmin und Anwender. Lasst sowas bitte.\n\n#### Disclaimer\nDas Übliche:\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnikeee%2Flicense-system","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnikeee%2Flicense-system","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnikeee%2Flicense-system/lists"}