HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

C# SHA512 Memory Issue

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
issuesha512memory

Problem

I need an implementation of PBKDF2 in C#. There is already a class 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.

  • It uses private reflection, so Microsoft can change SHA512Managed whenever 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.