Created
August 23, 2020 16:28
-
-
Save LanceMcCarthy/cba0ca943d1bd45a42a0e681982f7a37 to your computer and use it in GitHub Desktop.
Modern Password Salt and Ecryptor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Linq; | |
using System.Security.Cryptography; | |
using Microsoft.AspNetCore.Cryptography.KeyDerivation; | |
namespace PwdTools | |
{ | |
/* ======================= | |
* HASHED PASSWORD FORMATS | |
* ======================= | |
* | |
* Version 3: | |
* PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations. | |
* Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey } | |
* (All UInt32s are stored big-endian.) | |
*/ | |
public class CredentialsHelper | |
{ | |
public string HashPassword(string password) | |
{ | |
var prf = KeyDerivationPrf.HMACSHA256; | |
var rng = RandomNumberGenerator.Create(); | |
const int iterCount = 10000; | |
const int saltSize = 128 / 8; | |
const int numBytesRequested = 256 / 8; | |
// Produce a version 3 (see comment above) text hash. | |
var salt = new byte[saltSize]; | |
rng.GetBytes(salt); | |
var subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested); | |
var outputBytes = new byte[13 + salt.Length + subkey.Length]; | |
outputBytes[0] = 0x01; // format marker | |
WriteNetworkByteOrder(outputBytes, 1, (uint)prf); | |
WriteNetworkByteOrder(outputBytes, 5, iterCount); | |
WriteNetworkByteOrder(outputBytes, 9, saltSize); | |
Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length); | |
Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length); | |
return Convert.ToBase64String(outputBytes); | |
} | |
public bool VerifyHashedPassword(string hashedPassword, string providedPassword) | |
{ | |
var decodedHashedPassword = Convert.FromBase64String(hashedPassword); | |
// Wrong version | |
if (decodedHashedPassword[0] != 0x01) | |
return false; | |
// Read header information | |
var prf = (KeyDerivationPrf)ReadNetworkByteOrder(decodedHashedPassword, 1); | |
var iterCount = (int)ReadNetworkByteOrder(decodedHashedPassword, 5); | |
var saltLength = (int)ReadNetworkByteOrder(decodedHashedPassword, 9); | |
// Read the salt: must be >= 128 bits | |
if (saltLength < 128 / 8) | |
{ | |
return false; | |
} | |
var salt = new byte[saltLength]; | |
Buffer.BlockCopy(decodedHashedPassword, 13, salt, 0, salt.Length); | |
// Read the subkey (the rest of the payload): must be >= 128 bits | |
var subkeyLength = decodedHashedPassword.Length - 13 - salt.Length; | |
if (subkeyLength < 128 / 8) | |
{ | |
return false; | |
} | |
var expectedSubkey = new byte[subkeyLength]; | |
Buffer.BlockCopy(decodedHashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length); | |
// Hash the incoming password and verify it | |
var actualSubkey = KeyDerivation.Pbkdf2(providedPassword, salt, prf, iterCount, subkeyLength); | |
return actualSubkey.SequenceEqual(expectedSubkey); | |
} | |
private static void WriteNetworkByteOrder(byte[] buffer, int offset, uint value) | |
{ | |
buffer[offset + 0] = (byte)(value >> 24); | |
buffer[offset + 1] = (byte)(value >> 16); | |
buffer[offset + 2] = (byte)(value >> 8); | |
buffer[offset + 3] = (byte)(value >> 0); | |
} | |
private static uint ReadNetworkByteOrder(byte[] buffer, int offset) | |
{ | |
return ((uint)(buffer[offset + 0]) << 24) | |
| ((uint)(buffer[offset + 1]) << 16) | |
| ((uint)(buffer[offset + 2]) << 8) | |
| ((uint)(buffer[offset + 3])); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Window x:Class="PwdTools.MainWindow" | |
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | |
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | |
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | |
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | |
xmlns:local="clr-namespace:PwdTools" | |
mc:Ignorable="d" | |
Title="MainWindow" | |
Height="450" | |
Width="800"> | |
<Grid> | |
<Grid.ColumnDefinitions> | |
<ColumnDefinition Width="2*"/> | |
<ColumnDefinition Width="*"/> | |
</Grid.ColumnDefinitions> | |
<Grid.RowDefinitions> | |
<RowDefinition Height="Auto"/> | |
<RowDefinition/> | |
</Grid.RowDefinitions> | |
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> | |
<StackPanel Orientation="Horizontal" Margin="0,10,0,0"> | |
<TextBox x:Name="PassBox" Width="200"/> | |
<Button Content="Save password" Click="SaveBtnClick" Margin="10,0,0,0"/> | |
</StackPanel> | |
<StackPanel Orientation="Horizontal" Margin="0,10,0,0"> | |
<TextBox x:Name="HashBox" Width="200"/> | |
<Button Content="Check password" Click="CheckBtnClick" Margin="10,0,0,0"/> | |
</StackPanel> | |
</StackPanel> | |
<ListBox x:Name="HashesList" SelectionChanged="HashesList_SelectionChanged" Margin="10" Grid.Row="1"/> | |
<ListBox x:Name="OutputList" Margin="10" Grid.Column="1" Grid.RowSpan="2" /> | |
</Grid> | |
</Window> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.ObjectModel; | |
using System.Windows; | |
using System.Windows.Controls; | |
namespace PwdTools | |
{ | |
public partial class MainWindow : Window | |
{ | |
private readonly ObservableCollection<string> output = new ObservableCollection<string>(); | |
private readonly ObservableCollection<string> hashes = new ObservableCollection<string>(); | |
private CredentialsHelper p2 = new CredentialsHelper(); | |
public MainWindow() | |
{ | |
InitializeComponent(); | |
OutputList.ItemsSource = output; | |
HashesList.ItemsSource = hashes; | |
} | |
public void SaveBtnClick(object sender, EventArgs eventArgs) | |
{ | |
output.Add($"Saving {PassBox.Text}"); | |
var hashedPwd = p2.HashPassword(PassBox.Text); | |
hashes.Add(hashedPwd); | |
} | |
public void CheckBtnClick(object sender, EventArgs eventArgs) | |
{ | |
output.Add($"Checking..."); | |
var result = p2.VerifyHashedPassword(HashBox.Text, PassBox.Text); | |
output.Add($"Password match? {result}"); | |
} | |
private void HashesList_SelectionChanged(object sender, SelectionChangedEventArgs e) | |
{ | |
HashBox.Text = e.AddedItems[0] as string; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Original source - https://github.com/aspnet/Identity/blob/c7276ce2f76312ddd7fccad6e399da96b9f6fae1/src/Core/PasswordHasher.cs