PasswordHash.cs 4.8 KB

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