aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
blob: 2f2fd95922e09111b81513f07090f34baa938835 (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
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
    {
        private HashSet<string> SupportedHashMethods;
        public string DefaultHashMethod => "SHA256";
        private RandomNumberGenerator rng;
        private int defaultiterations = 1000;
        public CryptographyProvider()
        {
            //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
            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"
            };
            rng = RandomNumberGenerator.Create();
        }

        public Guid GetMD5(string str)
        {
            return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str)));
        }

        public byte[] ComputeSHA1(byte[] bytes)
        {
            using (var provider = SHA1.Create())
            {
                return provider.ComputeHash(bytes);
            }
        }

        public byte[] ComputeMD5(Stream str)
        {
            using (var provider = MD5.Create())
            {
                return provider.ComputeHash(str);
            }
        }

        public byte[] ComputeMD5(byte[] bytes)
        {
            using (var provider = MD5.Create())
            {
                return provider.ComputeHash(bytes);
            }
        }

        public IEnumerable<string> GetSupportedHashMethods()
        {
            return SupportedHashMethods;
        }

        private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations)
        {
            //downgrading for now as we need this library to be dotnetstandard compliant
            using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations))
            {
                return r.GetBytes(32);
            }
        }

        public byte[] ComputeHash(string HashMethod, byte[] bytes)
        {
            return ComputeHash(HashMethod, bytes, new byte[0]);
        }

        public byte[] ComputeHashWithDefaultMethod(byte[] bytes)
        {
            return ComputeHash(DefaultHashMethod, bytes);
        }

        public byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt)
        {
            if (SupportedHashMethods.Contains(HashMethod))
            {
                if (salt.Length == 0)
                {
                    using (var h = HashAlgorithm.Create(HashMethod))
                    {
                        return h.ComputeHash(bytes);
                    }
                }
                else
                {
                    return PBKDF2(HashMethod, bytes, salt, defaultiterations);
                }
            }
            else
            {
                throw new CryptographicException($"Requested hash method is not supported: {HashMethod}");
            }
        }

        public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)
        {
            return PBKDF2(DefaultHashMethod, bytes, salt, defaultiterations);
        }
        
        public byte[] ComputeHash(PasswordHash hash)
        {
            int iterations = defaultiterations;
            if (!hash.Parameters.ContainsKey("iterations"))
            {
                hash.Parameters.Add("iterations", defaultiterations.ToString(CultureInfo.InvariantCulture));
            }
            else
            {
                try
                {
                    iterations = int.Parse(hash.Parameters["iterations"]);
                }
                catch (Exception e)
                {
                    throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}", e);
                }
            }
            return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations);
        }

        public byte[] GenerateSalt()
        {
            byte[] salt = new byte[64];
            rng.GetBytes(salt);
            return salt;
        }
    }
}