@@ -14,18 +14,23 @@ namespace Microsoft.Build.Tasks
1414 /// Generates a hash of a given ItemGroup items. Metadata is not considered in the hash.
1515 /// </summary>
1616 /// <remarks>
17- /// Currently uses SHA1. Implementation subject to change between MSBuild versions.
18- /// This class is not intended as a cryptographic security measure, only uniqueness between build executions.
17+ /// Currently uses SHA256. Implementation subject to change between MSBuild versions.
18+ /// This class is not intended as a cryptographic security measure, only uniqueness between build executions
19+ /// - collisions can theoretically be possible in the future (should we move to noncrypto hash) and should be handled gracefully by the caller.
20+ ///
21+ /// Usage of cryptographic secure hash brings slight performance penalty, but it is considered acceptable.
22+ /// Would this need to be revised - XxHash64 from System.Io.Hashing could be used instead for better performance.
23+ /// (That however currently requires load of additional binary into VS process which has it's own costs)
1924 /// </remarks>
2025 public class Hash : TaskExtension
2126 {
2227 private const char ItemSeparatorCharacter = '\u2028 ' ;
2328 private static readonly Encoding s_encoding = Encoding . UTF8 ;
2429 private static readonly byte [ ] s_itemSeparatorCharacterBytes = s_encoding . GetBytes ( new char [ ] { ItemSeparatorCharacter } ) ;
2530
26- // Size of buffer where bytes of the strings are stored until sha1 .TransformBlock is to be run on them.
27- // It is needed to get a balance between amount of costly sha1 .TransformBlock calls and amount of allocated memory.
28- private const int Sha1BufferSize = 512 ;
31+ // Size of buffer where bytes of the strings are stored until sha .TransformBlock is to be run on them.
32+ // It is needed to get a balance between amount of costly sha .TransformBlock calls and amount of allocated memory.
33+ private const int ShaBufferSize = 512 ;
2934
3035 // Size of chunks in which ItemSpecs would be cut.
3136 // We have chosen this length so itemSpecChunkByteBuffer rented from ArrayPool will be close but not bigger than 512.
@@ -56,42 +61,42 @@ public override bool Execute()
5661 {
5762 if ( ItemsToHash ? . Length > 0 )
5863 {
59- using ( var sha1 = SHA1 . Create ( ) )
64+ using ( var sha = CreateHashAlgorithm ( ) )
6065 {
6166 // Buffer in which bytes of the strings are to be stored until their number reaches the limit size.
62- // Once the limit is reached, the sha1 .TransformBlock is to be run on all the bytes of this buffer.
63- byte [ ] sha1Buffer = null ;
67+ // Once the limit is reached, the sha .TransformBlock is to be run on all the bytes of this buffer.
68+ byte [ ] shaBuffer = null ;
6469
6570 // Buffer in which bytes of items' ItemSpec are to be stored.
6671 byte [ ] itemSpecChunkByteBuffer = null ;
6772
6873 try
6974 {
70- sha1Buffer = System . Buffers . ArrayPool < byte > . Shared . Rent ( Sha1BufferSize ) ;
75+ shaBuffer = System . Buffers . ArrayPool < byte > . Shared . Rent ( ShaBufferSize ) ;
7176 itemSpecChunkByteBuffer = System . Buffers . ArrayPool < byte > . Shared . Rent ( s_encoding . GetMaxByteCount ( MaxInputChunkLength ) ) ;
7277
73- int sha1BufferPosition = 0 ;
78+ int shaBufferPosition = 0 ;
7479 for ( int i = 0 ; i < ItemsToHash . Length ; i ++ )
7580 {
7681 string itemSpec = IgnoreCase ? ItemsToHash [ i ] . ItemSpec . ToUpperInvariant ( ) : ItemsToHash [ i ] . ItemSpec ;
7782
78- // Slice the itemSpec string into chunks of reasonable size and add them to sha1 buffer.
83+ // Slice the itemSpec string into chunks of reasonable size and add them to sha buffer.
7984 for ( int itemSpecPosition = 0 ; itemSpecPosition < itemSpec . Length ; itemSpecPosition += MaxInputChunkLength )
8085 {
8186 int charsToProcess = Math . Min ( itemSpec . Length - itemSpecPosition , MaxInputChunkLength ) ;
8287 int byteCount = s_encoding . GetBytes ( itemSpec , itemSpecPosition , charsToProcess , itemSpecChunkByteBuffer , 0 ) ;
8388
84- sha1BufferPosition = AddBytesToSha1Buffer ( sha1 , sha1Buffer , sha1BufferPosition , Sha1BufferSize , itemSpecChunkByteBuffer , byteCount ) ;
89+ shaBufferPosition = AddBytesToShaBuffer ( sha , shaBuffer , shaBufferPosition , ShaBufferSize , itemSpecChunkByteBuffer , byteCount ) ;
8590 }
8691
87- sha1BufferPosition = AddBytesToSha1Buffer ( sha1 , sha1Buffer , sha1BufferPosition , Sha1BufferSize , s_itemSeparatorCharacterBytes , s_itemSeparatorCharacterBytes . Length ) ;
92+ shaBufferPosition = AddBytesToShaBuffer ( sha , shaBuffer , shaBufferPosition , ShaBufferSize , s_itemSeparatorCharacterBytes , s_itemSeparatorCharacterBytes . Length ) ;
8893 }
8994
90- sha1 . TransformFinalBlock ( sha1Buffer , 0 , sha1BufferPosition ) ;
95+ sha . TransformFinalBlock ( shaBuffer , 0 , shaBufferPosition ) ;
9196
92- using ( var stringBuilder = new ReuseableStringBuilder ( sha1 . HashSize ) )
97+ using ( var stringBuilder = new ReuseableStringBuilder ( sha . HashSize ) )
9398 {
94- foreach ( var b in sha1 . Hash )
99+ foreach ( var b in sha . Hash )
95100 {
96101 stringBuilder . Append ( b . ToString ( "x2" ) ) ;
97102 }
@@ -100,9 +105,9 @@ public override bool Execute()
100105 }
101106 finally
102107 {
103- if ( sha1Buffer != null )
108+ if ( shaBuffer != null )
104109 {
105- System . Buffers . ArrayPool < byte > . Shared . Return ( sha1Buffer ) ;
110+ System . Buffers . ArrayPool < byte > . Shared . Return ( shaBuffer ) ;
106111 }
107112 if ( itemSpecChunkByteBuffer != null )
108113 {
@@ -114,44 +119,54 @@ public override bool Execute()
114119 return true ;
115120 }
116121
122+ private HashAlgorithm CreateHashAlgorithm ( )
123+ {
124+ return ChangeWaves . AreFeaturesEnabled ( ChangeWaves . Wave17_8 ) ?
125+ SHA256 . Create ( ) :
126+ #pragma warning disable CA5350
127+ // Kept for back compatibility reasons when chnange wave is opted-out
128+ SHA1 . Create ( ) ;
129+ #pragma warning restore CA5350
130+ }
131+
117132 /// <summary>
118- /// Add bytes to the sha1 buffer. Once the limit size is reached, sha1 .TransformBlock is called and the buffer is flushed.
133+ /// Add bytes to the sha buffer. Once the limit size is reached, sha .TransformBlock is called and the buffer is flushed.
119134 /// </summary>
120- /// <param name="sha1 ">Hashing algorithm sha1 .</param>
121- /// <param name="sha1Buffer ">The sha1 buffer which stores bytes of the strings. Bytes should be added to this buffer.</param>
122- /// <param name="sha1BufferPosition ">Number of used bytes of the sha1 buffer.</param>
123- /// <param name="sha1BufferSize ">The size of sha1 buffer.</param>
124- /// <param name="byteBuffer">Bytes buffer which contains bytes to be written to sha1 buffer.</param>
125- /// <param name="byteCount">Amount of bytes that are to be added to sha1 buffer.</param>
126- /// <returns>Updated sha1BufferPosition .</returns>
127- private int AddBytesToSha1Buffer ( SHA1 sha1 , byte [ ] sha1Buffer , int sha1BufferPosition , int sha1BufferSize , byte [ ] byteBuffer , int byteCount )
135+ /// <param name="sha ">Hashing algorithm sha .</param>
136+ /// <param name="shaBuffer ">The sha buffer which stores bytes of the strings. Bytes should be added to this buffer.</param>
137+ /// <param name="shaBufferPosition ">Number of used bytes of the sha buffer.</param>
138+ /// <param name="shaBufferSize ">The size of sha buffer.</param>
139+ /// <param name="byteBuffer">Bytes buffer which contains bytes to be written to sha buffer.</param>
140+ /// <param name="byteCount">Amount of bytes that are to be added to sha buffer.</param>
141+ /// <returns>Updated shaBufferPosition .</returns>
142+ private int AddBytesToShaBuffer ( HashAlgorithm sha , byte [ ] shaBuffer , int shaBufferPosition , int shaBufferSize , byte [ ] byteBuffer , int byteCount )
128143 {
129144 int bytesProcessed = 0 ;
130- while ( sha1BufferPosition + byteCount >= sha1BufferSize )
145+ while ( shaBufferPosition + byteCount >= shaBufferSize )
131146 {
132- int sha1BufferFreeSpace = sha1BufferSize - sha1BufferPosition ;
147+ int shaBufferFreeSpace = shaBufferSize - shaBufferPosition ;
133148
134- if ( sha1BufferPosition == 0 )
149+ if ( shaBufferPosition == 0 )
135150 {
136- // If sha1 buffer is empty and bytes number is big enough there is no need to copy bytes to sha1 buffer.
151+ // If sha buffer is empty and bytes number is big enough there is no need to copy bytes to sha buffer.
137152 // Pass the bytes to TransformBlock right away.
138- sha1 . TransformBlock ( byteBuffer , bytesProcessed , sha1BufferSize , null , 0 ) ;
153+ sha . TransformBlock ( byteBuffer , bytesProcessed , shaBufferSize , null , 0 ) ;
139154 }
140155 else
141156 {
142- Array . Copy ( byteBuffer , bytesProcessed , sha1Buffer , sha1BufferPosition , sha1BufferFreeSpace ) ;
143- sha1 . TransformBlock ( sha1Buffer , 0 , sha1BufferSize , null , 0 ) ;
144- sha1BufferPosition = 0 ;
157+ Array . Copy ( byteBuffer , bytesProcessed , shaBuffer , shaBufferPosition , shaBufferFreeSpace ) ;
158+ sha . TransformBlock ( shaBuffer , 0 , shaBufferSize , null , 0 ) ;
159+ shaBufferPosition = 0 ;
145160 }
146161
147- bytesProcessed += sha1BufferFreeSpace ;
148- byteCount -= sha1BufferFreeSpace ;
162+ bytesProcessed += shaBufferFreeSpace ;
163+ byteCount -= shaBufferFreeSpace ;
149164 }
150165
151- Array . Copy ( byteBuffer , bytesProcessed , sha1Buffer , sha1BufferPosition , byteCount ) ;
152- sha1BufferPosition += byteCount ;
166+ Array . Copy ( byteBuffer , bytesProcessed , shaBuffer , shaBufferPosition , byteCount ) ;
167+ shaBufferPosition += byteCount ;
153168
154- return sha1BufferPosition ;
169+ return shaBufferPosition ;
155170 }
156171 }
157172}
0 commit comments