aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Model/Cryptography/PasswordHash.cs
diff options
context:
space:
mode:
authorcvium <clausvium@gmail.com>2022-01-07 10:23:22 +0100
committercvium <clausvium@gmail.com>2022-01-07 10:23:22 +0100
commitc658a883a2bc84b46ed73d209d2983e8a324cdce (patch)
treedabdbb5ac224e202d5433e7062e0c1b6872d1af7 /MediaBrowser.Model/Cryptography/PasswordHash.cs
parent2899b77cd58456470b8dd4d01d3a8c525a9b5911 (diff)
parent6b4f5a86631e5bde93dae88553380c7ffd99b8e4 (diff)
Merge branch 'master' into keyframe_extraction_v1
# Conflicts: # Jellyfin.Api/Controllers/DynamicHlsController.cs # MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs # MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
Diffstat (limited to 'MediaBrowser.Model/Cryptography/PasswordHash.cs')
-rw-r--r--MediaBrowser.Model/Cryptography/PasswordHash.cs219
1 files changed, 219 insertions, 0 deletions
diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs
new file mode 100644
index 000000000..eec541041
--- /dev/null
+++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs
@@ -0,0 +1,219 @@
+#pragma warning disable CS1591
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MediaBrowser.Model.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;
+ private readonly byte[] _salt;
+ private readonly byte[] _hash;
+
+ 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)
+ {
+ if (id == null)
+ {
+ throw new ArgumentNullException(nameof(id));
+ }
+
+ if (id.Length == 0)
+ {
+ throw new ArgumentException("String can't be empty", nameof(id));
+ }
+
+ 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>
+ 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 ReadOnlySpan<byte> Salt => _salt;
+
+ /// <summary>
+ /// Gets the hashed password.
+ /// </summary>
+ /// <value>Return the hashed password.</value>
+ public ReadOnlySpan<byte> Hash => _hash;
+
+ public static PasswordHash Parse(ReadOnlySpan<char> hashString)
+ {
+ if (hashString.IsEmpty)
+ {
+ throw new ArgumentException("String can't be empty", nameof(hashString));
+ }
+
+ if (hashString[0] != '$')
+ {
+ throw new FormatException("Hash string must start with a $");
+ }
+
+ // Ignore first $
+ hashString = hashString[1..];
+
+ int nextSegment = hashString.IndexOf('$');
+ if (hashString.IsEmpty || nextSegment == 0)
+ {
+ throw new FormatException("Hash string must contain a valid id");
+ }
+ else if (nextSegment == -1)
+ {
+ return new PasswordHash(hashString.ToString(), Array.Empty<byte>());
+ }
+
+ ReadOnlySpan<char> id = hashString[..nextSegment];
+ hashString = hashString[(nextSegment + 1)..];
+ Dictionary<string, string>? parameters = null;
+
+ nextSegment = hashString.IndexOf('$');
+
+ // Optional parameters
+ ReadOnlySpan<char> parametersSpan = nextSegment == -1 ? hashString : hashString[..nextSegment];
+ if (parametersSpan.Contains('='))
+ {
+ while (!parametersSpan.IsEmpty)
+ {
+ ReadOnlySpan<char> parameter;
+ int index = parametersSpan.IndexOf(',');
+ if (index == -1)
+ {
+ parameter = parametersSpan;
+ parametersSpan = ReadOnlySpan<char>.Empty;
+ }
+ else
+ {
+ parameter = parametersSpan[..index];
+ parametersSpan = parametersSpan[(index + 1)..];
+ }
+
+ int splitIndex = parameter.IndexOf('=');
+ if (splitIndex == -1 || splitIndex == 0 || splitIndex == parameter.Length - 1)
+ {
+ throw new FormatException("Malformed parameter in password hash string");
+ }
+
+ (parameters ??= new Dictionary<string, string>()).Add(
+ parameter[..splitIndex].ToString(),
+ parameter[(splitIndex + 1)..].ToString());
+ }
+
+ if (nextSegment == -1)
+ {
+ // parameters can't be null here
+ return new PasswordHash(id.ToString(), Array.Empty<byte>(), Array.Empty<byte>(), parameters!);
+ }
+
+ hashString = hashString[(nextSegment + 1)..];
+ nextSegment = hashString.IndexOf('$');
+ }
+
+ if (nextSegment == 0)
+ {
+ throw new FormatException("Hash string contains an empty segment");
+ }
+
+ byte[] hash;
+ byte[] salt;
+
+ if (nextSegment == -1)
+ {
+ salt = Array.Empty<byte>();
+ hash = Convert.FromHexString(hashString);
+ }
+ else
+ {
+ salt = Convert.FromHexString(hashString[..nextSegment]);
+ hashString = hashString[(nextSegment + 1)..];
+ nextSegment = hashString.IndexOf('$');
+ if (nextSegment != -1)
+ {
+ throw new FormatException("Hash string contains too many segments");
+ }
+
+ if (hashString.IsEmpty)
+ {
+ throw new FormatException("Hash segment is empty");
+ }
+
+ hash = Convert.FromHexString(hashString);
+ }
+
+ return new PasswordHash(id.ToString(), hash, salt, parameters ?? new Dictionary<string, string>());
+ }
+
+ private void SerializeParameters(StringBuilder stringBuilder)
+ {
+ if (_parameters.Count == 0)
+ {
+ return;
+ }
+
+ stringBuilder.Append('$');
+ foreach (var pair in _parameters)
+ {
+ stringBuilder.Append(pair.Key)
+ .Append('=')
+ .Append(pair.Value)
+ .Append(',');
+ }
+
+ // Remove last ','
+ stringBuilder.Length -= 1;
+ }
+
+ /// <inheritdoc />
+ public override string ToString()
+ {
+ var str = new StringBuilder()
+ .Append('$')
+ .Append(Id);
+ SerializeParameters(str);
+
+ if (_salt.Length != 0)
+ {
+ str.Append('$')
+ .Append(Convert.ToHexString(_salt));
+ }
+
+ if (_hash.Length != 0)
+ {
+ str.Append('$')
+ .Append(Convert.ToHexString(_hash));
+ }
+
+ return str.ToString();
+ }
+ }
+}