|
| 1 | +// Licensed to the .NET Foundation under one or more agreements. |
| 2 | +// The .NET Foundation licenses this file to you under the MIT license. |
| 3 | + |
| 4 | +#if !NETFRAMEWORK || NET471_OR_GREATER |
| 5 | +#error "This implementation of IncrementalHash should only be used for .NET Framework where it is not available" |
| 6 | +#endif |
| 7 | + |
| 8 | +using System.Diagnostics; |
| 9 | + |
| 10 | +namespace System.Security.Cryptography |
| 11 | +{ |
| 12 | + /// <summary> |
| 13 | + /// Provides support for computing a hash or HMAC value incrementally across several segments. |
| 14 | + /// </summary> |
| 15 | + internal sealed class IncrementalHash : IDisposable |
| 16 | + { |
| 17 | + private const int NTE_BAD_ALGID = unchecked((int)0x80090008); |
| 18 | + |
| 19 | + private readonly HashAlgorithmName _algorithmName; |
| 20 | + private HashAlgorithm _hash; |
| 21 | + private bool _disposed; |
| 22 | + private bool _resetPending; |
| 23 | + |
| 24 | + private IncrementalHash(HashAlgorithmName name, HashAlgorithm hash) |
| 25 | + { |
| 26 | + Debug.Assert(!string.IsNullOrEmpty(name.Name)); |
| 27 | + Debug.Assert(hash != null); |
| 28 | + |
| 29 | + _algorithmName = name; |
| 30 | + _hash = hash; |
| 31 | + } |
| 32 | + |
| 33 | + /// <summary> |
| 34 | + /// Get the name of the algorithm being performed. |
| 35 | + /// </summary> |
| 36 | + public HashAlgorithmName AlgorithmName |
| 37 | + { |
| 38 | + get { return _algorithmName; } |
| 39 | + } |
| 40 | + |
| 41 | + /// <summary> |
| 42 | + /// Append the entire contents of <paramref name="data"/> to the data already processed in the hash or HMAC. |
| 43 | + /// </summary> |
| 44 | + /// <param name="data">The data to process.</param> |
| 45 | + /// <exception cref="ArgumentNullException"><paramref name="data"/> is <c>null</c>.</exception> |
| 46 | + /// <exception cref="ObjectDisposedException">The object has already been disposed.</exception> |
| 47 | + public void AppendData(byte[] data) |
| 48 | + { |
| 49 | + if (data == null) |
| 50 | + throw new ArgumentNullException(nameof(data)); |
| 51 | + |
| 52 | + AppendData(data, 0, data.Length); |
| 53 | + } |
| 54 | + |
| 55 | + /// <summary> |
| 56 | + /// Append <paramref name="count"/> bytes of <paramref name="data"/>, starting at <paramref name="offset"/>, |
| 57 | + /// to the data already processed in the hash or HMAC. |
| 58 | + /// </summary> |
| 59 | + /// <param name="data">The data to process.</param> |
| 60 | + /// <param name="offset">The offset into the byte array from which to begin using data.</param> |
| 61 | + /// <param name="count">The number of bytes in the array to use as data.</param> |
| 62 | + /// <exception cref="ArgumentNullException"><paramref name="data"/> is <c>null</c>.</exception> |
| 63 | + /// <exception cref="ArgumentOutOfRangeException"> |
| 64 | + /// <paramref name="offset"/> is out of range. This parameter requires a non-negative number. |
| 65 | + /// </exception> |
| 66 | + /// <exception cref="ArgumentOutOfRangeException"> |
| 67 | + /// <paramref name="count"/> is out of range. This parameter requires a non-negative number less than |
| 68 | + /// the <see cref="Array.Length"/> value of <paramref name="data"/>. |
| 69 | + /// </exception> |
| 70 | + /// <exception cref="ArgumentException"> |
| 71 | + /// <paramref name="count"/> is greater than |
| 72 | + /// <paramref name="data"/>.<see cref="Array.Length"/> - <paramref name="offset"/>. |
| 73 | + /// </exception> |
| 74 | + /// <exception cref="ObjectDisposedException">The object has already been disposed.</exception> |
| 75 | + public void AppendData(byte[] data, int offset, int count) |
| 76 | + { |
| 77 | + if (data == null) |
| 78 | + throw new ArgumentNullException(nameof(data)); |
| 79 | + if (offset < 0) |
| 80 | + throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum); |
| 81 | + if (count < 0 || (count > data.Length)) |
| 82 | + throw new ArgumentOutOfRangeException(nameof(count)); |
| 83 | + if ((data.Length - count) < offset) |
| 84 | + throw new ArgumentException(SR.Argument_InvalidOffLen); |
| 85 | + if (_disposed) |
| 86 | + throw new ObjectDisposedException(nameof(IncrementalHash)); |
| 87 | + |
| 88 | + Debug.Assert(_hash != null); |
| 89 | + |
| 90 | + if (_resetPending) |
| 91 | + { |
| 92 | + _hash.Initialize(); |
| 93 | + _resetPending = false; |
| 94 | + } |
| 95 | + |
| 96 | + _hash.TransformBlock(data, offset, count, null, 0); |
| 97 | + } |
| 98 | + |
| 99 | + /// <summary> |
| 100 | + /// Retrieve the hash or HMAC for the data accumulated from prior calls to |
| 101 | + /// <see cref="AppendData(byte[])"/>, and return to the state the object |
| 102 | + /// was in at construction. |
| 103 | + /// </summary> |
| 104 | + /// <returns>The computed hash or HMAC.</returns> |
| 105 | + /// <exception cref="ObjectDisposedException">The object has already been disposed.</exception> |
| 106 | + public byte[] GetHashAndReset() |
| 107 | + { |
| 108 | + if (_disposed) |
| 109 | + throw new ObjectDisposedException(nameof(IncrementalHash)); |
| 110 | + |
| 111 | + Debug.Assert(_hash != null); |
| 112 | + |
| 113 | + if (_resetPending) |
| 114 | + { |
| 115 | + // No point in setting _resetPending to false, we're about to set it to true. |
| 116 | + _hash.Initialize(); |
| 117 | + } |
| 118 | + |
| 119 | + _hash.TransformFinalBlock(Array.Empty<byte>(), 0, 0); |
| 120 | + byte[] hashValue = _hash.Hash; |
| 121 | + _resetPending = true; |
| 122 | + |
| 123 | + return hashValue; |
| 124 | + } |
| 125 | + |
| 126 | + /// <summary> |
| 127 | + /// Release all resources used by the current instance of the |
| 128 | + /// <see cref="IncrementalHash"/> class. |
| 129 | + /// </summary> |
| 130 | + public void Dispose() |
| 131 | + { |
| 132 | + _disposed = true; |
| 133 | + |
| 134 | + if (_hash != null) |
| 135 | + { |
| 136 | + _hash.Dispose(); |
| 137 | + _hash = null; |
| 138 | + } |
| 139 | + } |
| 140 | + |
| 141 | + /// <summary> |
| 142 | + /// Create an <see cref="IncrementalHash"/> for the algorithm specified by <paramref name="hashAlgorithm"/>. |
| 143 | + /// </summary> |
| 144 | + /// <param name="hashAlgorithm">The name of the hash algorithm to perform.</param> |
| 145 | + /// <returns> |
| 146 | + /// An <see cref="IncrementalHash"/> instance ready to compute the hash algorithm specified |
| 147 | + /// by <paramref name="hashAlgorithm"/>. |
| 148 | + /// </returns> |
| 149 | + /// <exception cref="ArgumentException"> |
| 150 | + /// <paramref name="hashAlgorithm"/>.<see cref="HashAlgorithmName.Name"/> is <c>null</c>, or |
| 151 | + /// the empty string. |
| 152 | + /// </exception> |
| 153 | + /// <exception cref="CryptographicException"><paramref name="hashAlgorithm"/> is not a known hash algorithm.</exception> |
| 154 | + public static IncrementalHash CreateHash(HashAlgorithmName hashAlgorithm) |
| 155 | + { |
| 156 | + if (string.IsNullOrEmpty(hashAlgorithm.Name)) |
| 157 | + throw new ArgumentException(SR.Cryptography_HashAlgorithmNameNullOrEmpty, nameof(hashAlgorithm)); |
| 158 | + |
| 159 | + return new IncrementalHash(hashAlgorithm, GetHashAlgorithm(hashAlgorithm)); |
| 160 | + } |
| 161 | + |
| 162 | + /// <summary> |
| 163 | + /// Create an <see cref="IncrementalHash"/> for the Hash-based Message Authentication Code (HMAC) |
| 164 | + /// algorithm utilizing the hash algorithm specified by <paramref name="hashAlgorithm"/>, and a |
| 165 | + /// key specified by <paramref name="key"/>. |
| 166 | + /// </summary> |
| 167 | + /// <param name="hashAlgorithm">The name of the hash algorithm to perform within the HMAC.</param> |
| 168 | + /// <param name="key"> |
| 169 | + /// The secret key for the HMAC. The key can be any length, but a key longer than the output size |
| 170 | + /// of the hash algorithm specified by <paramref name="hashAlgorithm"/> will be hashed (using the |
| 171 | + /// algorithm specified by <paramref name="hashAlgorithm"/>) to derive a correctly-sized key. Therefore, |
| 172 | + /// the recommended size of the secret key is the output size of the hash specified by |
| 173 | + /// <paramref name="hashAlgorithm"/>. |
| 174 | + /// </param> |
| 175 | + /// <returns> |
| 176 | + /// An <see cref="IncrementalHash"/> instance ready to compute the hash algorithm specified |
| 177 | + /// by <paramref name="hashAlgorithm"/>. |
| 178 | + /// </returns> |
| 179 | + /// <exception cref="ArgumentException"> |
| 180 | + /// <paramref name="hashAlgorithm"/>.<see cref="HashAlgorithmName.Name"/> is <c>null</c>, or |
| 181 | + /// the empty string. |
| 182 | + /// </exception> |
| 183 | + /// <exception cref="CryptographicException"><paramref name="hashAlgorithm"/> is not a known hash algorithm.</exception> |
| 184 | + public static IncrementalHash CreateHMAC(HashAlgorithmName hashAlgorithm, byte[] key) |
| 185 | + { |
| 186 | + if (key == null) |
| 187 | + throw new ArgumentNullException(nameof(key)); |
| 188 | + if (string.IsNullOrEmpty(hashAlgorithm.Name)) |
| 189 | + throw new ArgumentException(SR.Cryptography_HashAlgorithmNameNullOrEmpty, nameof(hashAlgorithm)); |
| 190 | + |
| 191 | + return new IncrementalHash(hashAlgorithm, GetHMAC(hashAlgorithm, key)); |
| 192 | + } |
| 193 | + |
| 194 | + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5351", Justification = "MD5 is used when the user asks for it.")] |
| 195 | + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "SHA1 is used when the user asks for it.")] |
| 196 | + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5354", Justification = "SHA1 is used when the user asks for it.")] |
| 197 | + private static HashAlgorithm GetHashAlgorithm(HashAlgorithmName hashAlgorithm) |
| 198 | + { |
| 199 | + if (hashAlgorithm == HashAlgorithmName.MD5) |
| 200 | + return new MD5CryptoServiceProvider(); |
| 201 | + if (hashAlgorithm == HashAlgorithmName.SHA1) |
| 202 | + return new SHA1CryptoServiceProvider(); |
| 203 | + if (hashAlgorithm == HashAlgorithmName.SHA256) |
| 204 | + return new SHA256CryptoServiceProvider(); |
| 205 | + if (hashAlgorithm == HashAlgorithmName.SHA384) |
| 206 | + return new SHA384CryptoServiceProvider(); |
| 207 | + if (hashAlgorithm == HashAlgorithmName.SHA512) |
| 208 | + return new SHA512CryptoServiceProvider(); |
| 209 | + |
| 210 | + throw new CryptographicException(NTE_BAD_ALGID); |
| 211 | + } |
| 212 | + |
| 213 | + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5351", Justification = "MD5 is used when the user asks for it.")] |
| 214 | + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "SHA1 is used when the user asks for it.")] |
| 215 | + private static HashAlgorithm GetHMAC(HashAlgorithmName hashAlgorithm, byte[] key) |
| 216 | + { |
| 217 | + if (hashAlgorithm == HashAlgorithmName.MD5) |
| 218 | + return new HMACMD5(key); |
| 219 | + if (hashAlgorithm == HashAlgorithmName.SHA1) |
| 220 | + return new HMACSHA1(key); |
| 221 | + if (hashAlgorithm == HashAlgorithmName.SHA256) |
| 222 | + return new HMACSHA256(key); |
| 223 | + if (hashAlgorithm == HashAlgorithmName.SHA384) |
| 224 | + return new HMACSHA384(key); |
| 225 | + if (hashAlgorithm == HashAlgorithmName.SHA512) |
| 226 | + return new HMACSHA512(key); |
| 227 | + |
| 228 | + throw new CryptographicException(NTE_BAD_ALGID); |
| 229 | + } |
| 230 | + } |
| 231 | +} |
0 commit comments