Dies ist zwar ganz schön, aber es hilft uns nicht wirklich für eine sinnvolle Überprüfung der Kombination. Deshalb habe ich mir heute morgen und den Tag über überlegt, wie man eine "relativ" sichere Überprüfung des Namen und der Seriennummer realisieren kann. Resultat ist dieser Artikel.
Unser "fertiges" Ergebnis wird später so aussehen und technisch ziemlich was unter der "Haube" haben:

Bei vielen Anwendungen setzen die Entwickler auf eine "schnelle" Benutzername/Seriennummer Kombination, was meistens bewirkt, das binnen weniger Tage ein entsprechender KeyGen für die Anwendung verfübar ist. Dies ist natürlich nicht wirklich Sinn der Sache, deswegen sollte man - aus meiner Sicht - schon einen relativen Aufwand fahren um es wenigstens zu erschweren. Verhindern kann man es logischerweise nie. Es gibt bis auf Software-/Hardware bzw. Software->Webdatenbank/Hardware-Lösungen keine 100% sicheren Lösungen. (Und auch diese sind nich 100%ig, aber sind in vielen Fällen soaufwendig das es viel zu viel Zeit oder zuviel Rechenleistung benötigt, diese auszuhebeln)
Lediglich versuchen wir es den Crackern nur schwer zu machen, sodass diese viel zu viel Zeit benötigen und dadurch entweder die verschlüsselte Information "abgelaufen" bzw. "verfallen" ist, oder eine neue Version mit einem neuen Mechanis erschienen ist. Um dies jedoch zu realisieren ist eine ausgefeilte Logik und gute Verschlüsselungsroutinen nötig.
Dies war auch der Ansatzpunkt meiner Methode. Sie sollte für den Anwender leicht zu implementieren sein, aber dennoch (für eine kostenlose Komponente) einen guten Schutz bieten. Das beste ist natürlich diese nicht offen preiszugeben, da dies ein großer Angriffspunkt ist, aber man kann diese an sehr vielen Stellen sehr gut abändern bzw. mit weiteren Mechanismen erweitern.
Funktionsweise & Parameter
Es werden mehrere Parameter benötigt, um die Funktion generateSerialNumber() aufzurufen:
- userName <- das ist bspw. der Name des Lizenznehmers (Max Mustermann)
- saltBytes <-- ein zusätzliche Sicherung: eine zufällige Byte-Folge zum Salten der Verschlüsselung. (Muss logischerweise beim Generieren und beim Überprüfen gleich sein) [4-8 byte]
- useFields <-- Ein Integer-Array, welches bestimmte Zeichen aus dem generierten Hash für die Seriennummer nutzt.
Sollten diese Informationen vorhanden sein, arbeitet der Generator wie folgt:
- Generiert ein Byte-Array mit Name und dem Salt
- Hasht das Byte-Array mit SHA512
- Konvertiert das Byte-Array zu einem String
- Entfernt Sonderzeichen
- nutzt die Fields um die Seriennummer zu erzeugen
Um das ganze Anschließend zu Überprüfen, nutzen wir die Methode validateSerialNumber(). Auch diese nutzt Parameter:
- userName <- das ist bspw. der Name des Lizenznehmers (Max Mustermann)
- Serial <-- eingegebene Seriennummer des Benutzers
- saltBytes <-- ein zusätzliche Sicherung: eine zufällige Byte-Folge zum Salten der Verschlüsselung. (Muss logischerweise beim Generieren und beim Überprüfen gleich sein) [4-8 byte]
- useFields <-- Ein Integer-Array, welches bestimmte Zeichen aus dem generierten Hash für die Seriennummer nutzt.
- Generieren des Hashs des eingegebene Benutzernamens
- Vergleiche Ergebnis mit der eingegebene Seriennummer
Nun, wofür sind die Fields eigentlich?
Die Fields sind bestimmte Angaben, welche Elemente die Methode aus dem bereinigten String nutzen soll, um damit eine Seriennummer zu bauen. Wenn man bspw {0,1,2,3,4} nutzt, nimmt er die ersten 5 Elemente des bereinigten Strings. bspw "ABK5A".
Das Problem dabei ist, das auch diese Angabe bei der Generierung und der Überprüfung gleichen sein müssen. Ich habe deshalb eine Methode geschrieben (generateFieldsFromGUID()), die die Guid der Anwendung nutzt, um daraus automatisch Fields zu generieren. Dieses Verfahren ist deshalb so interessant, weil jede Guid einer Anwendung sich komplett unterscheidet und aus einer anderen Guid ganz andere Fields generiert werden. Eine Guid unterstützt lediglich 20 native Fields, die restlichen werden durch einen kleinen Algorithmus dazugerechnet. Es werden so bis zu 50-Zeichen lange Seriennummern generiert.
Ich denke dies sollte ausreichend sein.
Ich hoffe ich kann damit dem ein oder anderen einen Denkanstoß vermitteln. Es lassen sich noch viele weitere Mechanismen integrieren - experimentiert etwas herum!
Im Anhang findet ihr die oben vorgestellte Anwendung (nur zum Testen als .exe-Datei).
Der Aufruf:
Visual C#
// Aufruf ohne Field-Generator:
byte[] mySalt = { 104, 255, 12, 132, 91, 254, 29 };
int[] myFields = new int[] { 10, 29, 41, 58, 80, 45, 31, 23, 74, 42 };
// Generieren der Seriennummer
string generierteSeriennummer = generateSerialNumber("Name", mySalt, myFields);
// Überprüfen der Seriennummer
bool serialIsValid = validateSerialNumber("Name", generierteSeriennummer, mySalt, myFields);
// Aufruf MIT Field-Generator
byte[] mySalt = { 104, 255, 12, 132, 91, 254, 29 };
int[] myFields = generateFieldsFromGUID(10); // Seriennummer soll 10 Zeichen lang sein
// Generieren der Seriennummer
string generierteSeriennummer = generateSerialNumber("Name", mySalt, myFields);
// Überprüfen der Seriennummer
bool serialIsValid = validateSerialNumber("Name", generierteSeriennummer, mySalt, myFields);
Die Methoden:
Visual C#
/// <summary>
/// Generiert eine Seriennummer für einen bestimmten Namen
/// © Dennis Alexander Petrasch, dotnetBase.de
/// </summary>
/// <param name="userName">Benutzername</param>
/// <param name="saltBytes">4 oder 8 Salt-Byte-Array</param>
/// <param name="useFields">Hashelemente für Seriennummer</param>
/// <returns>gibt die Seriennummer zurück</returns>
public static string generateSerialNumber(string userName, byte[] saltBytes, int[] useFields)
{
// Wir konvertieren den Username in ein byte-Array
byte[] userNameBytes = Encoding.UTF8.GetBytes(userName);
// Erzeugen ein Byte-Array der Länge Username + Salt
byte[] userNameWithSaltBytes = new byte[userNameBytes.Length + saltBytes.Length];
// Kopieren den Usernamen in unser Byte-Array
for (int i = 0; i < userNameBytes.Length; i++)
{
userNameWithSaltBytes[i] = userNameBytes[i];
}
// Hängen unseren Salt an
for (int i = 0; i < saltBytes.Length; i++)
{
userNameWithSaltBytes[userNameBytes.Length + i] = saltBytes[i];
}
// Erzeugen einen neuen SHA384 HashProvider
HashAlgorithm shaCrypto = new SHA512Managed();
// Erzeugen einen Hash von unserem Usernamen + Salt
byte[] hashBytes = shaCrypto.ComputeHash(userNameWithSaltBytes);
// Erzeugen ein Byte-Array für den Hash und den Salt
byte[] hashWithSaltBytes = new byte[hashBytes.Length +
saltBytes.Length];
// Kopiere den Hash in unser Byte-Array
for (int i = 0; i < hashBytes.Length; i++)
hashWithSaltBytes[i] = hashBytes[i];
// Kopiere den Salt ebenfalls in das Byte Array
for (int i = 0; i < saltBytes.Length; i++)
hashWithSaltBytes[hashBytes.Length + i] = saltBytes[i];
// Konvertiere unser Byte-Array zu einem String
string hashValue = Convert.ToBase64String(hashWithSaltBytes);
// Entferne Sonderzeichen und Kleinschreibung
hashValue = hashValue.Replace("/", "").Replace("-", "").Replace("+", "");
hashValue = hashValue.Replace("=", "");
hashValue = hashValue.ToUpper();
string serialNumber = "";
// Lese bestimtme Zeichen aus
foreach (int i in useFields)
{
serialNumber += hashValue[i];
}
// Gebe den Wert zurück
return serialNumber;
}
/// <summary>
/// Überprüf die eingegebene Seriennummer mit der originalen
/// © Dennis Alexander Petrasch, dotnetBase.de
/// </summary>
/// <param name="userName">Benutzername</param>
/// <param name="Serial">eingegebene Seriennummer</param>
/// <param name="saltBytes">4 oder 8 Salt-Byte-Array</param>
/// <param name="useFields">Hashelemente für Seriennummer</param>
/// <returns>true wenn die Eingaben korrekt sind, ansonsten false</returns>
public static bool validateSerialNumber(string userName, string Serial, byte[] saltBytes, int[] useFields)
{
// Generieren Seriennummer und vergleiche
string expectedSerial = generateSerialNumber(userName, saltBytes, useFields);
// Gebe das Resultat des Vergleichs zurück
return (Serial == expectedSerial);
}
/// <summary>
/// Generiert bis zu 50 Fields anhand der GUID
/// © Dennis Alexander Petrasch, dotnetBase.de
/// </summary>
/// <param name="length">Anzahl der Zeichen der Seriennummer (max. 50)</param>
/// <returns></returns>
public static int[] generateFieldsFromGUID(int length)
{
// Erzeuge Arrays und einen Counter
byte[] acceptedValues = new byte[32];
int[] resultArray = new int[length];
int counter = 0;
// Guid Auslesen
object[] assemblyObjects = System.Reflection.Assembly.GetEntryAssembly().GetCustomAttributes(
typeof(System.Runtime.InteropServices.GuidAttribute), true);
byte[] guid = Encoding.UTF8.GetBytes(
new Guid(((System.Runtime.InteropServices.GuidAttribute)assemblyObjects[0]).Value).ToString("N"));
// Durchsuche Guid nach akzeptablen Fields
foreach (byte b in guid)
{
if (b <= 96)
{
if (counter < length)
{
// Field gefunden, füge der Liste hinzu
resultArray[counter] = b;
counter += 1;
}
}
}
// Sollten die Zeichen aus der Guid nicht reichen, erzeugen wir weitere
if (counter != length)
{
for (int i = counter; i != length; i++)
{
// Wir generieren neue dazu
if (resultArray[i - 1] + 1 <= 96)
{
resultArray[i] = resultArray[i - 1] + 1;
}
else
{
resultArray[i] = resultArray[i - 5] - 10;
}
}
}
// Gebe die generierten Fields zurück
return resultArray;
}
Angehängte Datei(en)
-
Serial Tester.rar (17,99K)
Anzahl der Downloads: 43


Hilfe











.












RSS Feed