Sha256 hashing von Userdaten (Name/Pwd) inkl. Salt und Pepper. Hab ich das korrekt umgesetzt?

  • C#

Es gibt 7 Antworten in diesem Thema. Der letzte Beitrag () ist von nogood.

    Sha256 hashing von Userdaten (Name/Pwd) inkl. Salt und Pepper. Hab ich das korrekt umgesetzt?

    Hi, ich hab mich ein wenig mit 'hashing' beschäftigt. Ich möchte meine User-Passwörter gehashed in meiner Cloud Datenbank speichern.
    Ich hab dazu eine Class mit zwei Methoden erstellt eine CreateHash()->HashString und ein ConfirmHash()->bool.

    Könnte jemand sagen, ob ich dass korrekt umgesetzt habe oder hab ich mich da selber irgendwo versalzen oder verpfeffert :rolleyes: ?

    CreateHash:

    C#-Quellcode

    1. public enum Supported_Hashes
    2. {
    3. SHA256, SHA384, SHA512
    4. }
    5. public class Hashing
    6. {
    7. private static readonly Random rnd = new();
    8. private static readonly RNGCryptoServiceProvider rng = new();
    9. //TODO ist UTF8 richtig?
    10. public string CreateHash(string inputText, Supported_Hashes hashType,byte[] salt, string pepper)
    11. {
    12. int minSaltLength = 12;
    13. int maxSaltLenth = 16;
    14. byte[] SaltBytes;
    15. byte[] PepperBytes = Encoding.UTF8.GetBytes(pepper);
    16. if (salt != null)
    17. {
    18. SaltBytes = salt;
    19. }
    20. else
    21. {
    22. int Saltlength = rnd.Next(minSaltLength, maxSaltLenth);
    23. SaltBytes = new byte[Saltlength];
    24. rng.GetNonZeroBytes(SaltBytes);
    25. rng.Dispose();
    26. }
    27. byte[] inputData = Encoding.UTF8.GetBytes(inputText);
    28. byte[] inputDataAndSalt = new byte[inputText.Length + SaltBytes.Length];
    29. byte[] inputDataAndSaltAndPepper = new byte[inputText.Length + SaltBytes.Length + PepperBytes.Length];
    30. //preset
    31. for (int i = 0; i < inputData.Length; i++)
    32. inputDataAndSaltAndPepper[i] = inputData[i];
    33. for (int i = 0; i < SaltBytes.Length; i++)
    34. inputDataAndSalt[inputData.Length + i] = SaltBytes[i];
    35. for (int i = 0; i < PepperBytes.Length; i++)
    36. inputDataAndSaltAndPepper[inputDataAndSalt.Length + i] = PepperBytes[i];
    37. byte[] hashValue = null;
    38. switch (hashType)
    39. {
    40. case Supported_Hashes.SHA256:
    41. SHA256Managed sha256 = new();
    42. hashValue = sha256.ComputeHash(inputDataAndSaltAndPepper);
    43. sha256.Dispose();
    44. break;
    45. case Supported_Hashes.SHA384:
    46. SHA384Managed sha384 = new();
    47. hashValue = sha384.ComputeHash(inputDataAndSaltAndPepper);
    48. sha384.Dispose();
    49. break;
    50. case Supported_Hashes.SHA512:
    51. SHA512Managed sha512 = new();
    52. hashValue = sha512.ComputeHash(inputDataAndSaltAndPepper);
    53. sha512.Dispose();
    54. break;
    55. }
    56. //TODO hash+salt but not the pepper !? Correct
    57. byte[] result = new byte[hashValue.Length + SaltBytes.Length];
    58. for (int i = 0; i < hashValue.Length; i++)
    59. result[i] = hashValue[i];
    60. for (int i = 0; i < SaltBytes.Length; i++)
    61. result[hashValue.Length + i] = SaltBytes[i];
    62. return Convert.ToBase64String(result);
    63. }


    Check Hash:

    C#-Quellcode

    1. public bool ConfirmHash(string inputText, string confirmHashValue, Supported_Hashes hashType, string pepper)
    2. {
    3. byte[] confirmHashBytes = Convert.FromBase64String(confirmHashValue);
    4. int hashSize = 0;
    5. switch (hashType)
    6. {
    7. case Supported_Hashes.SHA256:
    8. hashSize = 32;
    9. break;
    10. case Supported_Hashes.SHA384:
    11. hashSize = 48;
    12. break;
    13. case Supported_Hashes.SHA512:
    14. hashSize = 64;
    15. break;
    16. }
    17. byte[] saltBytes = new byte[confirmHashBytes.Length - hashSize];
    18. for (int i = 0; i < saltBytes.Length; i++)
    19. saltBytes[i] = confirmHashBytes[hashSize + i];
    20. string controlRehashValue = CreateHash(inputText, hashType, saltBytes, pepper);
    21. return confirmHashValue == controlRehashValue;
    22. }




    LG nogood
    Bilder
    • vbsha.jpg

      47,4 kB, 557×332, 86 mal angesehen
    codewars.com Rank: 4 kyu
    @nogood Habe ich das so richtig implementiert:

    C#-Quellcode

    1. private void button1_Click(object sender, EventArgs e)
    2. {
    3. string pepper = "pepper";
    4. string xx = _Hashing.CreateHash(this.textBox1.Text, Supported_Hashes.SHA512, null, pepper);
    5. listBox1.Items.Add(xx);
    6. bool result = _Hashing.ConfirmHash(this.textBox1.Text, xx, Supported_Hashes.SHA512, pepper);
    7. listBox2.Items.Add(result);
    8. }
    In ListBox2 steht stets true, was ja auch sein soll.
    Was Du vereinfachen kannst:
    die Salt-Bytes können stets null sein, Du kannst stets mit 16 Salt-Bytes arbeiten, Du kannst stets mit SHA512 arbeiten.
    Nun wird der ganze Code deutlich schlanker und damit verständlicher, der Sicherheit schadet das nichts.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    von pepper habich im gegensatz zu salt noch nie gehört.

    sieht mir zu kompliziert zu benutzen aus.
    Als User will ich ein Passwort verhasht haben, also CreateHash soll nur password entgegennehmen, meinetwegen auch hashType, aber das ist eiglich auch schon überkandidelt. Und soll hashBytes returnen.
    Das Salt wird intern generiert und den hashBytes vorangestellt oder angehängt, oder sonstwie reproduzierbar eingearbeitet.

    ConfirmHash soll nur ein TestPW entgegennehmen und die hashBytes.
    Bei dir nimmt es hashValues entgegen statt Bytes - naja, wenn das besser sein soll...

    wie gesagt: pepper - ???
    Und hashType überkandidelt - warum willst du in deiner Klau-Datenbank unbedingt verschiedenartig verhashte Passwörter pflegen?
    Und muss sich dann der User bei dir als Credentials immer Benutzername, Passwort, hashType, pepper merken? und womöglich das Salt auch?

    Dassis doch unnötig verschwurbelt - da soller doch einfach ein längeres Passwort nehmen, dann isses genauso geheim.

    Ich hab iwo hier inne Tipps Tutorial dazu gemacht, da habich Wert drauf gelegt, die Benutzung möglcihst einfach zu halten - bei aller inneren Komplexität.



    Eine weitere Anmerkung: Deine Klasse Hashing merkt sich keinerlei Status. Also wäre besser, wenn eine Statische Klasse wäre.
    Was soll der Unfug, sich son Ding immer neu erstellen zu müssen um es zu benutzen, wenn man eine statische Klasse direkt benutzen könnte - ohne zusätzliche Erstellerei.
    @RodFromGermany @ErfinderDesRades Die Gui-Maske ist nur für den Test des Codes gedacht.

    Im echten Anwendungsfalls würde der User, so wie bekannt, Username und Password eintippten müssen nix sonst (nicht SHA-Typ wählen, nicht Salz oder irgendwas anderes). Die Methoden wollte ich später in meine eigene MySecurity.lib packen und auch in anderen Apps verwenden.


    Ablauf soll so sein:
    -Im Code/App/.exe ist das Pepper unter config oder was auch immer als statischer Wert gespeichert.
    -User erstellt seinen Account -- tippt zum ersten Mal sein Namen und sein PWD ein (CreateHash).
    -das PWD wird gehashed wie folgt: (PWD + Salt + Pepper) daraus wird der Hash erzeugt und an dieses Ergebnis wird dann wieder das Salt hinten drangehängt
    -dieser 'Hash-inkl.Salt' wird in der Datenbank gespeichert
    -beim späteren einloggen (nicht beim Account erstellen) wird dann die zweite Funktion aufgerufen (ConfirmHash)
    -User tippt login/Username und PWD ein mit dem UserNamen wird der Hash aus der DB geladen und das PWD wird erneut gehashed mit dem selben Salt und dem selben Pepper
    -wenn das neu gehashte 'Wort' gleich dem gespeicherten aus der DB ist -> ist der User erfolgreich eingeloggt.

    @ErfinderDesRades Das mit dem Pepper ist so wie ich es gelesen habe eben nur noch eine kleine Schicht extra Sicherheit. Das Pepper ist nur nochmal hinten drangehangen.
    codewars.com Rank: 4 kyu
    IMO kann pepper weg. Du hängst ja schon ein salt an, warum dann noch ein pepper? Und wenn pepper unbedingt drangehängt werden muss, warum nicht auch noch essig? und glutamat?
    Also das bringt keine zusätzliche Sicherheit, die man nicht ebenso oder besser erreichen würde durch ein längeres salt.

    Weiters hab ich jetzt gesehen, dass der parameter salt auch null sein darf - dann wird es generiert.
    Imo soll ein salt immer generiert werden, also der parameter salt kann auch weg, und salt werde immer generiert.

    ErfinderDesRades schrieb:

    IMO kann pepper weg. Du hängst ja schon ein salt an, warum dann noch ein pepper? Und wenn pepper unbedingt drangehängt werden muss, warum nicht auch noch essig? und glutamat?
    Also das bringt keine zusätzliche Sicherheit, die man nicht ebenso oder besser erreichen würde durch ein längeres salt


    Viel zu lernen du noch hast junger Padawan.
    Der Pepper bringt sehr wohl mehr Sicherheit.
    Wird, natürlich, wie der Salt neu generiert, aber separat von Salt und Hash abgespeichert. Nicht auf dem selben DB Server wie die eigentliche User-Table.
    Die deutsche Sprache ist Freeware, du kannst sie benutzen, ohne dafür zu bezahlen. Sie ist aber nicht Open Source, also darfst du sie nicht verändern, wie es dir gerade passt.
    @ErfinderDesRades
    ...Weiters hab ich jetzt gesehen, dass der parameter salt auch null sein darf - dann wird es generiert. Imo soll ein salt immer generiert...


    Das ist hier so gewollt. Das Salt wird nur dann generiert, wenn ein 'neuer Hash' entsteht (die CreateHashMethode also mit Salt=null aufgerufen wird).

    Der andere Fall ist der, dass der gespeicherte Hash (= UserPasswordHash aus der DB) abgeglichen werden soll.
    Dann trennt die ConfirmHash() Methode den HashString in Hash und Salt auf und ruft die CreateHash() Methode mit den gekannten/gespeicherten SaltBytes auf (inkl. UserEingabePWDString und Pepper das eben nicht in der DB steht).

    Es wird so zu sagen ein ReHash mit den gleichen Zutaten bemacht und wenn der Ergebisstring gleich dem HashString in der DB ist ... hat der User das richtige PWD eingetippt.
    --------------------------------
    Also:
    1. PWD als Klar Text -> muss man wohl nichts zu sagen warum das schlecht ist

    2. PWD als Hash ohne Salt -> Problem wenn der Angreifer mit geleakten Password-Hash Tabellen arbeitet und der User ein dummes PWD wählt
    (Passwort123=>(hash)sadfsvSDAVASdvuusdvhu==, 123456=(hash)wwwfgwWEFfWEfwef, superuser=(hash)dsafwefwefWEWef==, ...)

    3.PWD+Salt macht Angriff 2. schwerer. Wenn ich Salt benutze muss der Angreifer mit Passworthash-Table für jedes Salt eine komplette Tabelle mit allen Hashes pro Passwort durchlaufen lassen. Es wäre aber möglich.

    4.Pwd+Salt+Pepper verhindert, dass der Angreifer der ja genau wie ich, das Salt vom Hash trennen kann danach wieder 2. machen kann.
    Mit Pepper wird dieser Angriff dann praktisch sinnlos (da der Angreifer das Pepper 'nicht aus der DB kenn kann').

    Zu den RainbowTabellen ein Link: Absolut unterhaltsam und spannend ... Password Cracking - Computerphile
    ---------------

    I
    ch möchte nochmal darauf hinweisen, dass ich heute das erste Mal irgendwas mit Hash-Funktionen gemacht habe. Vielleicht bin ich also der Falsche auf den man hören sollte :)
    codewars.com Rank: 4 kyu
    Ich hab hier eine Grafik gefunden, bei der ich doch ins Grübeln gekommen bin.


    Die Übersicht zeigt wie viele Hashes ein PC pro Sekunde erzeugen kann (genaue Hardware war nicht beschrieben; hörte sich nach 'normalen' Deskptop-PC an). Das wäre bei SHA512 immerhin noch 1 Mio. pro Sekunde.
    Erschreckende Vorstellung...
    Bilder
    • hashpersecondsOnDesktop.png

      4,53 kB, 604×365, 64 mal angesehen
    codewars.com Rank: 4 kyu