BcryptNet/bcrypt.net

SHA instances aren't disposed

andreimilto opened this issue · 5 comments

AFAIU, an instance of SHA256, SHA384 or SHA512 should be disposed after use since it implements IDisposable. E.g., in the documentation they wrap the SHA-related code with using statement. I guess the same should be done here:

private static byte[] EnhancedHash(byte[] inputBytes, HashType hashType)
{
    switch (hashType)
    {
        case HashType.SHA256:
            inputBytes = SafeUTF8.GetBytes(Convert.ToBase64String(SHA256.Create().ComputeHash(inputBytes)));
            break;
        case HashType.SHA384:
            inputBytes = SafeUTF8.GetBytes(Convert.ToBase64String(SHA384.Create().ComputeHash(inputBytes)));
            break;
        case HashType.SHA512:
            inputBytes = SafeUTF8.GetBytes(Convert.ToBase64String(SHA512.Create().ComputeHash(inputBytes)));
            break;
        case HashType.Legacy384:
            inputBytes = SHA384.Create().ComputeHash(inputBytes);
            break;
        default:
            throw new ArgumentOutOfRangeException(nameof(hashType), hashType, null);
    }

    return inputBytes;
}

IDisposable in .net is a skip fire of legacy (hence the old HTTPClient is disposable, lets make a new one for every call... what do you mean that wasn't how its supposed to be used; endless battle)

The calls in SHAXXX (lets say 512 here) to dispose just call the base hashingalgorithm method as its part of the framework expectations https://github.com/dotnet/runtime/blob/master/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SHA512.cs#L56-L57
Which ultimately just calls dispose on base which has a nice note.
https://github.com/dotnet/runtime/blob/master/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/HashAlgorithm.cs#L186-L188

                // Although we don't have any resources to dispose at this level,
                // we need to continue to throw ObjectDisposedExceptions from CalculateHash
                // for compatibility with the .NET Framework.

GC.Suppress finalize is called with the current hash algo object which is only done to suppress pointless GC https://docs.microsoft.com/en-us/dotnet/api/system.gc.suppressfinalize?view=netframework-4.8

So the value of making the dispose wrapper / calls appears limited/worthless

The calls in SHAXXX (lets say 512 here) to dispose just call the base hashingalgorithm method as its part of the framework expectations

Well, first of, I wouldn't be so sure about that. From the way the value of _hashProvider is set, the _hashProvider.Dispose() seems to be doing some job, and that job is OS-specific (Windows, Unix, OSX).

And second, even if we agree that there's no harm in not disposing a SHA-2 instance currently, there's no guarantee that it's going to be like that in the future. So, I guess it's better to wrap all SHA instances in using statements just to be on the safe side. Since it doesn't cost anything and makes the code more robust, I see no reason not doing it.

HashProviderDispenser just creates a hash provider via the systems native provider. I had totally forgotten how god awful it is to have the core clr open on my machine.
And yeah the SafeEvpMdCtxHandle contexts are disposed of in the Windows/Unix ones, so there's that. Bit nightmare fuel tracing if the release is called in the dispose so 🤷‍♂️ may as well go with it

            switch (hashType)
            {
                case HashType.SHA256:
                    using (var sha = SHA256.Create())
                        inputBytes = SafeUTF8.GetBytes(Convert.ToBase64String(sha.ComputeHash(inputBytes)) + (bcryptMinorRevision >= 'a' ? Nul : EmptyString));
                    break;
                case HashType.SHA384:
                    using (var sha = SHA384.Create())
                        inputBytes = SafeUTF8.GetBytes(Convert.ToBase64String(sha.ComputeHash(inputBytes)) + (bcryptMinorRevision >= 'a' ? Nul : EmptyString));
                    break;
                case HashType.SHA512:
                    using (var sha = SHA512.Create())
                        inputBytes = SafeUTF8.GetBytes(Convert.ToBase64String(sha.ComputeHash(inputBytes)) + (bcryptMinorRevision >= 'a' ? Nul : EmptyString));
                    break;
                default:
                    throw new ArgumentOutOfRangeException(nameof(hashType), hashType, null);
            }

Tis in master