44using System ;
55using System . Buffers ;
66using System . Runtime . CompilerServices ;
7- using System . Runtime . InteropServices ;
87using System . Security . Cryptography ;
98using System . Text ;
9+ using SixLabors . ImageSharp . Web . Caching ;
1010
1111namespace SixLabors . ImageSharp . Web
1212{
@@ -15,55 +15,82 @@ namespace SixLabors.ImageSharp.Web
1515 /// </summary>
1616 public static class HMACUtilities
1717 {
18+ /// <summary>
19+ /// The command used by image requests for transporting Hash-based Message Authentication Code (HMAC) tokens.
20+ /// </summary>
21+ public const string TokenCommand = "hmac" ;
22+
1823 /// <summary>
1924 /// Computes a Hash-based Message Authentication Code (HMAC) by using the SHA256 hash function.
2025 /// </summary>
2126 /// <param name="value">The value to hash</param>
2227 /// <param name="secret">
23- /// The secret key for <see cref="HMACSHA256"/>encryption.
28+ /// The secret key for <see cref="HMACSHA256"/> encryption.
2429 /// The key can be any length. However, the recommended size is 64 bytes.
2530 /// </param>
2631 /// <returns>The hashed <see cref="string"/>.</returns>
27- public static unsafe string CreateSHA256HashCode ( string value , byte [ ] secret )
32+ public static unsafe string ComputeHMACSHA256 ( string value , byte [ ] secret )
2833 {
29- static void Action ( Span < char > chars , ( IntPtr Ptr , int Length , string Value , byte [ ] Secret ) args )
30- {
31- var bytes = new Span < byte > ( ( byte * ) args . Ptr , args . Length ) ;
32- Encoding . ASCII . GetBytes ( args . Value , bytes ) ;
33- using var hashAlgorithm = new HMACSHA256 ( args . Secret ) ;
34- hashAlgorithm . TryComputeHash ( bytes , MemoryMarshal . Cast < char , byte > ( chars ) , out int _ ) ;
35- }
34+ // TODO: In .NET 6 we can use single instance versions
35+ using var hmac = new HMACSHA256 ( secret ) ;
36+ return CreateHMAC ( value , hmac ) ;
37+ }
3638
37- // Bits to chars - 256/8/2
38- return CreateHMAC ( value , secret , 16 , Action ) ;
39+ /// <summary>
40+ /// Computes a Hash-based Message Authentication Code (HMAC) by using the SHA384 hash function.
41+ /// </summary>
42+ /// <param name="value">The value to hash</param>
43+ /// <param name="secret">
44+ /// The secret key for <see cref="HMACSHA256"/> encryption.
45+ /// The key can be any length. However, the recommended size is 128 bytes.
46+ /// </param>
47+ /// <returns>The hashed <see cref="string"/>.</returns>
48+ public static unsafe string ComputeHMACSHA384 ( string value , byte [ ] secret )
49+ {
50+ using var hmac = new HMACSHA384 ( secret ) ;
51+ return CreateHMAC ( value , hmac ) ;
52+ }
53+
54+ /// <summary>
55+ /// Computes a Hash-based Message Authentication Code (HMAC) by using the SHA512 hash function.
56+ /// </summary>
57+ /// <param name="value">The value to hash</param>
58+ /// <param name="secret">
59+ /// The secret key for <see cref="HMACSHA256"/> encryption.
60+ /// The key can be any length. However, the recommended size is 128 bytes.
61+ /// </param>
62+ /// <returns>The hashed <see cref="string"/>.</returns>
63+ public static unsafe string ComputeHMACSHA512 ( string value , byte [ ] secret )
64+ {
65+ using var hmac = new HMACSHA512 ( secret ) ;
66+ return CreateHMAC ( value , hmac ) ;
3967 }
4068
4169 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
42- private static unsafe string CreateHMAC ( string value , byte [ ] secret , int length , SpanAction < char , ( IntPtr Ptr , int Length , string Value , byte [ ] Secret ) > action )
70+ private static unsafe string CreateHMAC ( string value , HMAC hmac )
4371 {
4472 int byteCount = Encoding . ASCII . GetByteCount ( value ) ;
45-
46- // Allocating a buffer from the pool is ~27% slower than stackalloc so use that for short strings
47- if ( byteCount < 257 )
48- {
49- fixed ( byte * bytesPtr = stackalloc byte [ byteCount ] )
50- {
51- return string . Create ( length , ( ( IntPtr ) bytesPtr , byteCount , value , secret ) , action ) ;
52- }
53- }
54-
5573 byte [ ] buffer = null ;
74+
5675 try
5776 {
58- buffer = ArrayPool < byte > . Shared . Rent ( byteCount ) ;
59- fixed ( byte * bytesPtr = buffer . AsSpan ( 0 , byteCount ) )
60- {
61- return string . Create ( length , ( ( IntPtr ) bytesPtr , byteCount , value , secret ) , action ) ;
62- }
77+ // Allocating a buffer from the pool is ~27% slower than stackalloc so use that for short strings
78+ Span < byte > bytes = byteCount <= 128
79+ ? stackalloc byte [ byteCount ]
80+ : ( buffer = ArrayPool < byte > . Shared . Rent ( byteCount ) ) . AsSpan ( 0 , byteCount ) ;
81+
82+ Encoding . ASCII . GetBytes ( value , bytes ) ;
83+
84+ // Safe to always stackalloc here. We max out at 64 bytes.
85+ Span < byte > hash = stackalloc byte [ hmac . HashSize / 8 ] ;
86+ hmac . TryComputeHash ( bytes , hash , out int _ ) ;
87+
88+ // Finally encode the hash to make it web safe.
89+ return HexEncoder . Encode ( hash ) ;
6390 }
6491 finally
6592 {
66- if ( buffer != null )
93+ if ( buffer is not null )
6794 {
6895 ArrayPool < byte > . Shared . Return ( buffer ) ;
6996 }
0 commit comments