PasswordHash.cs 4.8 KB

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