aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
blob: f726dae2ee4fe1f63ee039474e6888d94d68aec3 (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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using MediaBrowser.Model.Cryptography;

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 const int _defaultIterations = 1000;

        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";

        [Obsolete("Use System.Security.Cryptography.MD5 directly")]
        public Guid GetMD5(string str)
            => new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str)));

        [Obsolete("Use System.Security.Cryptography.SHA1 directly")]
        public byte[] ComputeSHA1(byte[] bytes)
        {
            using (var provider = SHA1.Create())
            {
                return provider.ComputeHash(bytes);
            }
        }

        [Obsolete("Use System.Security.Cryptography.MD5 directly")]
        public byte[] ComputeMD5(Stream str)
        {
            using (var provider = MD5.Create())
            {
                return provider.ComputeHash(str);
            }
        }

        [Obsolete("Use System.Security.Cryptography.MD5 directly")]
        public byte[] ComputeMD5(byte[] bytes)
        {
            using (var provider = MD5.Create())
            {
                return provider.ComputeHash(bytes);
            }
        }

        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[] ComputeHash(PasswordHash hash)
        {
            int iterations = _defaultIterations;
            if (!hash.Parameters.ContainsKey("iterations"))
            {
                hash.Parameters.Add("iterations", iterations.ToString(CultureInfo.InvariantCulture));
            }
            else if (!int.TryParse(hash.Parameters["iterations"], out iterations))
            {
                throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}");
            }

            return PBKDF2(hash.Id, hash.Hash, hash.Salt, iterations);
        }

        public byte[] GenerateSalt()
        {
            byte[] salt = new byte[64];
            _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;
        }
    }
}