Feedback

C# - Data Fort

Veröffentlicht von am 4/2/2008
(3 Bewertungen)
Speichert Passwörter und andere geheime Daten in den isolierten Speicher des Systems.
Es wird das "Protection in Deepth" Muster angewandt, denn die zu speichernden Daten werden
nur verschlüsselt (SHA512 Algorythmus inkl. SALT) in die gut geschützte XML Datei geschrieben.

Weiters ist es möglich nach einer gewissen Anzahl von Fehlversuchen ein Ereignis zu konsumieren.

Der Code !sollte! selbstklärend sein.


// 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

2 Kommentare zum Snippet

Greenberet schrieb am 4/7/2008:
Nettes Beispiel, nur du solltest von dem Gedanken wegkommen, dass eine Hash Funktion ein Verschlüsselungsverfahren ist. Zwischen Hash und Verschlüsselung liegen Welten...
Sperneder Patrick schrieb am 6/8/2008:
Du hast recht. Aber wirklich lesbar ist eine gehashte Datei auch nicht mehr.
Aber da die Datei im geschützten Speicher liegt ( und die CLR die Berechtigung auf die Datei zuzugreifen verwaltet ) denke ich ist es für normale Fälle absolut ausreichend.
Danke für dein Feedback
 

Logge dich ein, um hier zu kommentieren!