CryptographyProvider.cs 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Security.Cryptography;
  5. using System.Text;
  6. using MediaBrowser.Common.Extensions;
  7. using MediaBrowser.Model.Cryptography;
  8. using static MediaBrowser.Model.Cryptography.Constants;
  9. namespace Emby.Server.Implementations.Cryptography
  10. {
  11. /// <summary>
  12. /// Class providing abstractions over cryptographic functions.
  13. /// </summary>
  14. public class CryptographyProvider : ICryptoProvider
  15. {
  16. // TODO: remove when not needed for backwards compat
  17. private static readonly HashSet<string> _supportedHashMethods = new HashSet<string>()
  18. {
  19. "MD5",
  20. "System.Security.Cryptography.MD5",
  21. "SHA",
  22. "SHA1",
  23. "System.Security.Cryptography.SHA1",
  24. "SHA256",
  25. "SHA-256",
  26. "System.Security.Cryptography.SHA256",
  27. "SHA384",
  28. "SHA-384",
  29. "System.Security.Cryptography.SHA384",
  30. "SHA512",
  31. "SHA-512",
  32. "System.Security.Cryptography.SHA512"
  33. };
  34. /// <inheritdoc />
  35. public string DefaultHashMethod => "PBKDF2-SHA512";
  36. /// <inheritdoc />
  37. public PasswordHash CreatePasswordHash(ReadOnlySpan<char> password)
  38. {
  39. byte[] salt = GenerateSalt();
  40. return new PasswordHash(
  41. DefaultHashMethod,
  42. Rfc2898DeriveBytes.Pbkdf2(
  43. password,
  44. salt,
  45. DefaultIterations,
  46. HashAlgorithmName.SHA512,
  47. DefaultOutputLength),
  48. salt,
  49. new Dictionary<string, string>
  50. {
  51. { "iterations", DefaultIterations.ToString(CultureInfo.InvariantCulture) }
  52. });
  53. }
  54. /// <inheritdoc />
  55. public bool Verify(PasswordHash hash, ReadOnlySpan<char> password)
  56. {
  57. if (string.Equals(hash.Id, "PBKDF2", StringComparison.Ordinal))
  58. {
  59. return hash.Hash.SequenceEqual(
  60. Rfc2898DeriveBytes.Pbkdf2(
  61. password,
  62. hash.Salt,
  63. int.Parse(hash.Parameters["iterations"], CultureInfo.InvariantCulture),
  64. HashAlgorithmName.SHA1,
  65. 32));
  66. }
  67. if (string.Equals(hash.Id, "PBKDF2-SHA512", StringComparison.Ordinal))
  68. {
  69. return hash.Hash.SequenceEqual(
  70. Rfc2898DeriveBytes.Pbkdf2(
  71. password,
  72. hash.Salt,
  73. int.Parse(hash.Parameters["iterations"], CultureInfo.InvariantCulture),
  74. HashAlgorithmName.SHA512,
  75. DefaultOutputLength));
  76. }
  77. if (!_supportedHashMethods.Contains(hash.Id))
  78. {
  79. throw new CryptographicException($"Requested hash method is not supported: {hash.Id}");
  80. }
  81. using var h = HashAlgorithm.Create(hash.Id) ?? throw new ResourceNotFoundException($"Unknown hash method: {hash.Id}.");
  82. var bytes = Encoding.UTF8.GetBytes(password.ToArray());
  83. if (hash.Salt.Length == 0)
  84. {
  85. return hash.Hash.SequenceEqual(h.ComputeHash(bytes));
  86. }
  87. byte[] salted = new byte[bytes.Length + hash.Salt.Length];
  88. Array.Copy(bytes, salted, bytes.Length);
  89. hash.Salt.CopyTo(salted.AsSpan(bytes.Length));
  90. return hash.Hash.SequenceEqual(h.ComputeHash(salted));
  91. }
  92. /// <inheritdoc />
  93. public byte[] GenerateSalt()
  94. => GenerateSalt(DefaultSaltLength);
  95. /// <inheritdoc />
  96. public byte[] GenerateSalt(int length)
  97. {
  98. var salt = new byte[length];
  99. using var rng = RandomNumberGenerator.Create();
  100. rng.GetNonZeroBytes(salt);
  101. return salt;
  102. }
  103. }
  104. }