@@ -22,15 +22,11 @@ public sealed class CopyToHashCalculator : IAsyncDisposable
2222 /// <param name="hashAlgorithm">The hash algorithm to use.</param>
2323 /// <param name="conversionMethod">The enum value identifying how hash byte arrays are converted to strings.</param>
2424 /// <param name="name">A name that uniquely identifies the hash algorithm.</param>
25- public CopyToHashCalculator (
26- HashAlgorithm hashAlgorithm ,
27- HashConversionMethod conversionMethod ,
28- string ? name = null
29- )
25+ public CopyToHashCalculator ( HashAlgorithm hashAlgorithm , HashConversionMethod conversionMethod , string ? name = null )
3026 {
3127 HashAlgorithm = hashAlgorithm . MustNotBeNull ( ) ;
3228 ConversionMethod = conversionMethod . MustBeValidEnumValue ( ) ;
33- Name = name ?? hashAlgorithm . GetType ( ) . Name ;
29+ Name = name ?? DetermineDefaultName ( hashAlgorithm ) ;
3430 }
3531
3632 /// <summary>
@@ -54,25 +50,43 @@ public CopyToHashCalculator(
5450 /// <exception cref="InvalidOperationException">
5551 /// Thrown when <see cref="ObtainHashFromAlgorithm" /> has not been called yet.
5652 /// </exception>
57- public string Hash =>
58- _hash . MustNotBeNull (
59- ( ) => new InvalidOperationException (
60- $ "ObtainHashFromAlgorithm must be called before accessing the { nameof ( Hash ) } property."
61- )
62- ) ;
53+ public string Hash
54+ {
55+ get
56+ {
57+ var hash = _hash ;
58+ if ( hash is null )
59+ {
60+ throw new InvalidOperationException (
61+ $ "ObtainHashFromAlgorithm must be called before accessing the { nameof ( Hash ) } property"
62+ ) ;
63+ }
64+
65+ return hash ;
66+ }
67+ }
6368
6469 /// <summary>
6570 /// The calculated hash in byte array representation.
6671 /// </summary>
6772 /// <exception cref="InvalidOperationException">
6873 /// Thrown when <see cref="ObtainHashFromAlgorithm" /> has not been called yet.
6974 /// </exception>
70- public byte [ ] HashArray =>
71- _hashArray . MustNotBeNull (
72- ( ) => new InvalidOperationException (
73- $ "ObtainHashFromAlgorithm must be called before accessing the { nameof ( HashArray ) } property."
74- )
75- ) ;
75+ public byte [ ] HashArray
76+ {
77+ get
78+ {
79+ var hashArray = _hashArray ;
80+ if ( hashArray is null )
81+ {
82+ throw new InvalidOperationException (
83+ $ "ObtainHashFromAlgorithm must be called before accessing the { nameof ( HashArray ) } property"
84+ ) ;
85+ }
86+
87+ return hashArray ;
88+ }
89+ }
7690
7791 /// <summary>
7892 /// Asynchronously disposes the resources used by the current instance, including the CryptoStream and the hash algorithm.
@@ -90,6 +104,23 @@ public async ValueTask DisposeAsync()
90104 HashAlgorithm . Dispose ( ) ;
91105 }
92106
107+ private static string DetermineDefaultName ( HashAlgorithm hashAlgorithm )
108+ {
109+ /* Some of the .NET hash algorithms (like .SHA1, MD5) are actually just abstract base classes. They have a
110+ * nested type called implementation which can be instantiated, but this type is not publicly visible. GetType()
111+ * will likely return the implementation type, which is not what we want as callers would want the name of the
112+ * base type instead. This is why we check the name of the type for ".Implementation" and if found, we return
113+ * the name of the base type instead.
114+ *
115+ * Other types like HMACSHA256 are public non-abstract types and can be used as expected. */
116+ var type = hashAlgorithm . GetType ( ) ;
117+ var name = type . Name ;
118+ var baseTypeName = type . BaseType ? . Name ;
119+ return name . Equals ( "Implementation" , StringComparison . Ordinal ) && ! baseTypeName . IsNullOrWhiteSpace ( ) ?
120+ baseTypeName :
121+ name ;
122+ }
123+
93124 /// <summary>
94125 /// Creates a CryptoStream wrapped around the specified stream. The CryptoStream
95126 /// is configured to calculate a hash using the hash algorithm provided by the
@@ -127,22 +158,13 @@ public void ObtainHashFromAlgorithm()
127158 . Hash
128159 . MustNotBeNull (
129160 ( ) => new InvalidOperationException (
130- "The crypto stream was not written to - no hash was calculated. "
161+ "The crypto stream was not written to - no hash was calculated"
131162 )
132163 ) ;
133164
134- _hash = ConvertHashToString ( _hashArray ) ;
165+ _hash = HashConverter . ConvertHashToString ( _hashArray , ConversionMethod ) ;
135166 }
136167
137- private string ConvertHashToString ( byte [ ] hashArray ) =>
138- ConversionMethod switch
139- {
140- HashConversionMethod . Base64 => Convert . ToBase64String ( hashArray ) ,
141- HashConversionMethod . UpperHexadecimal => Convert . ToHexString ( hashArray ) ,
142- HashConversionMethod . None => "" ,
143- _ => throw new InvalidDataException ( $ "{ nameof ( ConversionMethod ) } has an invalid value")
144- } ;
145-
146168 /// <summary>
147169 /// Converts a <see cref="HashAlgorithm" /> to a <see cref="CopyToHashCalculator" /> using the default settings
148170 /// (hash array is converted to a Base64 string, name is identical to the type name).
0 commit comments