Produktaktivierung der eigenen Anwendung

    • VB.NET

    Es gibt 11 Antworten in diesem Thema. Der letzte Beitrag () ist von Michael K..

      Produktaktivierung der eigenen Anwendung

      Diesmal gibt's mehr einen Workshop als einen FAQ-Eintrag.
      Der folgende Artikel wurde von Dennis Alexander für entwickler-zeitung.de und unser Board verfasst.


      Einleitung
      Viele Entwickler möchten Ihren Benutzern ein ausgereiftes und vorallem sicheres System bieten, um die eigene Anwendung ausprobieren zu können. Es existieren dann - man kennt es alle - kurze Zeit später entsprechende Key-Generatoren, Lizenzfile-Maker und Konsorten im Internet, und jeder kann es sich herunterladen und kostenlos/illegal nutzen.

      Das hierbei entsprechende Geldsummen verloren gehen, dürfte jedem wohl klar sein. Deshalb sucht eigentlich jeder Entwickler, der seine Anwendung kommerziell vertreibt, einen lukrativen Code, mit dem er Lizenzen verwalten, Benutzer sperren und seine Anwendung sicher machen kann - Solch einen Code werden wir uns heute erarbeiten. An dieser Stelle ein Dank an Toni Thenhausen, der mich auf die Idee mit den SignedXML-Dateien brachte.


      Inhaltsverzeichnis
      1. Gedankengang des Ablaufs
      2. Technologien
        1. SignedXML
        2. Hash (RSA)
        3. Computerinformationen auslesen
      3. Das Beispiel
      4. Der ActicationProvider
      5. Das Fazit


      1. Gedankengang des Ablaufs

      Es gibt bereits viele Möglichkeiten, die Registrierungsdaten auf der Festplatte zu speichern. Eine wäre z.B. die Registry. Viele Entwickler fühlen sich sicher, wenn ein entsprechender Eintrag in der Registry steht, wo die Anzahl der verbleibenden Starts oder Tage im Klartext enthalten ist.

      Über den Sinn und Zweck solcher Maßnahmen möchte ich hier auch nicht debattieren. Fakt ist, das wir ein Grundkonzept erarbeiten müssen, andem sich unsere spätere Entwicklung orientiert.

      Auf jeden Fall benötigen wir ein Script auf einem sicheren Server, an den wir eine Anfrage senden können. Dieses Script muss Zugang zu einer Datenbank haben, in der Seriennummern und Benutzerdaten gespeichert sind. Zu den Benutzerdaten sollte eine Laufzeit als Datum angegeben sein.

      Um das ganze an einem Beispiel zu verdeutlichen, gleich ein Eintrag wie diesem:

      Quellcode

      1. Firma Seriennummer Datum
      2. BlueLab GmbH 291791923 31.12.2009


      Unser Client soll später die Prozessor-ID und die Festplatten-ID auslesen, und die Daten an unseren Server übertragen. In dieser Übertragung soll auch die Seriennummer und der Firmenname enthalten sein.

      Wir werden dann auf dem Server überprüfen, ob der Firmenname und die Seriennummer zueinander passen, überprüfen das Datum (Laufzeit-Datum > Installationsdatum) und werden die Hardware-ID und den Tag der Installation speichern.

      So können wir später bei einer weiteren Aktivierung prüfen, ob der Computer neuinstalliert wurde, oder ob die Software auf einem anderen Computer installiert werden soll.

      Anschließend übertragen wir das Datum an unseren Client, der eine Singed-XML Datei erstellen soll. Diese Datei enthält die Seriennummer, den Benutzernamen, die Laufzeit sowie die Hardware ID als Klartext. Dazu später mehr.

      Bei jedem Start soll das aktuelle Datum aus dem Internet ausgelesen werden, und mit dem aus der Lizenzdatei verglichen werden.

      Vorteile:
      • Bei Veränderungen an der Lizenzdatei wird diese ungültig
      • Durch Zurückstellen des Datums kann das System nicht ausgetrickst werden
      • Lizenzmodelle wie z.B. Volumenlizenzen, Einzelplatz-Lizenzen Demo & Co können bequem administriert werden
      • Die Anwendung kann nicht auf einem anderen Computer ausgeführt werden (ohne erneute Aktivierung)
      • Die Aktivierung funktioniert auch unter Windows Vista mit UAC-Kontensteuerung einwandfrei
      • Daten über den Benutzer, die Seriennummer sowie die Hardware-ID können gesammelt werden
      • Sie ist kostenlos
      Nachteile:
      • Der Computer muss über eine aktive Internetverbindung verfügen
      • Wenn eine Lizenz abgelaufen ist, wird die Lizenzdatei bisher noch nicht gelöscht
      • Die Übertragung an den Server über ein PHP-Script ist sehr unsicher (Empfehle hier WebServices, nutze aber im Beispiel ein PHP-Script)


      2. Technologien

      In diesem Artikel wende ich einige aktuelle Technologien an, über die man sich nochmals genauer informieren sollte:

      2.1 SignedXML
      Der System.Security.Cryptography.Xml-Namespace enthält Klassen, die das Erstellen und Validieren digitaler XML-Signaturen unterstützen. Die Klassen in diesem Namespace implementieren die Empfehlung des World Wide Web Consortium (W3C) "XML-Signature Syntax and Processing", die unter w3.org/TR/xmldsig-core/ beschrieben ist. MSDN

      2.2 Hash (RSA)
      RSA ist ein asymmetrisches Kryptosystem, das sowohl zur Verschlüsselung als auch zur digitalen Signatur verwendet werden kann. Wikipedia

      2.3 Computerinformationen auslesen
      Mithilfe von WMI (Windows Management Interface) kann auf Computerinformationen zugegriffen werden. MSDN


      3. Das Beispiel

      Hier ein Beispiel-Ablauf:

      VB.NET-Quellcode

      1. ' Einstellungen zum Testen
      2. Dim ÜberspringeDasErstellen As Boolean = True
      3. ' Ablauf der Aktivierung
      4. Status("Beginne Aktivierung ...")
      5. ' Generieren einer Unique-Number des Computers
      6. Status("Erzeugen einer Computer-ID...")
      7. Status("Computer-ID: " & GenerateUniqueComputerID())
      8. Leer()
      9. ' Prüfen auf Online-Status
      10. Status("Überprüfe auf Online-Status")
      11. Status("Internet verfügbar: " & CheckOnLineStatus())
      12. Leer()
      13. ' Sende Informationen vom Server
      14. Status("Sende Informationen an den Server")
      15. Dim LizenzBis As Date = DoHandshakeWithServer(Me.txtUsername.Text, Me.txtSerial.Text, GenerateUniqueComputerID())
      16. Status("Antwort: " & LizenzBis)
      17. ' Überprüfe ob die Lizenz abgelaufen ist
      18. If LizenzBis > Now.Date Then
      19. Status("(Die Lizenz ist noch " & DateDiff(DateInterval.Day, Now.Date, LizenzBis) & " Tage gültig)")
      20. Else
      21. Status("--> Die Lizenz ist abgelaufen")
      22. Return
      23. End If
      24. Leer()
      25. If ÜberspringeDasErstellen = False Then
      26. ' Erstelle Lizenz-Datei
      27. Status("Erstelle Lizenz-Datei ...")
      28. If CreateLicenceFile(Me.txtUsername.Text, Me.txtSerial.Text, GenerateUniqueComputerID(), LizenzBis) Then
      29. Dim fi As New IO.FileInfo(Application.LocalUserAppDataPath & "\licence.xml")
      30. Dim Grösse As String = Math.Round((fi.Length / 1024), 3) & " KB"
      31. Status("Lizenz-Datei wurde erstellt (Größe: " & Grösse & ") ...")
      32. Dim Antwort As MsgBoxResult = MsgBox("Soll der Ordner der Lizenzdatei geöffnet werden?", MsgBoxStyle.YesNo, "Ordner öffnen")
      33. If Antwort = MsgBoxResult.Yes Then
      34. Process.Start(Application.LocalUserAppDataPath)
      35. End If
      36. Else
      37. Status("--> Lizenz-Datei konnte nicht erstellt werden")
      38. Return
      39. End If
      40. Leer()
      41. End If
      42. ' Lizenz-Datei überprüfen
      43. Status("Überprüfe Lizenz-Datei auf Änderungen...")
      44. Status("Lizenzdatei OK: " & CheckLicenceFile())
      45. Leer()
      46. Status("Überprüfe Lizenz auf Laufzeit...")
      47. Status("Lizenz gültig: " & CheckLicenceDate())
      48. Leer()
      49. Status("Aktivierung abgeschlossen")
      50. Status("Autor: Dennis Alexander Petrasch")
      51. Status("www.entwickler-zeitung.de")
      52. Leer()



      4. Der ActivationProvider

      VB.NET-Quellcode

      1. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''
      2. '''' -> Anwendungsaktivierung
      3. '''' (c) 2007 Dennis Alexander Petrasch
      4. '''' http://www.entwickler-zeitung.de
      5. ''''
      6. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''
      7. Imports System.Security.Cryptography
      8. Imports System.Security.Cryptography.Xml
      9. Imports System.Management
      10. Imports System.Net
      11. Imports System.Xml
      12. Imports System.IO
      13. Imports System
      14. Public Module ActivationProvider
      15. Dim Pfad As String = Application.LocalUserAppDataPath & "\licence.xml"
      16. '''
      17. ''' Private Funktion um auf das System.Management zuzugreifen
      18. '''
      19. ''' WMI-Klasse
      20. ''' WMI-Eigenschaft
      21. ''' Wert
      22. '''
      23. Private Function Identifier(ByVal wmiClass As String, ByVal wmiProperty As String) As String
      24. Dim Result As String = ""
      25. Dim mc As New System.Management.ManagementClass(wmiClass)
      26. Dim moc As System.Management.ManagementObjectCollection = mc.GetInstances
      27. Dim mo As System.Management.ManagementObject
      28. For Each mo In moc
      29. If Result = "" Then
      30. Try
      31. Result = mo(wmiProperty).ToString
      32. Exit For
      33. Catch ex As Exception
      34. End Try
      35. End If
      36. Next mo
      37. Return Result
      38. End Function
      39. '''
      40. ''' Erzeugt eine einmalige ID-Nummer des Computers
      41. '''
      42. ''' String
      43. '''
      44. Public Function GenerateUniqueComputerID() As String
      45. ' Prozessor-UniqueID
      46. Dim pID As String = Identifier("Win32_Processor", "UniqueId")
      47. ' Falls es keine UniqueID im Prozessor gibt, dann die ProzessorID
      48. If pID = "" Then
      49. pID = Identifier("Win32_Processor", "ProcessorId")
      50. End If
      51. ' Form aufbereiten & Festplattensignatur auslesen
      52. Return pID & "-" & Identifier("Win32_DiskDrive", "Signature")
      53. End Function
      54. '''
      55. ''' Überprüft, ob der Computer über eine aktive Internetverbindung verfügt
      56. '''
      57. ''' Boolean
      58. '''
      59. Public Function CheckOnLineStatus() As Boolean
      60. If My.Computer.Network.IsAvailable Then
      61. CheckOnLineStatus = My.Computer.Network.Ping("www.entwickler-zeitung.de")
      62. Else
      63. CheckOnLineStatus = False
      64. End If
      65. Return CheckOnLineStatus
      66. End Function
      67. '''
      68. ''' Sehr einfache Methode, ein Datum vom Server abzurufen
      69. '''
      70. ''' Benutzername
      71. ''' Seriennummer
      72. ''' Computer-ID
      73. ''' Datum
      74. '''
      75. Public Function DoHandshakeWithServer(ByVal Username As String, ByVal Password As String, ByVal ComputerID As String) As String
      76. ' Dies ist eine ziemlich unsichere Möglichkeit. Am besten wäre dafür ein WebService geeignet.
      77. Dim wc As New WebClient
      78. Dim Datum As Date
      79. Datum = Date.Parse(wc.DownloadString("http://www.entwickler-zeitung.de/activationtest.php?user=" & Username & "&pass=" & "&computerid=" & ComputerID))
      80. Return Datum
      81. End Function
      82. '''
      83. ''' Erzeugt eine Lizenzdatei
      84. '''
      85. ''' Benutzername
      86. ''' Seriennummer
      87. ''' ComputerID
      88. ''' Laufzeit
      89. ''' Boolean
      90. '''
      91. Public Function CreateLicenceFile(ByVal Username As String, ByVal Serial As String, ByVal ActivationID As String, ByVal Datum As Date) As Boolean
      92. Try
      93. Dim document As New XmlDocument()
      94. Dim Node As XmlNode
      95. Dim Benutzer, Zeit, Seriennummer, aID As XmlElement
      96. Node = document.CreateElement("Daten")
      97. Benutzer = document.CreateElement("Benutzername")
      98. Benutzer.InnerText = Username
      99. Node.AppendChild(Benutzer)
      100. Seriennummer = document.CreateElement("Seriennummer")
      101. Seriennummer.InnerText = Serial
      102. Node.AppendChild(Seriennummer)
      103. aID = document.CreateElement("ComputerID")
      104. aID.InnerText = ActivationID
      105. Node.AppendChild(aID)
      106. Zeit = document.CreateElement("Laufzeit")
      107. Zeit.InnerText = Datum
      108. Node.AppendChild(Zeit)
      109. document.AppendChild(Node)
      110. ' Create the SignedXml message.
      111. Dim signedXml As New SignedXml()
      112. Dim key As RSA = RSA.Create()
      113. signedXml.SigningKey = key
      114. ' Create a data object to hold the data to sign.
      115. Dim dataObject As New DataObject()
      116. dataObject.Data = document.ChildNodes
      117. dataObject.Id = "Aktivierungsdaten"
      118. ' Add the data object to the signature.
      119. signedXml.AddObject(dataObject)
      120. ' Create a reference to be able to package everything into the
      121. ' message.
      122. Dim reference As New Reference()
      123. reference.Uri = "#Aktivierungsdaten"
      124. ' Add it to the message.
      125. signedXml.AddReference(reference)
      126. ' Add a KeyInfo.
      127. Dim keyInfo As New KeyInfo()
      128. keyInfo.AddClause(New RSAKeyValue(key))
      129. signedXml.KeyInfo = keyInfo
      130. ' Compute the signature.
      131. signedXml.ComputeSignature()
      132. ' Get the XML representation of the signature.
      133. Dim xmlSignature As XmlElement = signedXml.GetXml()
      134. Using sw As New IO.StreamWriter(Pfad, False)
      135. sw.Write(xmlSignature.OuterXml)
      136. End Using
      137. Return True
      138. Catch ex As Exception
      139. Return False
      140. End Try
      141. End Function
      142. '''
      143. ''' Überprüft die Lizenz-Datei auf mögliche Veränderungen
      144. '''
      145. ''' Boolean
      146. '''
      147. Public Function CheckLicenceFile() As Boolean
      148. Dim signedXml As New SignedXml()
      149. Dim xmlDocument As New XmlDocument()
      150. xmlDocument.PreserveWhitespace = True
      151. xmlDocument.Load(New XmlTextReader(Pfad))
      152. Dim nodeList As XmlNodeList = xmlDocument.GetElementsByTagName("Signature")
      153. signedXml.LoadXml(CType(nodeList(0), XmlElement))
      154. If signedXml.CheckSignature() Then
      155. Return True
      156. Else
      157. Return False
      158. End If
      159. End Function
      160. '''
      161. ''' Überprüft die Laufzeit
      162. '''
      163. ''' Boolean
      164. '''
      165. Public Function CheckLicenceDate() As Boolean
      166. If Not File.Exists(Pfad) Then
      167. Return False
      168. End If
      169. If CheckOnLineStatus() Then
      170. Dim wc As New WebClient
      171. Dim Heute As Date
      172. Heute = Date.Parse(wc.DownloadString("http://www.entwickler-zeitung.de/today.php"))
      173. Dim xmlDocument As New XmlDocument()
      174. xmlDocument.PreserveWhitespace = True
      175. xmlDocument.Load(New XmlTextReader(Pfad))
      176. Dim nodeList As XmlNodeList = xmlDocument.GetElementsByTagName("Laufzeit")
      177. Dim Datum As Date = nodeList(0).InnerText
      178. If Datum > Heute Then
      179. Return True
      180. Else
      181. Return False
      182. End If
      183. Else
      184. Dim xmlDocument As New XmlDocument()
      185. xmlDocument.PreserveWhitespace = True
      186. xmlDocument.Load(New XmlTextReader(Pfad))
      187. Dim nodeList As XmlNodeList = xmlDocument.GetElementsByTagName("Laufzeit")
      188. Dim Datum As Date = nodeList(0).InnerText
      189. If Datum > Now.Date Then
      190. Return True
      191. Else
      192. Return False
      193. End If
      194. End If
      195. End Function
      196. End Module


      Wobei wie Anfangs gesagt hier ein PHP-Script verwendet wird. Ich empfehle ein WebService zu nutzen, da aber nicht jeder einen entsprechenden Host hat, stelle ich hier eine PHP basierte Lösung an.


      5. Das Fazit

      Diese Art der Aktivierung ist sicher, enthält aber z.B. bei der Überprüfung des Datums eine Schwachstelle. Mithilfe der digital signierten XML-Lizenzdateien ist das Verfahren sehr sicher.

      Das Problem jedoch ist, das die Lizenzdateien vom Client aus her generiert werden. Wenn man ein WebService nutzt, sollte man das erstellen eines Zertifikats auf den Server übertragen - denn: beim Dekompilieren der Anwendung sieht man den Quellcode und kann sich ganz einfach eigene Lizenzdateien erstellen.

      Falls jemand weitere Denkanstöße liefern könnte, würde ich mich darüber freuen.
      Bislang ist dies die sicherste Lösung, die mit Quellcode im Internet veröffentlich wurde.


      Nochmal ein großes Dankeschön an Dennis Alexander und seine Community, entwickler-zeitung.de :)


      Keywords: Visual Basic, vb.net, vb2005, Registrierung, Produktaktivierung, Product activation, XML, Signed XML, Hash, RSA, Prozessor Nummer, CPU number, Processor ID, Festplattennummer, HDD number, Disk Drive ID

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Mad Andy“ ()

      Hallo zusammen,

      so ein aktivierungs php würde mich interessieren...

      Vorallem vielleicht in Verbidung einer MDB Datenbank und nicht mit einem SQL Server.

      Denke die meisten werden sich nicht selbst einen Server mit Apache, MySQL oder gar einem ASP-Webserver mit MSSQL...

      So etwas über einem Provider ... ZahleMannUndSöhne...

      Ein php in Verbidung einer MDB DB könnte mann bei jedem Provider auch so hinterlegen...

      Gruß
      Alex
      Hallo zusammen,

      ich habe keine Scripte mehr von meinen VB-Zeiten. In meinem Artikel wird aber genannt wie die Logik aufgebaut ist, auf welche Plattform man das ganze überträgt spielt dabei ja keine Rolle.

      Außerdem ist der Artikel ja auch schon "etwas" älter ;)
      Hallo, bei mir ist dieses Thema gerade sehr Aktuell -aus diesen grund würde ich mich über eine endsprechende ASP.NET Datei Freuen, vilecitht über einen WebService Coode. Egal. Jedenfals irgendetwas um diesen Coode auch mit den Klassiscvhen ASP.NET Webdiensten regeln zu künnen!