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
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:
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:
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:
4. Der ActivationProvider
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
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
- Gedankengang des Ablaufs
- Technologien
- SignedXML
- Hash (RSA)
- Computerinformationen auslesen
- Das Beispiel
- Der ActicationProvider
- 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:
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
- 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
- ' Einstellungen zum Testen
- Dim ÜberspringeDasErstellen As Boolean = True
- ' Ablauf der Aktivierung
- Status("Beginne Aktivierung ...")
- ' Generieren einer Unique-Number des Computers
- Status("Erzeugen einer Computer-ID...")
- Status("Computer-ID: " & GenerateUniqueComputerID())
- Leer()
- ' Prüfen auf Online-Status
- Status("Überprüfe auf Online-Status")
- Status("Internet verfügbar: " & CheckOnLineStatus())
- Leer()
- ' Sende Informationen vom Server
- Status("Sende Informationen an den Server")
- Dim LizenzBis As Date = DoHandshakeWithServer(Me.txtUsername.Text, Me.txtSerial.Text, GenerateUniqueComputerID())
- Status("Antwort: " & LizenzBis)
- ' Überprüfe ob die Lizenz abgelaufen ist
- If LizenzBis > Now.Date Then
- Status("(Die Lizenz ist noch " & DateDiff(DateInterval.Day, Now.Date, LizenzBis) & " Tage gültig)")
- Else
- Status("--> Die Lizenz ist abgelaufen")
- Return
- End If
- Leer()
- If ÜberspringeDasErstellen = False Then
- ' Erstelle Lizenz-Datei
- Status("Erstelle Lizenz-Datei ...")
- If CreateLicenceFile(Me.txtUsername.Text, Me.txtSerial.Text, GenerateUniqueComputerID(), LizenzBis) Then
- Dim fi As New IO.FileInfo(Application.LocalUserAppDataPath & "\licence.xml")
- Dim Grösse As String = Math.Round((fi.Length / 1024), 3) & " KB"
- Status("Lizenz-Datei wurde erstellt (Größe: " & Grösse & ") ...")
- Dim Antwort As MsgBoxResult = MsgBox("Soll der Ordner der Lizenzdatei geöffnet werden?", MsgBoxStyle.YesNo, "Ordner öffnen")
- If Antwort = MsgBoxResult.Yes Then
- Process.Start(Application.LocalUserAppDataPath)
- End If
- Else
- Status("--> Lizenz-Datei konnte nicht erstellt werden")
- Return
- End If
- Leer()
- End If
- ' Lizenz-Datei überprüfen
- Status("Überprüfe Lizenz-Datei auf Änderungen...")
- Status("Lizenzdatei OK: " & CheckLicenceFile())
- Leer()
- Status("Überprüfe Lizenz auf Laufzeit...")
- Status("Lizenz gültig: " & CheckLicenceDate())
- Leer()
- Status("Aktivierung abgeschlossen")
- Status("Autor: Dennis Alexander Petrasch")
- Status("www.entwickler-zeitung.de")
- Leer()
4. Der ActivationProvider
VB.NET-Quellcode
- ''''''''''''''''''''''''''''''''''''''''''''''''''''''''
- '''' -> Anwendungsaktivierung
- '''' (c) 2007 Dennis Alexander Petrasch
- '''' http://www.entwickler-zeitung.de
- ''''
- ''''''''''''''''''''''''''''''''''''''''''''''''''''''''
- Imports System.Security.Cryptography
- Imports System.Security.Cryptography.Xml
- Imports System.Management
- Imports System.Net
- Imports System.Xml
- Imports System.IO
- Imports System
- Public Module ActivationProvider
- Dim Pfad As String = Application.LocalUserAppDataPath & "\licence.xml"
- '''
- ''' Private Funktion um auf das System.Management zuzugreifen
- '''
- ''' WMI-Klasse
- ''' WMI-Eigenschaft
- ''' Wert
- '''
- Private Function Identifier(ByVal wmiClass As String, ByVal wmiProperty As String) As String
- Dim Result As String = ""
- Dim mc As New System.Management.ManagementClass(wmiClass)
- Dim moc As System.Management.ManagementObjectCollection = mc.GetInstances
- Dim mo As System.Management.ManagementObject
- For Each mo In moc
- If Result = "" Then
- Try
- Result = mo(wmiProperty).ToString
- Exit For
- Catch ex As Exception
- End Try
- End If
- Next mo
- Return Result
- End Function
- '''
- ''' Erzeugt eine einmalige ID-Nummer des Computers
- '''
- ''' String
- '''
- Public Function GenerateUniqueComputerID() As String
- ' Prozessor-UniqueID
- Dim pID As String = Identifier("Win32_Processor", "UniqueId")
- ' Falls es keine UniqueID im Prozessor gibt, dann die ProzessorID
- If pID = "" Then
- pID = Identifier("Win32_Processor", "ProcessorId")
- End If
- ' Form aufbereiten & Festplattensignatur auslesen
- Return pID & "-" & Identifier("Win32_DiskDrive", "Signature")
- End Function
- '''
- ''' Überprüft, ob der Computer über eine aktive Internetverbindung verfügt
- '''
- ''' Boolean
- '''
- Public Function CheckOnLineStatus() As Boolean
- If My.Computer.Network.IsAvailable Then
- CheckOnLineStatus = My.Computer.Network.Ping("www.entwickler-zeitung.de")
- Else
- CheckOnLineStatus = False
- End If
- Return CheckOnLineStatus
- End Function
- '''
- ''' Sehr einfache Methode, ein Datum vom Server abzurufen
- '''
- ''' Benutzername
- ''' Seriennummer
- ''' Computer-ID
- ''' Datum
- '''
- Public Function DoHandshakeWithServer(ByVal Username As String, ByVal Password As String, ByVal ComputerID As String) As String
- ' Dies ist eine ziemlich unsichere Möglichkeit. Am besten wäre dafür ein WebService geeignet.
- Dim wc As New WebClient
- Dim Datum As Date
- Datum = Date.Parse(wc.DownloadString("http://www.entwickler-zeitung.de/activationtest.php?user=" & Username & "&pass=" & "&computerid=" & ComputerID))
- Return Datum
- End Function
- '''
- ''' Erzeugt eine Lizenzdatei
- '''
- ''' Benutzername
- ''' Seriennummer
- ''' ComputerID
- ''' Laufzeit
- ''' Boolean
- '''
- Public Function CreateLicenceFile(ByVal Username As String, ByVal Serial As String, ByVal ActivationID As String, ByVal Datum As Date) As Boolean
- Try
- Dim document As New XmlDocument()
- Dim Node As XmlNode
- Dim Benutzer, Zeit, Seriennummer, aID As XmlElement
- Node = document.CreateElement("Daten")
- Benutzer = document.CreateElement("Benutzername")
- Benutzer.InnerText = Username
- Node.AppendChild(Benutzer)
- Seriennummer = document.CreateElement("Seriennummer")
- Seriennummer.InnerText = Serial
- Node.AppendChild(Seriennummer)
- aID = document.CreateElement("ComputerID")
- aID.InnerText = ActivationID
- Node.AppendChild(aID)
- Zeit = document.CreateElement("Laufzeit")
- Zeit.InnerText = Datum
- Node.AppendChild(Zeit)
- document.AppendChild(Node)
- ' Create the SignedXml message.
- Dim signedXml As New SignedXml()
- Dim key As RSA = RSA.Create()
- signedXml.SigningKey = key
- ' Create a data object to hold the data to sign.
- Dim dataObject As New DataObject()
- dataObject.Data = document.ChildNodes
- dataObject.Id = "Aktivierungsdaten"
- ' Add the data object to the signature.
- signedXml.AddObject(dataObject)
- ' Create a reference to be able to package everything into the
- ' message.
- Dim reference As New Reference()
- reference.Uri = "#Aktivierungsdaten"
- ' Add it to the message.
- signedXml.AddReference(reference)
- ' Add a KeyInfo.
- Dim keyInfo As New KeyInfo()
- keyInfo.AddClause(New RSAKeyValue(key))
- signedXml.KeyInfo = keyInfo
- ' Compute the signature.
- signedXml.ComputeSignature()
- ' Get the XML representation of the signature.
- Dim xmlSignature As XmlElement = signedXml.GetXml()
- Using sw As New IO.StreamWriter(Pfad, False)
- sw.Write(xmlSignature.OuterXml)
- End Using
- Return True
- Catch ex As Exception
- Return False
- End Try
- End Function
- '''
- ''' Überprüft die Lizenz-Datei auf mögliche Veränderungen
- '''
- ''' Boolean
- '''
- Public Function CheckLicenceFile() As Boolean
- Dim signedXml As New SignedXml()
- Dim xmlDocument As New XmlDocument()
- xmlDocument.PreserveWhitespace = True
- xmlDocument.Load(New XmlTextReader(Pfad))
- Dim nodeList As XmlNodeList = xmlDocument.GetElementsByTagName("Signature")
- signedXml.LoadXml(CType(nodeList(0), XmlElement))
- If signedXml.CheckSignature() Then
- Return True
- Else
- Return False
- End If
- End Function
- '''
- ''' Überprüft die Laufzeit
- '''
- ''' Boolean
- '''
- Public Function CheckLicenceDate() As Boolean
- If Not File.Exists(Pfad) Then
- Return False
- End If
- If CheckOnLineStatus() Then
- Dim wc As New WebClient
- Dim Heute As Date
- Heute = Date.Parse(wc.DownloadString("http://www.entwickler-zeitung.de/today.php"))
- Dim xmlDocument As New XmlDocument()
- xmlDocument.PreserveWhitespace = True
- xmlDocument.Load(New XmlTextReader(Pfad))
- Dim nodeList As XmlNodeList = xmlDocument.GetElementsByTagName("Laufzeit")
- Dim Datum As Date = nodeList(0).InnerText
- If Datum > Heute Then
- Return True
- Else
- Return False
- End If
- Else
- Dim xmlDocument As New XmlDocument()
- xmlDocument.PreserveWhitespace = True
- xmlDocument.Load(New XmlTextReader(Pfad))
- Dim nodeList As XmlNodeList = xmlDocument.GetElementsByTagName("Laufzeit")
- Dim Datum As Date = nodeList(0).InnerText
- If Datum > Now.Date Then
- Return True
- Else
- Return False
- End If
- End If
- End Function
- 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“ ()