114 lines
3.9 KiB
C#
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}";
|
|
}
|
|
}
|