PasswordHash.cs 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. #pragma warning disable CS1591
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Text;
  6. using static MediaBrowser.Common.HexHelper;
  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. public PasswordHash(string id, byte[] hash)
  18. : this(id, hash, Array.Empty<byte>())
  19. {
  20. }
  21. public PasswordHash(string id, byte[] hash, byte[] salt)
  22. : this(id, hash, salt, new Dictionary<string, string>())
  23. {
  24. }
  25. public PasswordHash(string id, byte[] hash, byte[] salt, Dictionary<string, string> parameters)
  26. {
  27. Id = id;
  28. Hash = hash;
  29. Salt = salt;
  30. _parameters = parameters;
  31. }
  32. /// <summary>
  33. /// Gets the symbolic name for the function used.
  34. /// </summary>
  35. /// <value>Returns the symbolic name for the function used.</value>
  36. public string Id { get; }
  37. /// <summary>
  38. /// Gets the additional parameters used by the hash function.
  39. /// </summary>
  40. /// <value></value>
  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 byte[] Salt { get; }
  47. /// <summary>
  48. /// Gets the hashed password.
  49. /// </summary>
  50. /// <value>Return the hashed password.</value>
  51. public byte[] Hash { get; }
  52. public static PasswordHash Parse(string storageString)
  53. {
  54. string[] splitted = storageString.Split('$');
  55. // The string should at least contain the hash function and the hash itself
  56. if (splitted.Length < 3)
  57. {
  58. throw new ArgumentException("String doesn't contain enough segments", nameof(storageString));
  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('=') != -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 = FromHexString(splitted[index++]);
  88. hash = FromHexString(splitted[index++]);
  89. }
  90. else
  91. {
  92. salt = Array.Empty<byte>();
  93. hash = 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. stringBuilder.Append('=');
  108. stringBuilder.Append(pair.Value);
  109. stringBuilder.Append(',');
  110. }
  111. // Remove last ','
  112. stringBuilder.Length -= 1;
  113. }
  114. /// <inheritdoc />
  115. public override string ToString()
  116. {
  117. var str = new StringBuilder();
  118. str.Append('$');
  119. str.Append(Id);
  120. SerializeParameters(str);
  121. if (Salt.Length != 0)
  122. {
  123. str.Append('$');
  124. str.Append(ToHexString(Salt));
  125. }
  126. str.Append('$');
  127. str.Append(ToHexString(Hash));
  128. return str.ToString();
  129. }
  130. }
  131. }