Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion IT.Hashing.Gost.Native/HashAlgorithms.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using IT.Hashing.Gost.Native.Internal;
using System;
using System.Buffers;
using System.Buffers.Text;
using System.Diagnostics;

namespace IT.Hashing.Gost.Native;

Expand Down Expand Up @@ -54,7 +57,9 @@ private abstract class Resetable_Gost3411 : IHashAlgorithm
{
private SafeHashHandleImpl _handle;

public virtual int Size => _handle.Size;
public abstract int Size { get; }

public abstract int SizeInBase64 { get; }

protected Resetable_Gost3411()
{
Expand Down Expand Up @@ -82,13 +87,32 @@ public void Reset()
public bool TryGetHash(Span<byte> hash, out int length)
=> _handle.TryGetHash(hash, out length);

public bool TryGetHashInBase64(Span<byte> destination, out int length)
{
length = SizeInBase64;
if (destination.Length < length)
return false;

var isTrue = _handle.TryGetHash(destination, out var written);
Debug.Assert(isTrue);
Debug.Assert(written == Size);

var status = Base64.EncodeToUtf8InPlace(destination, written, out written);
Debug.Assert(status == OperationStatus.Done);
Debug.Assert(written == length);

return true;
}

protected abstract SafeHashHandleImpl CreateHandle();
}

private class Resetable_Gost3411_94 : Resetable_Gost3411
{
public override int Size => 32;

public override int SizeInBase64 => 44;

protected override SafeHashHandleImpl CreateHandle() =>
CryptoApiHelper.CreateHash_3411_94(_provider!);
}
Expand All @@ -97,6 +121,8 @@ private class Resetable_Gost3411_2012_256 : Resetable_Gost3411
{
public override int Size => 32;

public override int SizeInBase64 => 44;

protected override SafeHashHandleImpl CreateHandle() =>
CryptoApiHelper.CreateHash_3411_2012_256(_provider!);
}
Expand All @@ -105,6 +131,8 @@ private class Resetable_Gost3411_2012_512 : Resetable_Gost3411
{
public override int Size => 64;

public override int SizeInBase64 => 88;

protected override SafeHashHandleImpl CreateHandle() =>
CryptoApiHelper.CreateHash_3411_2012_512(_provider!);
}
Expand Down
2 changes: 1 addition & 1 deletion IT.Hashing.Gost.Native/IT.Hashing.Gost.Native.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<Authors>Ivan Tikhonov</Authors>
<Copyright>Ivan Tikhonov © 2026</Copyright>
<Version>2.0.2</Version>
<Version>2.0.3</Version>
<PackageTags>Gost Hashing Native CryptoPro VipNet</PackageTags>
<PackageReadmeFile>Readme.md</PackageReadmeFile>
<PackageIcon>Icon.png</PackageIcon>
Expand Down
28 changes: 28 additions & 0 deletions IT.Hashing.Gost.Native/Internal/SafeHashHandleImpl.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.Buffers;
using System.Buffers.Text;
using System.Security;

namespace IT.Hashing.Gost.Native.Internal;
Expand All @@ -14,6 +16,8 @@ internal class SafeHashHandleImpl : SafeHandleZeroOrMinusOneIsInvalid, IHashAlgo

public int Size => CryptoApiHelper.GetEndHashDataLength(this);

public int SizeInBase64 => Base64.GetMaxEncodedToUtf8Length(Size);

public SafeHashHandleImpl() : base(true)
{
}
Expand Down Expand Up @@ -63,6 +67,30 @@ public bool TryGetHash(Span<byte> hash, out int length)
return CryptoApiHelper.TryGetEndHashData(this, hash, out length);
}

