Merge pull request #3715 from kumpera/fix-44707
[mono.git] / mcs / class / referencesource / mscorlib / system / security / cryptography / rfc2898derivebytes.cs
1 // ==++==
2 // 
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 // 
5 // ==--==
6 // <OWNER>[....]</OWNER>
7 // 
8
9 //
10 // Rfc2898DeriveBytes.cs
11 //
12
13 // This implementation follows RFC 2898 recommendations. See http://www.ietf.org/rfc/Rfc2898.txt
14 // It uses HMACSHA1 as the underlying pseudorandom function.
15
16 namespace System.Security.Cryptography {
17     using System.Globalization;
18     using System.IO;
19     using System.Text;
20     using System.Diagnostics.Contracts;
21     using System.Runtime.CompilerServices;
22     using System.Runtime.InteropServices;
23     using System.Runtime.Versioning;
24     using System.Security.Cryptography.X509Certificates;
25
26     [System.Runtime.InteropServices.ComVisible(true)]
27     public class Rfc2898DeriveBytes : DeriveBytes
28     {
29         private byte[] m_buffer;
30         private byte[] m_salt;
31         private HMACSHA1 m_hmacsha1;  // The pseudo-random generator function used in PBKDF2
32         private byte[] m_password;
33 #if !MONO
34         private CspParameters m_cspParams = new CspParameters();
35 #endif
36
37         private uint m_iterations;
38         private uint m_block;
39         private int m_startIndex;
40         private int m_endIndex;
41
42         private const int BlockSize = 20;
43
44         //
45         // public constructors
46         //
47
48         public Rfc2898DeriveBytes(string password, int saltSize) : this(password, saltSize, 1000) {}
49
50         // This method needs to be safe critical, because in debug builds the C# compiler will include null
51         // initialization of the _safeProvHandle field in the method.  Since SafeProvHandle is critical, a
52         // transparent reference triggers an error using PasswordDeriveBytes.
53         [SecuritySafeCritical]
54         public Rfc2898DeriveBytes(string password, int saltSize, int iterations) {
55             if (saltSize < 0) 
56                 throw new ArgumentOutOfRangeException("saltSize", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
57             Contract.EndContractBlock();
58
59             byte[] salt = new byte[saltSize];
60             Utils.StaticRandomNumberGenerator.GetBytes(salt);
61
62             Salt = salt;
63             IterationCount = iterations;
64             m_password = new UTF8Encoding(false).GetBytes(password);
65             m_hmacsha1 = new HMACSHA1(m_password);
66             Initialize();
67         }
68
69         public Rfc2898DeriveBytes(string password, byte[] salt) : this(password, salt, 1000) {}
70
71         public Rfc2898DeriveBytes(string password, byte[] salt, int iterations) : this (new UTF8Encoding(false).GetBytes(password), salt, iterations) {}
72
73         // This method needs to be safe critical, because in debug builds the C# compiler will include null
74         // initialization of the _safeProvHandle field in the method.  Since SafeProvHandle is critical, a
75         // transparent reference triggers an error using PasswordDeriveBytes.
76         [SecuritySafeCritical]
77         public Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations) {
78             Salt = salt;
79             IterationCount = iterations;
80             m_password = password;
81             m_hmacsha1 = new HMACSHA1(password);
82             Initialize();
83         }
84
85         //
86         // public properties
87         //
88
89         public int IterationCount {
90             get { return (int) m_iterations; }
91             set {
92                 if (value <= 0)
93                     throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
94                 Contract.EndContractBlock();
95                 m_iterations = (uint) value;
96                 Initialize();
97             }
98         }
99
100         public byte[] Salt {
101             get { return (byte[]) m_salt.Clone(); }
102             set { 
103                 if (value == null)
104                     throw new ArgumentNullException("value");
105                 if (value.Length < 8) 
106                     throw new ArgumentException(Environment.GetResourceString("Cryptography_PasswordDerivedBytes_FewBytesSalt"));
107                 Contract.EndContractBlock();
108                 m_salt = (byte[]) value.Clone(); 
109                 Initialize();
110             }
111         }
112
113         //
114         // public methods
115         //
116
117         public override byte[] GetBytes(int cb) {
118             if (cb <= 0)
119                 throw new ArgumentOutOfRangeException("cb", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
120             Contract.EndContractBlock();
121             byte[] password = new byte[cb];
122
123             int offset = 0;
124             int size = m_endIndex - m_startIndex;
125             if (size > 0) {
126                 if (cb >= size) {
127                     Buffer.InternalBlockCopy(m_buffer, m_startIndex, password, 0, size);
128                     m_startIndex = m_endIndex = 0;
129                     offset += size;
130                 } else {
131                     Buffer.InternalBlockCopy(m_buffer, m_startIndex, password, 0, cb);
132                     m_startIndex += cb;
133                     return password;
134                 }
135             }
136
137             Contract.Assert(m_startIndex == 0 && m_endIndex == 0, "Invalid start or end index in the internal buffer." );
138
139             while(offset < cb) {
140                 byte[] T_block = Func();
141                 int remainder = cb - offset;
142                 if(remainder > BlockSize) {
143                     Buffer.InternalBlockCopy(T_block, 0, password, offset, BlockSize);
144                     offset += BlockSize;
145                 } else {
146                     Buffer.InternalBlockCopy(T_block, 0, password, offset, remainder);
147                     offset += remainder;
148                     Buffer.InternalBlockCopy(T_block, remainder, m_buffer, m_startIndex, BlockSize - remainder);
149                     m_endIndex += (BlockSize - remainder);
150                     return password;
151                 }
152             }
153             return password;
154         }
155
156         public override void Reset() {
157             Initialize();
158         }
159
160         protected override void Dispose(bool disposing) {
161             base.Dispose(disposing);
162
163             if (disposing) {
164                 if (m_hmacsha1 != null) {
165                     ((IDisposable)m_hmacsha1).Dispose();
166                 }
167
168                 if (m_buffer != null) {
169                     Array.Clear(m_buffer, 0, m_buffer.Length);
170                 }
171                 if (m_salt != null) {
172                     Array.Clear(m_salt, 0, m_salt.Length);
173                 }
174             }
175         }
176
177         private void Initialize() {
178             if (m_buffer != null)
179                 Array.Clear(m_buffer, 0, m_buffer.Length);
180             m_buffer = new byte[BlockSize];
181             m_block = 1;
182             m_startIndex = m_endIndex = 0;
183         }
184
185         // This function is defined as follow :
186         // Func (S, i) = HMAC(S || i) | HMAC2(S || i) | ... | HMAC(iterations) (S || i) 
187         // where i is the block number.
188         private byte[] Func () {
189             byte[] INT_block = Utils.Int(m_block);
190
191             m_hmacsha1.TransformBlock(m_salt, 0, m_salt.Length, null, 0);
192             m_hmacsha1.TransformBlock(INT_block, 0, INT_block.Length, null, 0);
193             m_hmacsha1.TransformFinalBlock(EmptyArray<Byte>.Value, 0, 0);
194             byte[] temp = m_hmacsha1.HashValue;
195             m_hmacsha1.Initialize();
196
197             byte[] ret = temp;
198             for (int i = 2; i <= m_iterations; i++) {
199                 m_hmacsha1.TransformBlock(temp, 0, temp.Length, null, 0);
200                 m_hmacsha1.TransformFinalBlock(EmptyArray<Byte>.Value, 0, 0);
201                 temp = m_hmacsha1.HashValue;
202                 for (int j = 0; j < BlockSize; j++) {
203                     ret[j] ^= temp[j];
204                 }
205                 m_hmacsha1.Initialize();
206             }
207
208             // increment the block count.
209             m_block++;
210             return ret;
211         }
212         [System.Security.SecuritySafeCritical]  // auto-generated
213         public byte[] CryptDeriveKey(string algname, string alghashname, int keySize, byte[] rgbIV)
214         {
215             if (keySize < 0)
216                 throw new CryptographicException(Environment.GetResourceString("Cryptography_InvalidKeySize"));
217
218 #if MONO
219             throw new NotSupportedException ("CspParameters are not supported by Mono");
220 #else
221             int algidhash = X509Utils.NameOrOidToAlgId(alghashname, OidGroup.HashAlgorithm);
222             if (algidhash == 0)
223                 throw new CryptographicException(Environment.GetResourceString("Cryptography_PasswordDerivedBytes_InvalidAlgorithm"));
224
225             int algid = X509Utils.NameOrOidToAlgId(algname, OidGroup.AllGroups);
226             if (algid == 0)
227                 throw new CryptographicException(Environment.GetResourceString("Cryptography_PasswordDerivedBytes_InvalidAlgorithm"));
228
229             // Validate the rgbIV array
230             if (rgbIV == null)
231                 throw new CryptographicException(Environment.GetResourceString("Cryptography_PasswordDerivedBytes_InvalidIV"));
232
233             byte[] key = null;
234             DeriveKey(ProvHandle, algid, algidhash,
235                       m_password, m_password.Length, keySize << 16, rgbIV, rgbIV.Length,
236                       JitHelpers.GetObjectHandleOnStack(ref key));
237             return key;
238 #endif
239         }
240
241 #if !MONO
242         [System.Security.SecurityCritical] // auto-generated
243         private SafeProvHandle _safeProvHandle = null;
244         private SafeProvHandle ProvHandle
245         {
246             [System.Security.SecurityCritical]  // auto-generated
247             get
248             {
249                 if (_safeProvHandle == null)
250                 {
251                     lock (this)
252                     {
253                         if (_safeProvHandle == null)
254                         {
255                             SafeProvHandle safeProvHandle = Utils.AcquireProvHandle(m_cspParams);
256                             System.Threading.Thread.MemoryBarrier();
257                             _safeProvHandle = safeProvHandle;
258                         }
259                     }
260                 }
261                 return _safeProvHandle;
262             }
263         }
264
265         [System.Security.SecurityCritical]  // auto-generated
266         [ResourceExposure(ResourceScope.None)]
267         [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode), SuppressUnmanagedCodeSecurity]
268         private static extern void DeriveKey(SafeProvHandle hProv, int algid, int algidHash,
269                                       byte[] password, int cbPassword, int dwFlags, byte[] IV, int cbIV,
270                                       ObjectHandleOnStack retKey);
271 #endif
272
273     }
274 }