using System; using System.Collections.Generic; using System.Globalization; using System.Security.Cryptography; using System.Text; using MediaBrowser.Common.Extensions; using MediaBrowser.Model.Cryptography; using static MediaBrowser.Model.Cryptography.Constants; namespace Emby.Server.Implementations.Cryptography { /// /// Class providing abstractions over cryptographic functions. /// public class CryptographyProvider : ICryptoProvider { // TODO: remove when not needed for backwards compat private static readonly HashSet _supportedHashMethods = new HashSet() { "MD5", "System.Security.Cryptography.MD5", "SHA", "SHA1", "System.Security.Cryptography.SHA1", "SHA256", "SHA-256", "System.Security.Cryptography.SHA256", "SHA384", "SHA-384", "System.Security.Cryptography.SHA384", "SHA512", "SHA-512", "System.Security.Cryptography.SHA512" }; /// public string DefaultHashMethod => "PBKDF2-SHA512"; /// public PasswordHash CreatePasswordHash(ReadOnlySpan password) { byte[] salt = GenerateSalt(); return new PasswordHash( DefaultHashMethod, Rfc2898DeriveBytes.Pbkdf2( password, salt, DefaultIterations, HashAlgorithmName.SHA512, DefaultOutputLength), salt, new Dictionary { { "iterations", DefaultIterations.ToString(CultureInfo.InvariantCulture) } }); } /// public bool Verify(PasswordHash hash, ReadOnlySpan password) { if (string.Equals(hash.Id, "PBKDF2", StringComparison.Ordinal)) { return hash.Hash.SequenceEqual( Rfc2898DeriveBytes.Pbkdf2( password, hash.Salt, int.Parse(hash.Parameters["iterations"], CultureInfo.InvariantCulture), HashAlgorithmName.SHA1, 32)); } if (string.Equals(hash.Id, "PBKDF2-SHA512", StringComparison.Ordinal)) { return hash.Hash.SequenceEqual( Rfc2898DeriveBytes.Pbkdf2( password, hash.Salt, int.Parse(hash.Parameters["iterations"], CultureInfo.InvariantCulture), HashAlgorithmName.SHA512, DefaultOutputLength)); } if (!_supportedHashMethods.Contains(hash.Id)) { throw new CryptographicException($"Requested hash method is not supported: {hash.Id}"); } using var h = HashAlgorithm.Create(hash.Id) ?? throw new ResourceNotFoundException($"Unknown hash method: {hash.Id}."); var bytes = Encoding.UTF8.GetBytes(password.ToArray()); if (hash.Salt.Length == 0) { return hash.Hash.SequenceEqual(h.ComputeHash(bytes)); } byte[] salted = new byte[bytes.Length + hash.Salt.Length]; Array.Copy(bytes, salted, bytes.Length); hash.Salt.CopyTo(salted.AsSpan(bytes.Length)); return hash.Hash.SequenceEqual(h.ComputeHash(salted)); } /// public byte[] GenerateSalt() => GenerateSalt(DefaultSaltLength); /// public byte[] GenerateSalt(int length) { var salt = new byte[length]; using var rng = RandomNumberGenerator.Create(); rng.GetNonZeroBytes(salt); return salt; } } }