[SecurityCritical]
public bool TryGetHashInBase64(Span<byte> hash, out int length)
{
if (!CryptoApiHelper.TryGetEndHashData(this, hash, out length))
{
length = Base64.GetMaxEncodedToUtf8Length(length);
return false;
}

var status = Base64.EncodeToUtf8InPlace(hash, length, out var written);
if (status != OperationStatus.Done)
{
if (status == OperationStatus.DestinationTooSmall)
{
hash.Slice(0, length).Clear();
length = Base64.GetMaxEncodedToUtf8Length(length);
return false;
}
throw new InvalidOperationException($"Status is {status}");
}
length = written;
return true;
}

public void Reset()
{
throw new NotImplementedException();
Expand Down
30 changes: 29 additions & 1 deletion IT.Hashing.Gost/Gost3411_2012_256.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using IT.Hashing.Gost.Internal;
using System;
using System.Buffers;
using System.Buffers.Text;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace IT.Hashing.Gost;
Expand All @@ -10,6 +13,8 @@ public class Gost3411_2012_256 : Gost3411_2012_512

public override int Size => 32;

public override int SizeInBase64 => 44;

/// <exception cref="ObjectDisposedException">Thrown when the instance has been disposed.</exception>
[MethodImpl(MethodImplOptionsEx.OptimizedLoop)]
public override bool TryGetHash(Span<byte> destination, out int length)
Expand All @@ -18,7 +23,30 @@ public override bool TryGetHash(Span<byte> destination, out int length)
if (destination.Length < length)
return false;

BinarySpans.WriteUInt64LittleEndian(HashFinal().AsSpan(HalfBlockSizeWords, HalfBlockSizeWords), destination);
Span<ulong> hash = stackalloc ulong[BlockSizeWords];
HashFinal(hash);

BinarySpans.WriteUInt64LittleEndian(hash.Slice(HalfBlockSizeWords, HalfBlockSizeWords), destination);

return true;
}

/// <exception cref="ObjectDisposedException">Thrown when the instance has been disposed.</exception>
[MethodImpl(MethodImplOptionsEx.OptimizedLoop)]
public override bool TryGetHashInBase64(Span<byte> destination, out int length)
{
length = 44;
if (destination.Length < length)
return false;

Span<ulong> hash = stackalloc ulong[BlockSizeWords];
HashFinal(hash);

BinarySpans.WriteUInt64LittleEndian(hash.Slice(HalfBlockSizeWords, HalfBlockSizeWords), destination);

var status = Base64.EncodeToUtf8InPlace(destination, 32, out var written);
Debug.Assert(status == OperationStatus.Done);
Debug.Assert(written == length);

return true;
}
Expand Down
74 changes: 70 additions & 4 deletions IT.Hashing.Gost/Gost3411_2012_512.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

using IT.Hashing.Gost.Internal;
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Diagnostics;
using System.Buffers.Text;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -196,6 +199,8 @@ static Gost3411_2012_512()

public virtual int Size => 64;

public virtual int SizeInBase64 => 88;

public Gost3411_2012_512()
{
_h = new ulong[BlockSizeWords];
Expand Down Expand Up @@ -270,7 +275,30 @@ public virtual bool TryGetHash(Span<byte> destination, out int length)
if (destination.Length < length)
return false;

BinarySpans.WriteUInt64LittleEndian(HashFinal(), destination);
Span<ulong> hash = stackalloc ulong[BlockSizeWords];
HashFinal(hash);
BinarySpans.WriteUInt64LittleEndian(hash, destination);

return true;
}

/// <exception cref="ObjectDisposedException">Thrown when the instance has been disposed.</exception>
[MethodImpl(MethodImplOptionsEx.OptimizedLoop)]
public virtual bool TryGetHashInBase64(Span<byte> destination, out int length)
{
if (_disposed) throw new ObjectDisposedException(GetType().FullName);

length = 88;
if (destination.Length < length)
return false;

Span<ulong> hash = stackalloc ulong[BlockSizeWords];
HashFinal(hash);
BinarySpans.WriteUInt64LittleEndian(hash, destination);

var status = Base64.EncodeToUtf8InPlace(destination, 64, out var written);
Debug.Assert(status == OperationStatus.Done);
Debug.Assert(written == length);

return true;
}
Expand All @@ -287,6 +315,44 @@ public void Dispose()
}
}

