Lizenzsystem mit RSA-Signaturen

    • Allgemein
    • .NET (FX) 4.0

    Es gibt 2 Antworten in diesem Thema. Der letzte Beitrag () ist von nikeee13.

      Lizenzsystem mit RSA-Signaturen

      Lizenzsystem mit RSA-Signaturen
      Aufgrund der Nachfrage habe ich mich mal dazu durchgerungen, einen kleinen Beitrag dazu zu verfassen.

      Voraussetzungen
      Um 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.
      Auß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.

      Software
      Softwareseitig benötigst Du zum Nachvollziehen des kompletten Beitrags:
      • Visual Studio oder eine andere Möglichkeit, C#-Code zu kompilieren
      • Einen Web-Server mit PHP >= 5.4
      • Einen Editor für PHP-Dateien
      • OpenSSL oder eine andere Möglichkeit, RSA-Schlüsselpaare zu erzeugen (z. B. via .NET und ToXmlString(), siehe unten)

      Ich verwende in diesem Beitrag auf der Client-Seite nur .NET-Bordmittel. Wenn Du eine externe Crypto-Library (z. B. BouncyCastle) verwenden möchtest, kannst Du das natürlich auch gerne tun.

      Verbesser mich!
      Der ganze Beitrag inklusive Quelltext befindet sich auf GitHub und kann dort von Jedem verbessert werden:
      nikeee/license-system
      Falls Dir etwas auffällt oder Du ein anderes Anliegen hast, kannst Du mir gerne eine Issue hinterlassen, einen Pull-Request öffnen oder mich kontaktieren.

      Was ist das Ziel?
      Ziel 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.

      Okay, dann mal los!
      Die Vorgehensweise bei der Methode, wie ich sie hier zeige, lässt sich in folgende Schritte unterteilen:

      1. Einlesen der Lizenz
        1. Auftrennung der Lizenz in einzelne Datenparameter (Name, Typ, Signatur)
      2. Standardisierung der übergebenen Daten in einheitliches Format
      3. Validierung der Daten mittels überprüfung der RSA-Signatur


      Aufbau der Lizenz
      Eine Lizenz ist wie folgt aufgebaut:

      Quellcode

      1. ----------BEGIN LICENSE----------
      2. <Vorname> <Nachname>
      3. <Lizenztyp>
      4. <Signatur>
      5. -----------END LICENSE-----------


      • <Vorname> <Nachname>: stehen für den Lizenznehmer. Das kann auch eine E-Mail-Adresse oder irgendein beliebiger String sein. Ich verwende hier Vor- und Nachname.
      • <Lizenztyp>: 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.
      • <Signatur>: 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.

      Lass' 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.
      Wie Du die Lizenzdaten letztendlich aufbaust, ist Dir überlassen. Man könnte hierbei auch mit XML oder JSON arbeiten, um die Verarbeitung etwas zu vereinfachen.

      Eine Lizenz sieht dann z. B. so aus:

      Quellcode

      1. ----------BEGIN LICENSE----------
      2. Erika Mustermann
      3. 2
      4. DEADBEEFCAFEBABEC001D00DEBEEFEA7E5
      5. DEADBEEFCAFEBABEC001D00DEBEEFEA7E5
      6. DEADBEEFCAFEBABEC001D00DEBEEFEA7E5
      7. DEADBEEFCAFEBABEC001D00DEBEEFEA7E5
      8. DEADBEEFCAFEBABEC001D00DEBEEFEA7E5
      9. -----------END LICENSE-----------
      (Die Signaturdaten sind nicht gültig)

      0. Die Lizenz-Klasse
      Um 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:

      Spoiler anzeigen

      C#-Quellcode

      1. class License
      2. {
      3. private const string _publicKey = ""; // TODO
      4. private readonly bool _isValid;
      5. public bool IsValid { get { return _isValid; } }
      6. private readonly string _licensee;
      7. public string Licensee { get { return _licensee; } }
      8. private readonly LicenseType _type;
      9. public LicenseType Type { get { return _type; } }
      10. protected License(string licensee, LicenseType type, byte[] verificationData)
      11. {
      12. if (string.IsNullOrEmpty(licensee))
      13. throw new ArgumentNullException("licensee");
      14. if (verificationData == null)
      15. throw new ArgumentNullException("verificationData");
      16. _licensee = licensee;
      17. _type = type;
      18. _isValid = ValidateLicense(verificationData);
      19. }
      20. private bool ValidateLicense(byte[] signature) { /* TODO */ }
      21. public static License Parse(string licenseData) { /* TODO */ }
      22. private static string GeneralizeDataString(string someString) { /* TODO */ }
      23. }



      Außerdem habe ich noch 3 verschiedene Lizenztypen gewählt, um zu zeigen, dass man noch weitere Daten in die Lizenz packen kann:

      C#-Quellcode

      1. enum LicenseType
      2. {
      3. SingleUser = 1,
      4. Commercial,
      5. OpenSource
      6. }


      Die Stellen, die mit "TODO" gekennzeichnet sind, werden wir in den nächsten Schritten behandeln.

      0.5 Verwendung der Lizenz-Klasse
      Die Lizenzklasse kann am Ende so verwendet werden:

      C#-Quellcode

      1. var license = License.Parse("----BEGIN LICENSE-----...");
      2. if(license.IsValid)
      3. {
      4. Console.WriteLine("Gültige Lizenz!");
      5. Console.WriteLine("Lizenztyp: " + license.Type);
      6. }
      7. else
      8. {
      9. Console.WriteLine("Ungültige Lizenz!");
      10. }


      Der 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.

      1. Einlesen der Lizenz
      Dieser 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.
      Der Parse-Teil sieht bei mir so aus:

      Spoiler anzeigen

      C#-Quellcode

      1. public static License Parse(string licenseData)
      2. {
      3. // Pattern, um an die Daten zwischen BEGIN und END zu kommen
      4. const string pattern = "^\\s*-+BEGIN LICENSE-+(?<data>(\\s|.)*?)-+END LICENSE-+\\s*$";
      5. var match = Regex.Match(licenseData, pattern, RegexOptions.IgnoreCase); // string auf Muster prüfen
      6. if (!match.Success) // Wenn das Muster nicht gematched wurde, ist der Lizenz-String nicht lesbar und somit ungültig.
      7. throw new FormatException();
      8. var rawStringData = match.Groups["data"].Value;
      9. if (string.IsNullOrWhiteSpace(rawStringData)) // Wenn die Daten zwischen BEGIN und END leer bzw nur WhiteSpace sind -> ungültig
      10. throw new FormatException();
      11. rawStringData = rawStringData.Trim(); // sonstiges whitespace trimmen (links udn rechts)
      12. var splitData = rawStringData.Split('\n'); // Splitten beim Zeilenumbruch
      13. if (splitData.Length < 3) // Wenn es weniger als 3 Zeilen (Name, Typ, Signatur) waren -> ungültig
      14. throw new FormatException();
      15. // Ab hier findet auch Schirtt 1.1 statt:
      16. // 1.1. Auftrennung der Lizenz in einzelne Datenparameter (Name, Typ, Signatur)
      17. var licenseeRaw = splitData[0].Trim(); // Name des Lizenznehmers in 1. Zeile
      18. var licenseTypeRaw = splitData[1].Trim(); // Integer-Wert des Enum-Members von LicenseType in 2. Zeile
      19. var type = (LicenseType)int.Parse(licenseTypeRaw); // Integer-Wert in LicenseType umwandeln
      20. if (type != LicenseType.SingleUser
      21. && type != LicenseType.Commercial
      22. && type != LicenseType.OpenSource)
      23. {
      24. // Enums könenn auch Werte annehmen, die nicht im Enum definiert sind, z. B. durch einen Cast.
      25. // Falls dies bei LicenseType der Fall ist -> ungültig
      26. throw new FormatException();
      27. }
      28. // Die Signatur besteht aus allen verbleibenden Zeilen
      29. var verificationDataRaw = string.Join(string.Empty, splitData.Skip(2)).Trim();
      30. // Dekodierung des Strings zu Binärdaten (byte[]).
      31. var verificationData = DecodeDataFromString(verificationDataRaw);
      32. // Bis hier hin konnte alles erfolgreich eingelesen werden
      33. // Ob die Daten aber gültig (== Signatur ist korrekt) sind, wird später überprüft.
      34. return new License(licenseeRaw, type, verificationData); // Rückgabe des Lizenz-Objektes mit den eingelesenen Daten
      35. }
      36. // Zum Dekodieren der Signaturdaten wird diese Funkton verwendet.
      37. // 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.
      38. // Wenn man das durch Convert.FromBase64String() ersetzt, muss man auf der Server-Seite evenfalls die funktion ersetzen.
      39. private static byte[] DecodeDataFromString(string value)
      40. {
      41. // Hexadezimaen String zurück in Byte-Daten umwandeln
      42. // macht das gleiche wie PHPs hex2bin; kehrt das bin2hex um.
      43. if (value == null)
      44. return new byte[0];
      45. if ((value.Length & 1) != 0) // Länge der Daten ist nicht durch 2 teilbar -> kein gültiger hexadezimaler string
      46. throw new FormatException();
      47. if (string.IsNullOrWhiteSpace(value))
      48. return new byte[0];
      49. value = value.ToUpperInvariant();
      50. byte[] ab = new byte[value.Length >> 1];
      51. for (int i = 0; i < value.Length; i++)
      52. {
      53. int b = value[i];
      54. b = (b - '0') + ((('9' - b) >> 31) & -7);
      55. ab[i >> 1] |= (byte)(b << 4 * ((i & 1) ^ 1));
      56. }
      57. return ab;
      58. }



      Wie 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.


      2. Standardisierung der übergebenen Daten in einheitliches Format

      Da 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.
      Hierfür habe ich folgende Funktion angelegt:
      Spoiler anzeigen

      C#-Quellcode

      1. private static string GeneralizeDataString(string someString)
      2. {
      3. return someString.StripWhiteSpace().ToUpperInvariant();
      4. }

      Die StripWhiteSpace-Funktion ist als String-Extension wie folgt definiert:

      C#-Quellcode

      1. internal static class StringExtensions
      2. {
      3. public static string StripWhiteSpace(this string value)
      4. {
      5. if (value == null)
      6. return null;
      7. if (value.Length == 0 || value.Trim().Length == 0)
      8. return string.Empty;
      9. var sb = new StringBuilder(value.Length);
      10. for (int i = 0; i < value.Length; ++i)
      11. if (!char.IsWhiteSpace(value[i]))
      12. sb.Append(value[i]);
      13. return sb.ToString();
      14. }
      15. }



      Diese funktion entfernt sämtlichen Whitespace aus dem String und konvertiert anschließend alle Buchstaben in Großbuchstaben.
      So wird:
      "Erika Mustermann" zu "ErikaMustermann" zu "ERIKAMUSTERMANN"
      ...und dementsprechend
      "erika Mustermann" zu "erikaMustermann" zu "ERIKAMUSTERMANN"
      Auch "eRikA musStermAnN" wird zu "ERIKAMUSTERMANN".
      Dadurch erreichen wir, dass die Lizenz weniger anfällig für Änderungen ist, die ein unerfahrener Benutzer eventuell vornehmen könnte (Änderung des String-Casings).

      Dieser Schritt ist für alle Daten nötig, die für soetwas anfällig wären. In diesem Beispiel sind das aber keine weiteren.

      3. Validierung der Daten mittels überprüfung der RSA-Signatur
      Nun kommt der eigentlich kryptografische Teil und auch die letzte Funktion der License-Klasse.

      Spoiler anzeigen

      C#-Quellcode

      1. private bool ValidateLicense(byte[] signature)
      2. {
      3. // Um die Lizenz auf Gültigkeit zu prüfen müssen alle zu prüfenden Parameter (Name, Typ) in einen Buffer gepackt werden
      4. // Dies kann man wie folgt umsetzen:
      5. // Standardisierung des Namens des Lizenznehmers
      6. var licenseeGen = GeneralizeDataString(this._licensee); // "ERIKAMUSTERMANN"
      7. // Zusammenfüren des Namens "ERIKAMUSTERMANN" mit dem Int-Wert des Lizenztyps (z. B. 2 für "Commercial").
      8. var dataStr = licenseeGen + (int)this._type; //ERIKAMUSTERMANN2
      9. // Erstellen eines Byte-Arrays aus dem zusammengefügten String
      10. var dataBuffer = System.Text.Encoding.UTF8.GetBytes(dataStr);
      11. // Crypto-Provider erstellen
      12. using (var provider = new RSACryptoServiceProvider())
      13. {
      14. // Den Public Key festlegen
      15. provider.FromXmlString(_publicKey);
      16. provider.PersistKeyInCsp = false;
      17. // Daten mit VerifyData überprüfen
      18. // Übergeben wird hier der Datenpuffer, das Hashing-Verfahren für die Signatur und Signatur selbst
      19. // In diesem Fall verwende ich SHA1
      20. return provider.VerifyData(dataBuffer, new SHA1CryptoServiceProvider(), signature);
      21. // Wenn die Daten gültig sind, sind die Lizenzdaten ebenfalls gültig. Wenn nicht, dann nicht.
      22. }
      23. }



      Das 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!(!)

      Mein Private Key in dem Fall:
      Spoiler anzeigen

      XML-Quellcode

      1. <RSAKeyValue><Modulus>8CKn78RI6h7vNOPMeMCeRCHegEgG1nR+X84B8b3sOZF6hAjDXF80ag1Zw1T0E+NVHmbPB8aLgRPmQPA351ZR8D+BCHooDlGqstLLHiqTu9bbqRVPti46XBeju3Fbi47euO+omH0sq7LCuIZ5s1WBmTc9ejkkfc/0rk3fAYaIRuE=</Modulus><Exponent>AQAB</Exponent><P>/m1FEqol/KKhxOyGsK4GVuansBXhrAgpwMlYLT+vF0gy1jzYQDNNQXzeQFYH6gZY66RTYFl3JPNL8KXLyhwDLQ==</P><Q>8Z7DrGQsGhiLgg70j40/+AgfNKJB4SXY7FmyBmLPRiHkT2d3AyvzuNNf/hkHA2UMLQT4xewmkxK9MU2nDitzBQ==</Q><DP>uRVOSSyjo6u/WJzjwoVmMTNryymv2FC75vXRgmEwgxRPfxAWFGX9jmVC3LR432KsrwcEbDPI+4VNugsyO52zJQ==</DP><DQ>AJFY8FzD5cPNAB883+F7FwAd4qfG89p86gFD89PjnMyTlsQteWpvBi4o+ZXheFaScsCiPQTTCmFu5GDEVbowaQ==</DQ><InverseQ>7gQ8MGqjjrCAfOzrrC9ZuVdGRfEjUEdHMqiF+js7XNBvnT5lBznUOd+eta6CGo7S5hjU7D3CEzmVGQfxUsRZ1w==</InverseQ><D>6YSaERSs31dTwPghV+/gOFtDVzYzyAqi9iGMTHwnotfw70LiUAqZGuR+vO/5Jvn0RUsu2t3dvZkPWWkAxCtyIzALk8Brx1r8n76VHVWMzkZvOoqMa1/HdZCXM0TVlpnYVJjyUA8wzi4tzPIPv08lAGwYJzHcoMlFHkQ2npqflxE=</D></RSAKeyValue>


      (Anmerkung: Beim XML-Format ist hier der Public Key mit dabei)

      Der dazugehörige Public Key:
      Spoiler anzeigen

      XML-Quellcode

      1. <RSAKeyValue><Modulus>8CKn78RI6h7vNOPMeMCeRCHegEgG1nR+X84B8b3sOZF6hAjDXF80ag1Zw1T0E+NVHmbPB8aLgRPmQPA351ZR8D+BCHooDlGqstLLHiqTu9bbqRVPti46XBeju3Fbi47euO+omH0sq7LCuIZ5s1WBmTc9ejkkfc/0rk3fAYaIRuE=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>



      Ich 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.

      Der 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!

      ...weiter im Text.

      Den Public Key fügen wir einfach oben als String-Wert der Konstante ein.

      Spoiler anzeigen

      C#-Quellcode

      1. private const string _publicKey = @"<RSAKeyValue><Modulus>8CKn78RI6h7vNOPMeMCeRCHegEgG1nR+X84B8b3sOZF6hAjDXF80ag1Zw1T0E+NVHmbPB8aLgRPmQPA351ZR8D+BCHooDlGqstLLHiqTu9bbqRVPti46XBeju3Fbi47euO+omH0sq7LCuIZ5s1WBmTc9ejkkfc/0rk3fAYaIRuE=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";



      Soweit sind wir fertig! Der Client kann nun eine Lizenz parsen, sie in eine Klasse stecken und mittels RSA-Signatur validieren.

      Lizenzen ausstellen

      Um Lizenzen auszustellen benötigen wir den Private Key. Bitte achte darauf, dass _niemand_ außer Dir Zugriff auf diesen Schlüssel haben darf.

      Um dies zu tun bietet sich ein Server an.

      Mit PHP und der PHPSecLib könnte es wie folgt gehen. Die PHPSecLib bekommst Du hier. Falls Du das GitHub-Repo geklont hast, ist es dort ebenfalls dabei.
      Spoiler anzeigen

      PHP-Quellcode

      1. // Abbildung des Enums, das wir auch in der Client-Anwendung haben
      2. class LicenseType
      3. {
      4. const Personal = 1;
      5. const Commercial = 2;
      6. const OpenSource = 3;
      7. }


      PHP-Quellcode

      1. // Generierung von Lizenzen in einer separaten Klasse
      2. class LicenseCreator
      3. {
      4. // Niemals anderen Leuten zugänglich machen!
      5. const privateKey = '<RSAKeyValue><Modulus>8CKn78RI6h7vNOPMeMCeRCHegEgG1nR+X84B8b3sOZF6hAjDXF80ag1Zw1T0E+NVHmbPB8aLgRPmQPA351ZR8D+BCHooDlGqstLLHiqTu9bbqRVPti46XBeju3Fbi47euO+omH0sq7LCuIZ5s1WBmTc9ejkkfc/0rk3fAYaIRuE=</Modulus><Exponent>AQAB</Exponent><P>/m1FEqol/KKhxOyGsK4GVuansBXhrAgpwMlYLT+vF0gy1jzYQDNNQXzeQFYH6gZY66RTYFl3JPNL8KXLyhwDLQ==</P><Q>8Z7DrGQsGhiLgg70j40/+AgfNKJB4SXY7FmyBmLPRiHkT2d3AyvzuNNf/hkHA2UMLQT4xewmkxK9MU2nDitzBQ==</Q><DP>uRVOSSyjo6u/WJzjwoVmMTNryymv2FC75vXRgmEwgxRPfxAWFGX9jmVC3LR432KsrwcEbDPI+4VNugsyO52zJQ==</DP><DQ>AJFY8FzD5cPNAB883+F7FwAd4qfG89p86gFD89PjnMyTlsQteWpvBi4o+ZXheFaScsCiPQTTCmFu5GDEVbowaQ==</DQ><InverseQ>7gQ8MGqjjrCAfOzrrC9ZuVdGRfEjUEdHMqiF+js7XNBvnT5lBznUOd+eta6CGo7S5hjU7D3CEzmVGQfxUsRZ1w==</InverseQ><D>6YSaERSs31dTwPghV+/gOFtDVzYzyAqi9iGMTHwnotfw70LiUAqZGuR+vO/5Jvn0RUsu2t3dvZkPWWkAxCtyIzALk8Brx1r8n76VHVWMzkZvOoqMa1/HdZCXM0TVlpnYVJjyUA8wzi4tzPIPv08lAGwYJzHcoMlFHkQ2npqflxE=</D></RSAKeyValue>';
      6. public static function CreateLicense($licensee, $type)
      7. {
      8. // Gleiche Generalisierung wie am Client:
      9. $licenseeGen = self::GeneralizeDataString($licensee);
      10. $dataStr = $licenseeGen . (int)$type; // "ERIKAMUSTERMANN2"
      11. $rsa = new Crypt_RSA(); // Neue RSA-Klasse erstellen
      12. // Setzen der RSA-Optionen auf die, die auch am Client verwendet werden:
      13. $rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_XML);
      14. $rsa->setHash('SHA1');
      15. $rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
      16. // privaten Schlüssel laden
      17. $rsa->loadKey(self::privateKey);
      18. // Erstellen der Signatur
      19. $signature = $rsa->sign($dataStr);
      20. // Formatierte Lizenzdaten zurückgeben
      21. return self::FormatLicense($licensee, $type, $signature);
      22. }
      23. private static function FormatLicense($licensee, $type, $signature)
      24. {
      25. // Binärdaten aus $signature in hexadezimal kodierten String umwandeln
      26. $formattedSignature = self::EncodeDataToHexString($signature);
      27. // Signatur in 29-Zeichen-Blöcke aufteilen (sieht schöner aus)
      28. $formattedSignature = chunk_split($formattedSignature, 29);
      29. $l = "--------BEGIN LICENSE--------\n"; // Unser Anfangsblock
      30. $l .= $licensee . "\n"; // Der Name des Lizenznehmers
      31. $l .= (int)$type . "\n"; // Der Lizenztyp als Int
      32. $l .= trim($formattedSignature) . "\n"; // die in mehrere Zeilen aufgeteilte, kodierte Signatur
      33. $l .= "---------END LICENSE---------"; // Ende der Lizenz
      34. return $l;
      35. }
      36. private static function EncodeDataToHexString($data)
      37. {
      38. return strtoupper(bin2hex($data));
      39. }
      40. private static function GeneralizeDataString($someString)
      41. {
      42. // Gleiche Funktion wie am Client
      43. return strtoupper(self::StripWhiteSpace($someString));
      44. }
      45. private static function StripWhiteSpace($someString)
      46. {
      47. // Gleiche Funktion wie am Client, nur mit RegEx
      48. return preg_replace('/\s+/', '', $someString);
      49. }
      50. }



      Abschluss

      Das war's.

      Auf der Serverseite können wir nun mit Hilfe der LicenseCreator-Klasse eine Lizenz erstellen:

      PHP-Quellcode

      1. $license = LicenseCreator::CreateLicense("Erika Mustermann", LicenseType::Commercial);

      Heraus kommt sowas:

      Quellcode

      1. --------BEGIN LICENSE--------
      2. Erika Mustermann
      3. 2
      4. 0D0E9D62B80195C9C867CF451C312
      5. 80593BFAEE80450BDD46A2CEAFFED
      6. 6D378CD9408B328B05AC2C8D9A7AE
      7. D8B8B69D44DBF66EA0F814A800393
      8. 7AD16197EF4DB28FDD27CFF58B1FC
      9. 14DF3CD7912C41C2573BB0A0D59AD
      10. 94BE0EFCD804D8A809875F13CAC70
      11. 137F24E30478AE8DFD3B94025A38D
      12. 80D636637F725887869ED77E
      13. ---------END LICENSE---------


      Dieser String kann am Client validiert werden.

      C#-Quellcode

      1. var license = License.Parse(lizenzString);
      2. Console.WriteLine("Lizenz gültig? " + license.IsValid);
      3. if(license.IsValid)
      4. Console.WriteLine("Lizenztyp: " + license.Type);


      Was es zu beachten gibt
      - Die Schlüssel sollten lang genug gewühlt werden (mindestens 2048 Bit sollten ausreichen)
      - Niemand anders sollte auf den Private Key Zugriff haben, da das komplette System sonst hinfällig ist.

      Erstellen von Schlüsseln
      Dafü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:

      C#-Quellcode

      1. const int KeyLength = 2048;
      2. var rsa = new RSACryptoServiceProvider(KeyLength);
      3. File.WriteAllText("private_key.xml", rsa.ToXmlString(true));
      4. File.WriteAllText("public_key.xml", rsa.ToXmlString(false));


      Vorteile und Nachteile dieser Methode
      Vorteile
      • Keine Internetverbindung zum Validieren der Lizenz notwendig
      • Key-Generatoren sind so gut wie unmöglich, solange der Schlüssel lang genug gewählt wurde und der Private key privat bleibt
      • Geringe Fehleranfälligkeit, da man nicht auf Firewall-/Firmen-Umgebungen, die UAC oder ähnliches Rücksicht nehmen muss.


      Nachteile
      • Sehr einfach zu cracken


      Was noch gemacht werden muss
      • License.TryParse(), bei der keine Exception geworfen wird
      • Server-Beispiel mit Node.js


      "Einfach zu Cracken" vs "Keygens unmöglich":
      Das 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.

      Meine Meinung zu dem Thema:
      Ich 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.
      Generell sollte man IMO die Zeit lieber in die Funktionalität des Programms statt in ein komplexes Lizenzsystem stecken.
      Das hier gezeigte System ähnelt stark dem, welches u. A. bei Sublime Text zum Einsatz kommt.
      Ich finde diese Herangehensweise noch vertretbar, da sie recht simpel gehalten ist und trotzdem noch eine (kleine) Hürde bietet.
      Dieser 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.
      Außerdem habe ich auf meiner Arbeit oft mit schlecht programmierter Software zu tun, die häufig wegen ignorant implementiertem Lizenzkram die Funktion verweigert. Beispiel?
      Hier 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.



      Disclaimer
      Das Übliche:
      Disclaimer

      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
      ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
      WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
      DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
      ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
      (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
      LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
      ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
      (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
      SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
      Von meinem iPhone gesendet

      Dieser Beitrag wurde bereits 7 mal editiert, zuletzt von „nikeee13“ ()

      Hallo nikeee,

      frage zum Lizenssystem: Ich habe mir eigene Keys (Private & Public erstellt, mit dem von dir eingefügten Schnipsel in einer c# Win-Form) und bekomme hier die Meldung nicht durch 2 Teilbar.
      Logischerweiße habe ich auch den privateKey auf der Server-Seite getauscht.

      Die von mir erstellten Keys sind auch um einiges länger als Deine, was aber ja nichts weiter machen sollte.

      Mit den von dir gelieferten Beispielkeys funktioniert es prima.

      Hast Du eine Idee woran es liegen kann?

      Vielen Dank im voraus
      Tom
      Hi @tpieger,

      liegen deine Keys im gleichen Format vor? Tritt der Fehler bei dir in der DecodeDataFromString-Funktion auf? Wenn ja, scheinenen die Hex-Daten bei der Signatur nicht ganz zu stimmen (fehlt 1 Zeichen?).
      Von meinem iPhone gesendet