aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
blob: 23b77e268785e1074dd8349343cf53b2b139bea9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using MediaBrowser.Model.Cryptography;
using static MediaBrowser.Common.Cryptography.Constants;

namespace Emby.Server.Implementations.Cryptography
{
    public class CryptographyProvider : ICryptoProvider, IDisposable
    {
        private static readonly HashSet<string> _supportedHashMethods = new HashSet<string>()
            {
                "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"
            };

        private RandomNumberGenerator _randomNumberGenerator;

        private bool _disposed = false;

        public CryptographyProvider()
        {
            // FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto
            // Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1
            // there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one
            // Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1
            _randomNumberGenerator = RandomNumberGenerator.Create();
        }

        public string DefaultHashMethod => "PBKDF2";

        public IEnumerable<string> GetSupportedHashMethods()
            => _supportedHashMethods;

        private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations)
        {
            // downgrading for now as we need this library to be dotnetstandard compliant
            // with this downgrade we'll add a check to make sure we're on the downgrade method at the moment
            if (method == DefaultHashMethod)
            {
                using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations))
                {
                    return r.GetBytes(32);
                }
            }

            throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}");
        }

        public byte[] ComputeHash(string hashMethod, byte[] bytes)
            => ComputeHash(hashMethod, bytes, Array.Empty<byte>());

        public byte[] ComputeHashWithDefaultMethod(byte[] bytes)
            => ComputeHash(DefaultHashMethod, bytes);

        public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt)
        {
            if (hashMethod == DefaultHashMethod)
            {
                return PBKDF2(hashMethod, bytes, salt, DefaultIterations);
            }
            else if (_supportedHashMethods.Contains(hashMethod))
            {
                using (var h = HashAlgorithm.Create(hashMethod))
                {
                    if (salt.Length == 0)
                    {
                        return h.ComputeHash(bytes);
                    }
                    else
                    {
                        byte[] salted = new byte[bytes.Length + salt.Length];
                        Array.Copy(bytes, salted, bytes.Length);
                        Array.Copy(salt, 0, salted, bytes.Length, salt.Length);
                        return h.ComputeHash(salted);
                    }
                }
            }

            throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");

        }

        public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)
            => PBKDF2(DefaultHashMethod, bytes, salt, DefaultIterations);

        public byte[] GenerateSalt()
            => GenerateSalt(DefaultSaltLength);

        public byte[] GenerateSalt(int length)
        {
            byte[] salt = new byte[length];
            _randomNumberGenerator.GetBytes(salt);
            return salt;
        }

        /// <inheritdoc />
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
            {
                return;
            }

            if (disposing)
            {
                _randomNumberGenerator.Dispose();
            }

            _randomNumberGenerator = null;

            _disposed = true;
        }
    }
}