[MethodImpl(MethodImplOptionsEx.OptimizedLoop)]
protected void HashFinal(Span<ulong> h)
{
if (_disposed) throw new ObjectDisposedException(GetType().FullName);

// Pad the final block: m || 1 || 0...0
Span<byte> paddedBlock = stackalloc byte[64];
paddedBlock.Clear();
_buffer.AsSpan(0, _bufferLength).CopyTo(paddedBlock);
paddedBlock[_bufferLength] = 0x01;

// Convert padded block to ulongs
Span<ulong> p = stackalloc ulong[BlockSizeWords];
BinarySpans.ReadUInt64LittleEndian(paddedBlock, p);

Debug.Assert(h.Length == _h.Length);
_h.CopyTo(h);

Span<ulong> n = stackalloc ulong[BlockSizeWords];
_n.CopyTo(n);

Span<ulong> sigma = stackalloc ulong[BlockSizeWords];
_sigma.CopyTo(sigma);

// Stage 3: Process padded block
GN(h, n, p);

// Update N with the bit count of the final partial block
AddModulus512(n, _bufferLength * 8);

// Update Sigma with padded block
AddBlock512(sigma, p);

// Stage 4: Final compressions with zero
GN(h, _zeroArray, n);
GN(h, _zeroArray, sigma);
}

[MethodImpl(MethodImplOptionsEx.OptimizedLoop)]
protected ulong[] HashFinal()
{
Expand Down Expand Up @@ -421,7 +487,7 @@ private static void E(ReadOnlySpan<ulong> k, ReadOnlySpan<ulong> m, ref Vector51
/// Uses Miyaguchi-Preneel construction.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void GN(ulong[] h, ReadOnlySpan<ulong> n, ReadOnlySpan<ulong> m)
private static void GN(Span<ulong> h, ReadOnlySpan<ulong> n, ReadOnlySpan<ulong> m)
{
Span<ulong> k = stackalloc ulong[BlockSizeWords];
Span<ulong> t = stackalloc ulong[BlockSizeWords];
Expand Down Expand Up @@ -542,7 +608,7 @@ private static void ApplyLPS(Span<byte> data)
/// Add a number of bits to the 512-bit counter (little-endian arithmetic).
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void AddModulus512(ulong[] counter, int bits)
private static void AddModulus512(Span<ulong> counter, int bits)
{
unchecked
{
Expand Down Expand Up @@ -583,7 +649,7 @@ private static void AddModulus512(ulong[] counter, int bits)
/// Add two 512-bit blocks as little-endian integers modulo 2^512.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void AddBlock512(ulong[] a, ReadOnlySpan<ulong> b)
private static void AddBlock512(Span<ulong> a, ReadOnlySpan<ulong> b)
{
unchecked
{
Expand Down
4 changes: 4 additions & 0 deletions IT.Hashing.Gost/IHashAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ public interface IHashAlgorithm : IDisposable
{
int Size { get; }

int SizeInBase64 { get; }

void Append(byte value);

void Append(ReadOnlySpan<byte> span);
Expand All @@ -14,5 +16,7 @@ public interface IHashAlgorithm : IDisposable

bool TryGetHash(Span<byte> hash, out int length);

bool TryGetHashInBase64(Span<byte> hash, out int length);

void Reset();
}
2 changes: 1 addition & 1 deletion IT.Hashing.Gost/IT.Hashing.Gost.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<Authors>Ivan Tikhonov</Authors>
<Copyright>Ivan Tikhonov © 2026</Copyright>
<Version>2.0.2</Version>
<Version>2.0.3</Version>
<PackageTags>Gost Hashing Managed</PackageTags>
<PackageReadmeFile>Readme.md</PackageReadmeFile>
<PackageIcon>Icon.png</PackageIcon>
Expand Down
Loading
Loading