Files
git.stella-ops.org/src/Excititor/StellaOps.Excititor.WebService/Services/VexHashingService.cs
2026-02-01 21:37:40 +02:00

114 lines
3.9 KiB
C#

using StellaOps.Cryptography;
using System;
using System.Security.Cryptography;
using System.Text;
namespace StellaOps.Excititor.WebService.Services;
/// <summary>
/// Service interface for hashing operations in Excititor (CRYPTO-90-001).
/// Abstracts hashing implementation to support GOST/SM algorithms via ICryptoProviderRegistry.
/// </summary>
public interface IVexHashingService
{
/// <summary>
/// Compute hash of a UTF-8 encoded string.
/// </summary>
string ComputeHash(string value, string algorithm = "sha256");
/// <summary>
/// Compute hash of raw bytes.
/// </summary>
string ComputeHash(ReadOnlySpan<byte> data, string algorithm = "sha256");
/// <summary>
/// Try to compute hash of raw bytes with stack-allocated buffer optimization.
/// </summary>
bool TryComputeHash(ReadOnlySpan<byte> data, Span<byte> destination, out int bytesWritten, string algorithm = "sha256");
/// <summary>
/// Format a hash digest with algorithm prefix.
/// </summary>
string FormatDigest(string algorithm, ReadOnlySpan<byte> digest);
}
/// <summary>
/// Default implementation of <see cref="IVexHashingService"/> that uses ICryptoProviderRegistry
/// when available, falling back to System.Security.Cryptography for SHA-256.
/// </summary>
public sealed class VexHashingService : IVexHashingService
{
private readonly ICryptoProviderRegistry? _registry;
public VexHashingService(ICryptoProviderRegistry? registry = null)
{
_registry = registry;
}
public string ComputeHash(string value, string algorithm = "sha256")
{
if (string.IsNullOrEmpty(value))
{
return string.Empty;
}
var bytes = Encoding.UTF8.GetBytes(value);
return ComputeHash(bytes, algorithm);
}
public string ComputeHash(ReadOnlySpan<byte> data, string algorithm = "sha256")
{
Span<byte> buffer = stackalloc byte[64]; // Large enough for SHA-512 and GOST
if (!TryComputeHash(data, buffer, out var written, algorithm))
{
throw new InvalidOperationException($"Failed to compute {algorithm} hash.");
}
return FormatDigest(algorithm, buffer[..written]);
}
public bool TryComputeHash(ReadOnlySpan<byte> data, Span<byte> destination, out int bytesWritten, string algorithm = "sha256")
{
bytesWritten = 0;
// Try to use crypto provider registry first for pluggable algorithms
if (_registry is not null)
{
try
{
var resolution = _registry.ResolveHasher(algorithm);
var hasher = resolution.Hasher;
var result = hasher.ComputeHash(data);
if (result.Length <= destination.Length)
{
result.CopyTo(destination);
bytesWritten = result.Length;
return true;
}
}
catch
{
// Fall through to built-in implementation
}
}
// Fall back to System.Security.Cryptography for standard algorithms
var normalizedAlgorithm = algorithm.ToLowerInvariant().Replace("-", string.Empty);
return normalizedAlgorithm switch
{
"sha256" => SHA256.TryHashData(data, destination, out bytesWritten),
"sha384" => SHA384.TryHashData(data, destination, out bytesWritten),
"sha512" => SHA512.TryHashData(data, destination, out bytesWritten),
_ => throw new NotSupportedException($"Unsupported hash algorithm: {algorithm}")
};
}
public string FormatDigest(string algorithm, ReadOnlySpan<byte> digest)
{
var normalizedAlgorithm = algorithm.ToLowerInvariant().Replace("-", string.Empty);
var hexDigest = Convert.ToHexString(digest).ToLowerInvariant();
return $"{normalizedAlgorithm}:{hexDigest}";
}
}