patterncsharpMinor
C# SHA512 Memory Issue
Viewed 0 times
issuesha512memory
Problem
I need an implementation of PBKDF2 in C#. There is already a class
```
using System;
using System.Reflection;
using System.Runtime;
using System.Security;
using System.Security.Cryptography;
namespace HashImprovement
{
public class CustomSHA512Managed : SHA512Managed
{
private bool disposed;
private ulong count;
private readonly ulong[] stateSHA512;
private readonly byte[] resultBlock = new byte[64];
private byte[] partIn;
public CustomSHA512Managed()
{
stateSHA512 = (ulong[]) typeof (SHA512Managed)
.GetField("_stateSHA512", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(this);
}
public new byte[] ComputeHash(byte[] buffer)
{
if (disposed)
throw new ObjectDisposedException(null);
if (buffer == null)
throw new ArgumentNullException("buffer");
count = (ulong) buffer.Length;
HashCore(buffer, 0, buffer.Length);
HashValue = HashFinal();
Initialize();
return HashValue;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
disposed = true;
}
[SecuritySafeCritical]
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
protected override byte[] HashFinal()
{
return EndH
Rfc2898DeriveBytes, but it uses SHA1, and I need SHA512. The problem that I faced with during implementation is intensive allocation of memory after each hash computing iteration. The proposed solution is to change a little bit SHA512Managed class implementation using disassembler. The problem is in EndHash method: every iteration it creates two new arrays. My solution is below. What do you think about it? May be there are some security considerations that I don't know?```
using System;
using System.Reflection;
using System.Runtime;
using System.Security;
using System.Security.Cryptography;
namespace HashImprovement
{
public class CustomSHA512Managed : SHA512Managed
{
private bool disposed;
private ulong count;
private readonly ulong[] stateSHA512;
private readonly byte[] resultBlock = new byte[64];
private byte[] partIn;
public CustomSHA512Managed()
{
stateSHA512 = (ulong[]) typeof (SHA512Managed)
.GetField("_stateSHA512", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(this);
}
public new byte[] ComputeHash(byte[] buffer)
{
if (disposed)
throw new ObjectDisposedException(null);
if (buffer == null)
throw new ArgumentNullException("buffer");
count = (ulong) buffer.Length;
HashCore(buffer, 0, buffer.Length);
HashValue = HashFinal();
Initialize();
return HashValue;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
disposed = true;
}
[SecuritySafeCritical]
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
protected override byte[] HashFinal()
{
return EndH
Solution
Concerning the design: It's a hack.
-
Returning the same array each time violates the contract of the base class. Not a big issue as long as the class is internal.
Consider using a different method, something like
-
Are you sure the cost of allocating the array matters?
Each SHA-512 compression (and with typical use in PBKDF2-HMAC-SHA-2 you'll have two of them per hash) costs about 2000 CPU cycles. In my micro-benchmarks the cost of allocating and collecting small short lived objects is below 100 CPU cycles.
Concerning the code itself:
-
You write:
and
This is unintuitive. While both the block size of SHA-512 and
Define a constant
-
You write:
I'd simply use an unchecked context and eliminate the
Alternative approaches:
-
Use native interop to call a high performance native library.
While
-
Start with an open source implementation of SHA-512 and tweak it to your needs.
-
A bit of advertisement:
I wrote a public domain implementation of SHA-512 which is about 30% faster than
Compression function and the hash including padding.
- It uses private reflection, so Microsoft can change
SHA512Managedwhenever they feel like it, breaking your code. It also might not work on mono.
-
Returning the same array each time violates the contract of the base class. Not a big issue as long as the class is internal.
Consider using a different method, something like
void GetHash(byte[] buffer) instead.-
Are you sure the cost of allocating the array matters?
Each SHA-512 compression (and with typical use in PBKDF2-HMAC-SHA-2 you'll have two of them per hash) costs about 2000 CPU cycles. In my micro-benchmarks the cost of allocating and collecting small short lived objects is below 100 CPU cycles.
Concerning the code itself:
-
You write:
var length = 128 - (int) ((long) count & sbyte.MaxValue)and
partIn[0] = (byte)sbyte.MinValue;This is unintuitive. While both the block size of SHA-512 and
sbyte.MaxValue + 1 happen to be 128, they're completely unrelated.Define a constant
const int BlockSize = 128 and replace this with:var length = BlockSize - (int)(count & (BlockSize-1));-
You write:
partIn[length - 8] = (byte)(num >> 56 & byte.MaxValue);I'd simply use an unchecked context and eliminate the
& byte.MaxValue part.Alternative approaches:
-
Use native interop to call a high performance native library.
While
SHA512CSP and SHA512Cng have a high per-call overhead which makes them slower than SHA512Managed, this isn't true for native interop in general. In a different project I called OpenSSL's MD5 from C#, which was much faster than any implementation built into .NET.- Use an existing managed implementation of PBKDF2-HMAC-SHA-512. I think BouncyCastle includes it.
-
Start with an open source implementation of SHA-512 and tweak it to your needs.
- Make sure to save the state of SHA-512 after the first block, this speeds up HMAC by a factor 2.
- Most of the time the output of SHA-512 is used as the input for the next iteration. No need to convert between bytes and longs on each iteration.
- Apart from the first iteration the message length is fixed (512 bits), so you can hardcode the padding.
-
A bit of advertisement:
I wrote a public domain implementation of SHA-512 which is about 30% faster than
Sha512Managed, when including the above optimizations you should be able to be at least 2.6 times as fast as an implementation that uses the built in HMAC-SHA-512 as a blackbox.Compression function and the hash including padding.
Code Snippets
var length = 128 - (int) ((long) count & sbyte.MaxValue)partIn[0] = (byte)sbyte.MinValue;var length = BlockSize - (int)(count & (BlockSize-1));partIn[length - 8] = (byte)(num >> 56 & byte.MaxValue);Context
StackExchange Code Review Q#78020, answer score: 4
Revisions (0)
No revisions yet.