// Teil 1. von DataFort (partielle Klasse)
#region Part1 DataFort.cs
using System;
using System.Drawing;
using System.IO;
using System.IO.IsolatedStorage;
using System.Xml;
using System.Diagnostics;
using System.Security.Principal;
using System.Security.Cryptography;
using System.Text;
using System.ComponentModel;
namespace sxSpernsoft.DataFort
{
[DesignTimeVisible (true ),ToolboxItem (true ),
ToolboxBitmap (typeof(sxSpernsoft.DataFort.Properties.Resources),"shield"),
Description("sxDataSafe ™.\n\n" +
"Stellt Möglichkeiten bereit, um Passwörter in verschlüsselter Form im isolierten" +
" Speicher des Betriebssystems zu speichern und zu manipulieren.\n" +
" ACHTUNG ! Die gespeicherten Passwörter können nur aus der Assembly die sie erstellt hat" +
" auch wieder ausgelesen oder verglichen werden !\n" +
"Copyright © 2008 Doc.NET.Noodles")]
public partial class sxDataSafe : Component
{
#region Konstruktoren
public sxDataSafe()
{
InitializeComponent();
}
public sxDataSafe(IContainer container)
{
container.Add(this);
InitializeComponent();
}
#endregion
#region lokale Variablen
// Das Dokument indem das Passwort in verschlüsselter Form abgelegt ist.
//
XmlDocument settingsDoc = null;
// Diese Datei heisst immer gleich !
//
const string storageName = "sxDataSafe.xml";
// Der Zähler der zählt wie oft eine Passworteingabe falsch sein darf,
// bevor das Event MultipleTriesFailed gefeuert wird.
private int _WrongInputs = 0;
// Die Begrenzung für die maximalen Anzahl der falschen Passworteingaben
//
private int _MAXWRONGINPUTS = 3;
// Das flag das angibt, ob bei falschen Eingaben ein Event abgefeuert werden soll
//
private bool _RaiseWrongInputMaximumReachedEvent = false;
// Das flag das angibt, ob bei gescheitertem Passwortwechsel ein Event gefeuert werden soll
//
private bool _RaisePasswordChangeFailedEvent = false;
#endregion
#region Ereignisse
public event DataSafeWrongPasswordInputMaximumReachedEventHandler WrongPasswordInputMaxReached;
public event DataSafePasswordChangeFailureEventHandler PasswordChangeFailed;
#endregion
#region sichere Eigenschaften
// Benutzer Eigenschaft. Nur innerhalb der Klasse lesbar.
//
private string User
{
get
{
if (settingsDoc != null)
{
XmlNode userNode = settingsDoc.SelectSingleNode("Settings/User");
if (userNode != null)
{
return userNode.InnerText;
}
return "";
}
return "";
}
}
// Passwort Eigenschaft. Nur innerhalb der Klasse lesbar.
//
protected string _pwd;
protected string Password
{
get
{
if (settingsDoc != null)
{
XmlNode pwdNode = settingsDoc.SelectSingleNode("Settings/Password");
if (pwdNode != null)
{
return pwdNode.InnerText;
}
return "";
}
return "";
}
}
#endregion
#region öffentliche Eigenschaften
/// <summary>
/// Die Anzahl der Fehlversuche bevor das event WrongInputsMaximumReached gefeuert wird.
/// </summary>
[Description ("Gibt die Anzahl der maximalen fehlgeschlagenen" +
" Anmeldeversuche zurück , oder legt diese fest.\n" +
"Der Default ist auf 3 gesetzt.")]
public int WrongInputsMaxValue
{
get { return this._MAXWRONGINPUTS ;}
set
{
if(value > 0)
{
this._MAXWRONGINPUTS = value ;
}
}
}
/// <summary>
/// Gibt die Anzahl der bereits falschen Passworteingaben zurück. (readonly)
/// </summary>
[Description("Gibt die Anzahl der fehlgeschlagenen Anmeldeversuche zurück.\n")]
public int ActualWrongInputs
{
get { return this._WrongInputs; }
}
/// <summary>
/// Gibt an, ob das WrongInputMaximumReachedEvent abgefeuert wird oder nicht.
/// Die Anzahl der Falscheingaben ist per Default auf 3 gesetzt, kann aber
/// über die Eigenschaft [int32] WrongInputMaxValue auf einen Wert > 0 gesetzt werden.
/// </summary>
[Description("Gibt an ob das Event 'WrongPasswordInputReached' abgefeuert werden soll, wenn das" +
" Maximum an Falscheingaben erreicht wurde.\n" +
"Diese Eigenschaft ist standardmässig deaktiviert.")]
public bool RaiseWrongInputMaximumReachedEvent
{
get { return this._RaiseWrongInputMaximumReachedEvent; }
set { this._RaiseWrongInputMaximumReachedEvent = value; }
}
/// <summary>
/// Flag das angibt, ob bei einem gescheiterten Versuch das Passwort zu ändern
/// das event PasswordChangeFailedEvent gefeuert werden soll.
/// </summary>
[Description("Gibt an ob das Event 'PasswordChangeFailed' abgefeuert werden soll, wenn der" +
" Wechsel des Passwortes fehlgeschlagen ist.\n" +
"Diese Eigenschaft ist standardmässig deaktiviert.")]
public bool RaisePasswordChangeFailedEvent
{
get { return this._RaisePasswordChangeFailedEvent; }
set { this._RaisePasswordChangeFailedEvent = value; }
}
#endregion
#region Private Methoden
/// <summary>
/// Erzeugt ein gehashtes Passwort mit Salt.
/// </summary>
/// <param name="password">Das Passwort im Klartext.</param>
/// <param name="existingSalt">Wenn schon ein Salt vorhanden ist, dann mitübergen, ansonsten
/// null übergeben, denn dann wird ein neuer Salt erzeugt.</param>
/// <returns></returns>
private string CreateHashedPassword(string password, byte[] existingSalt)
{
byte[] salt = null;
if (existingSalt == null)
{
// Ein Salt zufälliger Grösse erzeugen.
Random random = new Random();
int size = random.Next(16, 64);
// Das SaltArray erzeugen
salt = new byte[size];
// Den besseren Zufallsgenerator benutzen um die Bytes
// für das Salt zu erhalten.
RNGCryptoServiceProvider rngSalt = new RNGCryptoServiceProvider();
rngSalt.GetNonZeroBytes(salt);
}
else
salt = existingSalt;
// Den string in bytes umwandeln.
byte[] pwd = Encoding.UTF8.GetBytes(password);
// Speicherplatz für Passwort und Salt schaffen.
byte[] saltedPWD = new byte[pwd.Length + salt.Length];
// erst die PWD bytes hinzufügen
pwd.CopyTo(saltedPWD, 0);
// Jetzt das Salt hinzu ..
salt.CopyTo(saltedPWD, pwd.Length);
// SHA12 als Hashing Algorythmus verwenden.
byte[] hashWithSalt = null;
using (SHA512Managed sha512 = new SHA512Managed())
{
// Den Hash-Wert für das Passwort mit dem Salt berechnen.
byte[] hash = sha512.ComputeHash(saltedPWD);
// Salt an den Hash-Wert anhängen, damit er verfügbar ist.
hashWithSalt = new byte[hash.Length + salt.Length];
// Bytes einkopieren.
hash.CopyTo(hashWithSalt, 0);
salt.CopyTo(hashWithSalt, hash.Length);
}
// Den Base64 codierten Hash-Wert mit Salt zurückliefern.
return Convert.ToBase64String(hashWithSalt);
}
/// <summary>
/// Diese Funktion wird aufgerufen, wenn ein bereits abgelegtes Passwort verglichen werden soll,
/// aber die Klasse jedoch noch nicht initialisiert ist, und daher keine Daten geladen sind.
///
/// Wenn keine Datei im isolierten Speicherbereich gefunden werden kann, die für den
/// Vergleich entspricht, dann wird eine ArgumentException geworfen.
/// </summary>
private void InitDataSafe()
{
// Den isolierten Speicher anfordern.
using (IsolatedStorageFile isoStorageFile =
IsolatedStorageFile.GetUserStoreForDomain())
{
// Ein internes DOM für die Einstellungen speichern
settingsDoc = new XmlDocument();
// wenn noch keine Einstellungen vorhanden sind, Exception werfen,
// denn es kann kein Passwort abgefragt werden wenn noch nie zuvor eine
// Sicherheitsdatei angelegt wurde. Oder sie zuvor gelöscht wurde.
if (isoStorageFile.GetFileNames(storageName).Length == 0)
{
throw new ArgumentException("Es ist keine Datei namens '" + storageName +"' im isolierten Speicher vorhanden!");
}
// Zugriff auf den Einstellungsspeicher einrichten ..
using (IsolatedStorageFileStream isoFileStream =
new IsolatedStorageFileStream(storageName,
FileMode.Open,
isoStorageFile))
{
// Einstellungen aus dem isolierten Stream lesen.
settingsDoc.Load(isoFileStream);
// Einstellungen wurden geladen ..
}
}
}
#endregion
#region Öffentliche Methoden
/// <summary>
/// Überprüft das Passwort das im Klartext übergeben wurde, mit dem Passwort
/// das im isolierten Speicher im verschlüsselten Zustand liegt.
/// </summary>
/// <param name="password">Das Passwort im Klartext.</param>
/// <returns>Wenn das Passwort authentifiziert ist, wird 'true' ansonsten 'false' zurückgeliefert.</returns>
public bool CheckPassword(string password)
{
if (this.Password == "")
{
// Dann ist es eine Abfrage des Passwortes, ohne das die Klasse zuvor erstellt wurde ..
// dadurch müssen wir jetzt die Datei aus dem Speicher laden und die Felder bestücken
// damit wir den Vergleich der Passwörter ausführen können.
InitDataSafe();
}
// Bytes für das Passwort ermitteln..
// Das ist die Hash/Salt-Kombination für das Passwort.
byte[] hashWithSalt = Convert.FromBase64String(Password);
// ich habe 512 Bits als Hash-Grösse verwendet (SHA512)
int hashSizeInBytes = 512 / 8;
// Eine Variable für das ursprüngliche Salte erzeugen
int SaltSize = hashWithSalt.Length - hashSizeInBytes;
byte[] salt = new byte[SaltSize];
// Den Salt-Wert herauskopieren
Array.Copy(hashWithSalt, hashSizeInBytes, salt, 0, SaltSize);
// Den Hash-Wert für dieses Passwort ermitteln.
string PasswordHash = CreateHashedPassword(password, salt);
// Wenn die errechneten Werte gleich sind, dann ist auch das Passwort
// verifiziert.
if (Password == PasswordHash)
{
// wenn die Passworteingabe gestimmt hat, dann wird der Fehlerzähler
// wieder zurückgesetzt.
this._WrongInputs = 0;
return true;
}
// wenn nicht, dann Fehler mitzählen ..
else
{
// Falscheingaben mitzählen ..
this._WrongInputs += 1;
// wenn das Maximum der Falscheingaben erreicht ist ,
if (this._WrongInputs >= this._MAXWRONGINPUTS)
{
// wenn dies auch gemeldet werden soll ,
if (this._RaiseWrongInputMaximumReachedEvent)
{
// event abfeuern ..
this.WrongPasswordInputMaxReached(this,
new DataSafeWrongInputMaximumReachedEventArgs(this.User,
DateTime.Now,
this._WrongInputs));
return false;
}
}
return false;
}
}
/// <summary>
/// Speichert das Passwort in isolierten Speicher ab.
/// </summary>
/// <param name="password">Klartext Passwort [string].</param>
/// <returns>Wenn das Passwort gespeichert wurde 'true', wenn schon ein Passwort vorhanden ist 'false'.</returns>
public bool SavePassword(string password)
{
if (password == "") { throw new ArgumentException("Das Passwort darf nicht 'null' oder 'string.Empty' sein !"); }
try
{
// Den isolierten Speicher anfordern.
using (IsolatedStorageFile isoStorageFile =
IsolatedStorageFile.GetUserStoreForDomain())
{
// Ein internes DOM für die Einstellungen speichern
settingsDoc = new XmlDocument();
// wenn noch keine Einstellungen vorhanden sind, Defaults verwenden.
if (isoStorageFile.GetFileNames(storageName).Length == 0)
{
using (IsolatedStorageFileStream isoFileStream =
new IsolatedStorageFileStream(storageName,
FileMode.Create,
isoStorageFile))
{
using (XmlTextWriter writer = new
XmlTextWriter(isoFileStream, Encoding.UTF8))
{
writer.WriteStartDocument();
writer.WriteStartElement("Settings");
writer.WriteStartElement("User");
// aktuellen Benutzer abrufen,
WindowsIdentity user = WindowsIdentity.GetCurrent();
writer.WriteString(user.Name);
writer.WriteEndElement();
writer.WriteStartElement("Password");
// CreateHashedPassword als Salt null übergeben,
// damit ein Salt-Wert generiert wird.
// CreateHashedPassword wird in Kürze vorgestellt.
string hashedPassword =
CreateHashedPassword(password, null);
writer.WriteString(hashedPassword);
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndDocument();
}
return true;
}
}
else
{
// Es ist schon eine Passwortdatei vorhanden,
return false;
}
}
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// Ändert das aktuelle Passwort in das neue,
/// vorausgesetzt es wird zuerst das alte 'richtige' Passwort übergeben.
/// </summary>
/// <param name="oldpassword">Das bisher verwendete Passwort.</param>
/// <param name="newpassword">Das neue Passwort.</param>
/// <returns>Bei Erfolg 'true' ansonsten 'false'.</returns>
public bool ChangePassword(string oldpassword, string newpassword)
{
// wenn das Passwort nicht stimmt, überprüfen was geschehen soll
if (!CheckPassword(oldpassword))
{ // wenn erwünscht, das event abfeuern
if (this._RaisePasswordChangeFailedEvent)
{
this.PasswordChangeFailed(this, new DataSafePasswordChangeFailedEventArgs(this.User, DateTime.Now));
return false;
}
return false;
}
// Nun da wir wissen, dass das Passwort stimmt, müssen wir das aktuelle Passwort
// neu verschlüsseln und im isolierten Speicher ablegen.
// Den isolierten Speicher anfordern.
using (IsolatedStorageFile isoStorageFile =
IsolatedStorageFile.GetUserStoreForDomain())
{
// Die Datei neu erstellen, und danach der Klasse wieder zur Verfügung zu stellen.
using (IsolatedStorageFileStream isoFileStream = new IsolatedStorageFileStream(storageName,
FileMode.Open,
isoStorageFile))
{
using (XmlTextWriter writer = new
XmlTextWriter(isoFileStream, Encoding.UTF8))
{
writer.WriteStartDocument();
writer.WriteStartElement("Settings");
writer.WriteStartElement("User");
// aktuellen Benutzer abrufen,
WindowsIdentity user = WindowsIdentity.GetCurrent();
writer.WriteString(user.Name);
writer.WriteEndElement();
writer.WriteStartElement("Password");
// CreateHashedPassword als Salt null übergeben,
// damit ein Salt-Wert generiert wird.
// CreateHashedPassword wird in Kürze vorgestellt.
string hashedPassword = CreateHashedPassword(newpassword, null);
writer.WriteString(hashedPassword);
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndDocument();
}
// Die Datei wieder laden, um die Eigenschaften wieder darstellen zu können.
// Zugriff auf den Einstellungsspeicher einrichten ..
using (IsolatedStorageFileStream storedFileStream =
new IsolatedStorageFileStream(storageName,
FileMode.Open,
isoStorageFile))
{
// Einstellungen aus dem isolierten Stream lesen.
settingsDoc.Load(storedFileStream);
// Einstellungen wurden geladen ..
}
}
}
// Passwort erfolgreich geändert.
return true;
}
/// <summary>
/// Löscht die Passwortdatei aus dem isolierten Speicher.
/// </summary>
/// <returns>Bei erfolgter Löschung 'true' andernfalls 'false'.</returns>
public bool DeleteIsolatedCryptoFile()
{
using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForDomain())
{
if (isoFile.GetFileNames(storageName).Length == 0)
{
// dann ist die Datei nicht mehr im isolierten Speicher vorhanden.
return false;
}
else
{ // Dateie löschen ..
isoFile.DeleteFile(storageName);
return true;
}
}
}
/// <summary>
/// Löscht den gesamten Isolierten Speicher für diesen User.
/// </summary>
/// <returns>Wenn die Ausführung erfolgreich war, dann wird das Objekt anschliessend entsorgt,
/// andernfalls liefert es false zurück.</returns>
public bool DeleteIsolatedStorage()
{
try
{
IsolatedStorageFile.Remove(IsolatedStorageScope.User );
// Alle Eigenschaften die aus dem XML Dokument lesen, werden mit dem
// Zurücksetzen auch zurückgesetzt.
// Wird nachdem das Dokument auf Null gesetzt wurde auf eine Eigenschaft zugegriffen,
// Dann wird eine Exception geworfen.
this.settingsDoc = null;
return true;
}
catch (Exception)
{
return false;
}
}
#endregion
}
}
#endregion
#region Part2 Datafort.designer.cs
// der zweite Teil der partiellen Klasse. (Designer)
namespace sxSpernsoft.DataFort
{
partial class sxDataSafe
{
/// <summary>
/// Erforderliche Designervariable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Verwendete Ressourcen bereinigen.
/// </summary>
/// <param name="disposing">True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Vom Komponenten-Designer generierter Code
/// <summary>
/// Erforderliche Methode für die Designerunterstützung.
/// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
}
}
#endregion
#region EventArgs
using System;
using System.Collections.Generic;
using System.Text;
namespace sxSpernsoft.DataFort
{
public class DataSafeWrongInputMaximumReachedEventArgs : EventArgs
{
string _user;
DateTime _timestamp;
int _trys;
public DataSafeWrongInputMaximumReachedEventArgs(string user, DateTime timestamp, int FailedTrys)
{
this._user = user;
this._timestamp = timestamp;
this._trys = FailedTrys;
}
public string User { get { return _user; } }
public DateTime TimeStamp { get { return _timestamp; } }
public int FailedTrys { get { return _trys; } }
}
public class DataSafePasswordChangeFailedEventArgs : EventArgs
{
string _user;
DateTime _timestamp;
public DataSafePasswordChangeFailedEventArgs(string user, DateTime timestamp)
{
this._user = user;
this._timestamp = timestamp;
}
public string User { get { return _user; } }
public DateTime TimeStamp { get { return _timestamp; } }
}
}
#endregion
#region Eventhandler
using System;
using System.Collections.Generic;
using System.Text;
namespace sxSpernsoft.DataFort
{
public delegate void DataSafeWrongPasswordInputMaximumReachedEventHandler(object sender, DataSafeWrongInputMaximumReachedEventArgs e);
public delegate void DataSafePasswordChangeFailureEventHandler(object sender, DataSafePasswordChangeFailedEventArgs e);
}
#endregion