1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using static MediaBrowser.Common.HexHelper;
namespace MediaBrowser.Common.Cryptography
{
// Defined from this hash storage spec
// https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
// $<id>[$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
// with one slight amendment to ease the transition, we're writing out the bytes in hex
// rather than making them a BASE64 string with stripped padding
public class PasswordHash
{
private readonly Dictionary<string, string> _parameters;
public PasswordHash(string id, byte[] hash)
: this(id, hash, Array.Empty<byte>())
{
}
public PasswordHash(string id, byte[] hash, byte[] salt)
: this(id, hash, salt, new Dictionary<string, string>())
{
}
public PasswordHash(string id, byte[] hash, byte[] salt, Dictionary<string, string> parameters)
{
Id = id;
Hash = hash;
Salt = salt;
_parameters = parameters;
}
/// <summary>
/// Gets the symbolic name for the function used.
/// </summary>
/// <value>Returns the symbolic name for the function used.</value>
public string Id { get; }
/// <summary>
/// Gets the additional parameters used by the hash function.
/// </summary>
/// <value></value>
public IReadOnlyDictionary<string, string> Parameters => _parameters;
/// <summary>
/// Gets the salt used for hashing the password.
/// </summary>
/// <value>Returns the salt used for hashing the password.</value>
public byte[] Salt { get; }
/// <summary>
/// Gets the hashed password.
/// </summary>
/// <value>Return the hashed password.</value>
public byte[] Hash { get; }
public static PasswordHash Parse(string storageString)
{
string[] splitted = storageString.Split('$');
// The string should at least contain the hash function and the hash itself
if (splitted.Length < 3)
{
throw new ArgumentException("String doesn't contain enough segments", nameof(storageString));
}
// Start at 1, the first index shouldn't contain any data
int index = 1;
// Name of the hash function
string id = splitted[index++];
// Optional parameters
Dictionary<string, string> parameters = new Dictionary<string, string>();
if (splitted[index].IndexOf('=') != -1)
{
foreach (string paramset in splitted[index++].Split(','))
{
if (string.IsNullOrEmpty(paramset))
{
continue;
}
string[] fields = paramset.Split('=');
if (fields.Length != 2)
{
throw new InvalidDataException($"Malformed parameter in password hash string {paramset}");
}
parameters.Add(fields[0], fields[1]);
}
}
byte[] hash;
byte[] salt;
// Check if the string also contains a salt
if (splitted.Length - index == 2)
{
salt = FromHexString(splitted[index++]);
hash = FromHexString(splitted[index++]);
}
else
{
salt = Array.Empty<byte>();
hash = FromHexString(splitted[index++]);
}
return new PasswordHash(id, hash, salt, parameters);
}
private void SerializeParameters(StringBuilder stringBuilder)
{
if (_parameters.Count == 0)
{
return;
}
stringBuilder.Append('$');
foreach (var pair in _parameters)
{
stringBuilder.Append(pair.Key);
stringBuilder.Append('=');
stringBuilder.Append(pair.Value);
stringBuilder.Append(',');
}
// Remove last ','
stringBuilder.Length -= 1;
}
/// <inheritdoc />
public override string ToString()
{
var str = new StringBuilder();
str.Append('$');
str.Append(Id);
SerializeParameters(str);
if (Salt.Length != 0)
{
str.Append('$');
str.Append(ToHexString(Salt));
}
str.Append('$');
str.Append(ToHexString(Hash));
return str.ToString();
}
}
}
|