PasswordHashTests.cs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. using System;
  2. using System.Collections.Generic;
  3. using MediaBrowser.Common.Cryptography;
  4. using Xunit;
  5. namespace Jellyfin.Common.Tests.Cryptography
  6. {
  7. public static class PasswordHashTests
  8. {
  9. [Fact]
  10. public static void Ctor_Null_ThrowsArgumentNullException()
  11. {
  12. Assert.Throws<ArgumentNullException>(() => new PasswordHash(null!, Array.Empty<byte>()));
  13. }
  14. [Fact]
  15. public static void Ctor_Empty_ThrowsArgumentException()
  16. {
  17. Assert.Throws<ArgumentException>(() => new PasswordHash(string.Empty, Array.Empty<byte>()));
  18. }
  19. public static IEnumerable<object[]> Parse_Valid_TestData()
  20. {
  21. // Id
  22. yield return new object[]
  23. {
  24. "$PBKDF2",
  25. new PasswordHash("PBKDF2", Array.Empty<byte>())
  26. };
  27. // Id + parameter
  28. yield return new object[]
  29. {
  30. "$PBKDF2$iterations=1000",
  31. new PasswordHash(
  32. "PBKDF2",
  33. Array.Empty<byte>(),
  34. Array.Empty<byte>(),
  35. new Dictionary<string, string>()
  36. {
  37. { "iterations", "1000" },
  38. })
  39. };
  40. // Id + parameters
  41. yield return new object[]
  42. {
  43. "$PBKDF2$iterations=1000,m=120",
  44. new PasswordHash(
  45. "PBKDF2",
  46. Array.Empty<byte>(),
  47. Array.Empty<byte>(),
  48. new Dictionary<string, string>()
  49. {
  50. { "iterations", "1000" },
  51. { "m", "120" }
  52. })
  53. };
  54. // Id + hash
  55. yield return new object[]
  56. {
  57. "$PBKDF2$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
  58. new PasswordHash(
  59. "PBKDF2",
  60. Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
  61. Array.Empty<byte>(),
  62. new Dictionary<string, string>())
  63. };
  64. // Id + salt + hash
  65. yield return new object[]
  66. {
  67. "$PBKDF2$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
  68. new PasswordHash(
  69. "PBKDF2",
  70. Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
  71. Convert.FromHexString("69F420"),
  72. new Dictionary<string, string>())
  73. };
  74. // Id + parameter + hash
  75. yield return new object[]
  76. {
  77. "$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
  78. new PasswordHash(
  79. "PBKDF2",
  80. Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
  81. Array.Empty<byte>(),
  82. new Dictionary<string, string>()
  83. {
  84. { "iterations", "1000" }
  85. })
  86. };
  87. // Id + parameters + hash
  88. yield return new object[]
  89. {
  90. "$PBKDF2$iterations=1000,m=120$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
  91. new PasswordHash(
  92. "PBKDF2",
  93. Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
  94. Array.Empty<byte>(),
  95. new Dictionary<string, string>()
  96. {
  97. { "iterations", "1000" },
  98. { "m", "120" }
  99. })
  100. };
  101. // Id + parameters + salt + hash
  102. yield return new object[]
  103. {
  104. "$PBKDF2$iterations=1000,m=120$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
  105. new PasswordHash(
  106. "PBKDF2",
  107. Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
  108. Convert.FromHexString("69F420"),
  109. new Dictionary<string, string>()
  110. {
  111. { "iterations", "1000" },
  112. { "m", "120" }
  113. })
  114. };
  115. }
  116. [Theory]
  117. [MemberData(nameof(Parse_Valid_TestData))]
  118. public static void Parse_Valid_Success(string passwordHashString, PasswordHash expected)
  119. {
  120. var passwordHash = PasswordHash.Parse(passwordHashString);
  121. Assert.Equal(expected.Id, passwordHash.Id);
  122. Assert.Equal(expected.Parameters, passwordHash.Parameters);
  123. Assert.Equal(expected.Salt.ToArray(), passwordHash.Salt.ToArray());
  124. Assert.Equal(expected.Hash.ToArray(), passwordHash.Hash.ToArray());
  125. Assert.Equal(expected.ToString(), passwordHash.ToString());
  126. }
  127. [Theory]
  128. [InlineData("$PBKDF2")]
  129. [InlineData("$PBKDF2$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
  130. [InlineData("$PBKDF2$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
  131. [InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
  132. [InlineData("$PBKDF2$iterations=1000,m=120$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
  133. [InlineData("$PBKDF2$iterations=1000,m=120$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
  134. [InlineData("$PBKDF2$iterations=1000,m=120")]
  135. public static void ToString_Roundtrip_Success(string passwordHash)
  136. {
  137. Assert.Equal(passwordHash, PasswordHash.Parse(passwordHash).ToString());
  138. }
  139. [Fact]
  140. public static void Parse_Null_ThrowsArgumentException()
  141. {
  142. Assert.Throws<ArgumentException>(() => PasswordHash.Parse(null));
  143. }
  144. [Fact]
  145. public static void Parse_Empty_ThrowsArgumentException()
  146. {
  147. Assert.Throws<ArgumentException>(() => PasswordHash.Parse(string.Empty));
  148. }
  149. [Theory]
  150. [InlineData("$")] // No id
  151. [InlineData("$$")] // Empty segments
  152. [InlineData("PBKDF2$")] // Doesn't start with $
  153. [InlineData("$PBKDF2$$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Empty segment
  154. [InlineData("$PBKDF2$iterations=1000$$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Empty salt segment
  155. [InlineData("$PBKDF2$iterations=1000$69F420$")] // Empty hash segment
  156. [InlineData("$PBKDF2$=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter
  157. [InlineData("$PBKDF2$=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter
  158. [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter
  159. [InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Ends on $
  160. [InlineData("$PBKDF2$iterations=1000$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Extra segment
  161. [InlineData("$PBKDF2$iterations=1000$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$anotherone")] // Extra segment
  162. [InlineData("$PBKDF2$iterations=1000$invalidstalt$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid salt
  163. [InlineData("$PBKDF2$iterations=1000$69F420$invalid hash")] // Invalid hash
  164. [InlineData("$PBKDF2$69F420$")] // Empty hash
  165. public static void Parse_InvalidFormat_ThrowsFormatException(string passwordHash)
  166. {
  167. Assert.Throws<FormatException>(() => PasswordHash.Parse(passwordHash));
  168. }
  169. }
  170. }