private string GetHash(string queryStringTokenKey) { if (!_memoryCache.TryGetValue(queryStringTokenKey, out var result)) { using var entry = _memoryCache.CreateEntry(queryStringTokenKey); entry.SlidingExpiration = TimeSpan.FromHours(5); using var hmac = new HMACSHA256(_hashKey); // queryStringTokenKey also contains prefix var chars = queryStringTokenKey.AsSpan(TokenCacheKeyPrefix.Length); // only allocate on stack if it's small enough var requiredLength = Encoding.UTF8.GetByteCount(chars); var stringBytes = requiredLength < 1024 ? stackalloc byte[requiredLength] : new byte[requiredLength]; // 256 for SHA-256, fits in stack nicely Span <byte> hashBytes = stackalloc byte[hmac.HashSize]; var stringBytesLength = Encoding.UTF8.GetBytes(chars, stringBytes); hmac.TryComputeHash(stringBytes.Slice(0, stringBytesLength), hashBytes, out var hashBytesLength); entry.Value = result = Convert.ToBase64String(hashBytes.Slice(0, hashBytesLength)); } return((string)result); }
public static bool TryGetHmacSha256(byte[] secret, ReadOnlySpan <byte> message, Span <byte> sign) { secret.ThrowIfNullArgument(nameof(secret)); var hmac = new HMACSHA256(secret); return(hmac.TryComputeHash(message, sign, out var written)); }
public static void ComputeHash(this HMACSHA256 hmac, ReadOnlySpan <byte> source, Span <byte> destination) { const int HashSize = 32; if (!hmac.TryComputeHash(source, destination, out int bytesWritten) || bytesWritten != HashSize) { throw new CryptographicUnexpectedOperationException(); } }
/// <summary> /// Decrypts a message using AES-256-CBC and verifies its HMAC-SHA-256 digest. /// </summary> /// <param name="ciphertext">A buffer containing an HMAC (32 byte), IV (16 byte) and at least one AES-encrypted block.</param> /// <param name="hmacKey">A 256 bit key for HMAC-SHA-256 message digest calculation.</param> /// <param name="aesKey">A 256 bit key for AES-256-CBC encryption.</param> public static ReadOnlyMemory <byte> DecryptWithHmac(ReadOnlyMemory <byte> ciphertext, byte[] hmacKey, byte[] aesKey) { if (hmacKey == null) { throw new ArgumentNullException(nameof(hmacKey)); } if (aesKey == null) { throw new ArgumentNullException(nameof(aesKey)); } if (ciphertext.Length < 64) { throw new ArgumentException("The ciphertext must be at least 64 bytes long.", nameof(ciphertext)); } if (hmacKey.Length != 32) { throw new ArgumentException("The HMAC key must be exactly 32 bytes long.", nameof(hmacKey)); } if (aesKey.Length != 32) { throw new ArgumentException("The AES key must be exaclty 32 bytes long.", nameof(aesKey)); } using (var hmac = new HMACSHA256(hmacKey)) { Span <byte> actualHash = stackalloc byte[32]; if (!hmac.TryComputeHash(ciphertext.Span.Slice(32), actualHash, out _)) { throw new CryptographicException("HMAC calculation failed for unknown reason"); } if (!actualHash.SequenceEqual(ciphertext.Span.Slice(0, 32))) { throw new CryptographicException("Message Corrupted: The HMAC values are not equal. The encrypted block may be tampered."); } } byte[] iv = ciphertext.Slice(32, 16).ToArray(); ReadOnlyMemory <byte> encrypted = ciphertext.Slice(48); if (!MemoryMarshal.TryGetArray(encrypted, out ArraySegment <byte> cipherSegment)) { cipherSegment = encrypted.ToArray(); } using ICryptoTransform transform = aes.CreateDecryptor(aesKey, iv); return(ProcessData(cipherSegment, transform, 0)); }
/// <summary> /// Encrypts a message with a random IV and AES-256-CBC and computes its HMAC-SHA-256 digest. /// The output order is HMAC (32 byte), IV (16 byte), ciphertext. /// </summary> /// <param name="plaintext"></param> /// <param name="hmacKey">A 256 bit key for HMAC-SHA-256 message digest calculation.</param> /// <param name="aesKey">A 256 bit key for AES-256-CBC encryption.</param> public static ReadOnlyMemory <byte> EncryptWithHmac(ReadOnlyMemory <byte> plaintext, byte[] hmacKey, byte[] aesKey) { if (hmacKey == null) { throw new ArgumentNullException(nameof(hmacKey)); } if (aesKey == null) { throw new ArgumentNullException(nameof(aesKey)); } if (hmacKey.Length != 32) { throw new ArgumentException("The HMAC key must be exactly 32 bytes long.", nameof(hmacKey)); } if (aesKey.Length != 32) { throw new ArgumentException("The AES key must be exaclty 32 bytes long.", nameof(aesKey)); } byte[] iv = new byte[16]; RandomNumberGenerator.Fill(iv); Memory <byte> ciphertext; using (ICryptoTransform transform = aes.CreateEncryptor(aesKey, iv)) { if (!MemoryMarshal.TryGetArray(plaintext, out ArraySegment <byte> plainSegment)) { plainSegment = plaintext.ToArray(); } ciphertext = ProcessData(plainSegment, transform, 32 + 16); } iv.AsSpan().CopyTo(ciphertext.Span.Slice(32)); using (var hmac = new HMACSHA256(hmacKey)) { if (!hmac.TryComputeHash(ciphertext.Span.Slice(32), ciphertext.Span.Slice(0, 32), out _)) { throw new CryptographicException("HMAC calculation failed for unknown reason"); } } return(ciphertext); }
private bool TrySign(ReadOnlySpan <byte> input, Span <byte> output) { using (var hmac = new HMACSHA256(this.SigningKey)) return(hmac.TryComputeHash(input, output, out _)); }