New hashing added instead of sha-1

This commit is contained in:
cake
2025-11-18 00:13:40 +01:00
parent bebf6c745b
commit bfaa583808
7 changed files with 196 additions and 17 deletions

View File

@@ -230,11 +230,11 @@ public sealed class FileCacheManager : IHostedService
brokenEntities.Add(fileCache); brokenEntities.Add(fileCache);
return; return;
} }
var algo = Crypto.DetectAlgo(fileCache.Hash);
string computedHash; string computedHash;
try try
{ {
computedHash = await Crypto.GetFileHashAsync(fileCache.ResolvedFilepath, token).ConfigureAwait(false); computedHash = await Crypto.ComputeFileHashAsync(fileCache.ResolvedFilepath, algo, token).ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -246,8 +246,8 @@ public sealed class FileCacheManager : IHostedService
if (!string.Equals(computedHash, fileCache.Hash, StringComparison.Ordinal)) if (!string.Equals(computedHash, fileCache.Hash, StringComparison.Ordinal))
{ {
_logger.LogInformation( _logger.LogInformation(
"Hash mismatch: {file} (got {computedHash}, expected {expected})", "Hash mismatch: {file} (got {computedHash}, expected {expected} : hash {hash})",
fileCache.ResolvedFilepath, computedHash, fileCache.Hash); fileCache.ResolvedFilepath, computedHash, fileCache.Hash, algo);
brokenEntities.Add(fileCache); brokenEntities.Add(fileCache);
} }
@@ -422,12 +422,13 @@ public sealed class FileCacheManager : IHostedService
_logger.LogTrace("Updating hash for {path}", fileCache.ResolvedFilepath); _logger.LogTrace("Updating hash for {path}", fileCache.ResolvedFilepath);
var oldHash = fileCache.Hash; var oldHash = fileCache.Hash;
var prefixedPath = fileCache.PrefixedFilePath; var prefixedPath = fileCache.PrefixedFilePath;
var algo = Crypto.DetectAlgo(fileCache.ResolvedFilepath);
if (computeProperties) if (computeProperties)
{ {
var fi = new FileInfo(fileCache.ResolvedFilepath); var fi = new FileInfo(fileCache.ResolvedFilepath);
fileCache.Size = fi.Length; fileCache.Size = fi.Length;
fileCache.CompressedSize = null; fileCache.CompressedSize = null;
fileCache.Hash = Crypto.GetFileHash(fileCache.ResolvedFilepath); fileCache.Hash = Crypto.ComputeFileHash(fileCache.ResolvedFilepath, algo);
fileCache.LastModifiedDateTicks = fi.LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture); fileCache.LastModifiedDateTicks = fi.LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture);
} }
RemoveHashedFile(oldHash, prefixedPath); RemoveHashedFile(oldHash, prefixedPath);
@@ -570,7 +571,8 @@ public sealed class FileCacheManager : IHostedService
private FileCacheEntity? CreateFileCacheEntity(FileInfo fileInfo, string prefixedPath, string? hash = null) private FileCacheEntity? CreateFileCacheEntity(FileInfo fileInfo, string prefixedPath, string? hash = null)
{ {
hash ??= Crypto.GetFileHash(fileInfo.FullName); var algo = Crypto.DetectAlgo(Path.GetFileNameWithoutExtension(fileInfo.Name));
hash ??= Crypto.ComputeFileHash(fileInfo.FullName, algo);
var entity = new FileCacheEntity(hash, prefixedPath, fileInfo.LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture), fileInfo.Length); var entity = new FileCacheEntity(hash, prefixedPath, fileInfo.LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture), fileInfo.Length);
entity = ReplacePathPrefixes(entity); entity = ReplacePathPrefixes(entity);
AddHashedFile(entity); AddHashedFile(entity);

View File

@@ -27,6 +27,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Blake3" Version="2.0.0" />
<PackageReference Include="Downloader" Version="4.0.3" /> <PackageReference Include="Downloader" Version="4.0.3" />
<PackageReference Include="K4os.Compression.LZ4.Legacy" Version="1.3.8" /> <PackageReference Include="K4os.Compression.LZ4.Legacy" Version="1.3.8" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.212"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.212">

View File

@@ -68,7 +68,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
try try
{ {
var cid = await _dalamudUtil.GetCIDAsync().ConfigureAwait(false); var cid = await _dalamudUtil.GetCIDAsync().ConfigureAwait(false);
return cid.ToString().GetHash256(); return cid.ToString().GetBlake3Hash();
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -143,7 +143,7 @@ internal class ContextMenuService : IHostedService
return; return;
} }
var senderCid = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256(); var senderCid = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetBlake3Hash();
var receiverCid = DalamudUtilService.GetHashedCIDFromPlayerPointer(targetData.Address); var receiverCid = DalamudUtilService.GetHashedCIDFromPlayerPointer(targetData.Address);
_logger.LogInformation("Sending pair request: sender {SenderCid}, receiver {ReceiverCid}", senderCid, receiverCid); _logger.LogInformation("Sending pair request: sender {SenderCid}, receiver {ReceiverCid}", senderCid, receiverCid);

View File

@@ -347,7 +347,7 @@ public sealed class DtrEntry : IDisposable, IHostedService
try try
{ {
var cid = _dalamudUtilService.GetCIDAsync().GetAwaiter().GetResult(); var cid = _dalamudUtilService.GetCIDAsync().GetAwaiter().GetResult();
var hashedCid = cid.ToString().GetHash256(); var hashedCid = cid.ToString().GetBlake3Hash();
_localHashedCid = hashedCid; _localHashedCid = hashedCid;
_localHashedCidFetchedAt = now; _localHashedCidFetchedAt = now;
return hashedCid; return hashedCid;

View File

@@ -1,4 +1,5 @@
using System.Security.Cryptography; using Blake3;
using System.Security.Cryptography;
using System.Text; using System.Text;
namespace LightlessSync.Utils; namespace LightlessSync.Utils;
@@ -9,20 +10,93 @@ public static class Crypto
private const int _bufferSize = 65536; private const int _bufferSize = 65536;
#pragma warning disable SYSLIB0021 // Type or member is obsolete #pragma warning disable SYSLIB0021 // Type or member is obsolete
// SHA256 hash caches
private static readonly Dictionary<(string, ushort), string> _hashListPlayersSHA256 = []; private static readonly Dictionary<(string, ushort), string> _hashListPlayersSHA256 = [];
private static readonly Dictionary<string, string> _hashListSHA256 = new(StringComparer.Ordinal); private static readonly Dictionary<string, string> _hashListSHA256 = new(StringComparer.Ordinal);
private static readonly SHA256CryptoServiceProvider _sha256CryptoProvider = new(); private static readonly SHA256CryptoServiceProvider _sha256CryptoProvider = new();
public static string GetFileHash(this string filePath) // BLAKE3 hash caches
private static readonly Dictionary<(string, ushort), string> _hashListPlayersBlake3 = [];
private static readonly Dictionary<string, string> _hashListBlake3 = new(StringComparer.Ordinal);
/// <summary>
/// Supports Blake3 or SHA1 for file transfers, no SHA256 supported on it
/// </summary>
public enum HashAlgo
{
Blake3,
Sha1
}
/// <summary>
/// Detects which algo is being used for the file
/// </summary>
/// <param name="hashHex">Hashed string</param>
/// <returns>HashAlgo</returns>
public static HashAlgo DetectAlgo(string hashHex)
{
if (hashHex.Length == 40)
return HashAlgo.Sha1;
return HashAlgo.Blake3;
}
#region File Hashing
/// <summary>
/// Compute file hash with given algorithm, supports BLAKE3 and Sha1 for file hashing
/// </summary>
/// <param name="filePath">Filepath for the hashing</param>
/// <param name="algo">BLAKE3 or Sha1</param>
/// <returns>Hashed file hash</returns>
/// <exception cref="ArgumentOutOfRangeException">Not a valid HashAlgo or Filepath</exception>
public static string ComputeFileHash(string filePath, HashAlgo algo)
{
return algo switch
{
HashAlgo.Blake3 => ComputeFileHashBlake3(filePath),
HashAlgo.Sha1 => ComputeFileHashSha1(filePath),
_ => throw new ArgumentOutOfRangeException(nameof(algo), algo, null)
};
}
/// <summary>
/// Compute file hash asynchronously with given algorithm, supports BLAKE3 and SHA1 for file hashing
/// </summary>
/// <param name="filePath">Filepath for the hashing</param>
/// <param name="algo">BLAKE3 or Sha1</param>
/// <returns>Hashed file hash</returns>
/// <exception cref="ArgumentOutOfRangeException">Not a valid HashAlgo or Filepath</exception>
public static async Task<string> ComputeFileHashAsync(string filePath, HashAlgo algo, CancellationToken cancellationToken = default)
{
return algo switch
{
HashAlgo.Blake3 => await ComputeFileHashBlake3Async(filePath, cancellationToken).ConfigureAwait(false),
HashAlgo.Sha1 => await ComputeFileHashSha1Async(filePath, cancellationToken).ConfigureAwait(false),
_ => throw new ArgumentOutOfRangeException(nameof(algo), algo, message: null)
};
}
/// <summary>
/// Computes an file hash with SHA1
/// </summary>
/// <param name="filePath">Filepath that has to be computed</param>
/// <returns>Hashed file in hex string</returns>
private static string ComputeFileHashSha1(string filePath)
{ {
using var stream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); using var stream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
using var sha1 = SHA1.Create(); using var sha1 = SHA1.Create();
var hash = sha1.ComputeHash(stream); var hash = sha1.ComputeHash(stream);
return Convert.ToHexString(hash); return Convert.ToHexString(hash);
} }
public static async Task<string> GetFileHashAsync(string filePath, CancellationToken cancellationToken = default) /// <summary>
/// Computes an file hash with SHA1 asynchronously
/// </summary>
/// <param name="filePath">Filepath that has to be computed</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Hashed file in hex string hashed in SHA1</returns>
private static async Task<string> ComputeFileHashSha1Async(string filePath, CancellationToken cancellationToken)
{ {
var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, bufferSize: _bufferSize, options: FileOptions.Asynchronous); var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, bufferSize: _bufferSize, options: FileOptions.Asynchronous);
await using (stream.ConfigureAwait(false)) await using (stream.ConfigureAwait(false))
@@ -31,18 +105,107 @@ public static class Crypto
var buffer = new byte[8192]; var buffer = new byte[8192];
int bytesRead; int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken).ConfigureAwait(false)) > 0) while ((bytesRead = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken).ConfigureAwait(false)) > 0)
{ {
sha1.TransformBlock(buffer, 0, bytesRead, outputBuffer: null, 0); sha1.TransformBlock(buffer, 0, bytesRead, outputBuffer: null, 0);
} }
sha1.TransformFinalBlock([], 0, 0); sha1.TransformFinalBlock([], 0, 0);
return Convert.ToHexString(sha1.Hash!); return Convert.ToHexString(sha1.Hash!);
} }
} }
/// <summary>
/// Computes an file hash with Blake3
/// </summary>
/// <param name="filePath">Filepath that has to be computed</param>
/// <returns>Hashed file in hex string hashed in Blake3</returns>
private static string ComputeFileHashBlake3(string filePath)
{
using var stream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
using var hasher = Hasher.New();
var buffer = new byte[_bufferSize];
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
{
hasher.Update(buffer.AsSpan(0, bytesRead));
}
var hash = hasher.Finalize();
return hash.ToString();
}
/// <summary>
/// Computes an file hash with Blake3 asynchronously
/// </summary>
/// <param name="filePath">Filepath that has to be computed</param>
/// <returns>Hashed file in hex string hashed in Blake3</returns>
private static async Task<string> ComputeFileHashBlake3Async(string filePath, CancellationToken cancellationToken)
{
var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, bufferSize: _bufferSize, options: FileOptions.Asynchronous);
await using (stream.ConfigureAwait(false))
{
using var hasher = Hasher.New();
var buffer = new byte[8192];
int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken).ConfigureAwait(false)) > 0)
{
hasher.Update(buffer.AsSpan(0, bytesRead));
}
var hash = hasher.Finalize();
return hash.ToString();
}
}
#endregion
#region String hashing
public static string GetBlake3Hash(this (string, ushort) playerToHash)
{
if (_hashListPlayersBlake3.TryGetValue(playerToHash, out var hash))
return hash;
var toHash = playerToHash.Item1 + playerToHash.Item2.ToString();
hash = ComputeBlake3Hex(toHash);
_hashListPlayersBlake3[playerToHash] = hash;
return hash;
}
/// <summary>
/// Computes or gets an Blake3 hash(ed) string.
/// </summary>
/// <param name="stringToHash">String that needs to be hashsed</param>
/// <returns>Hashed string</returns>
public static string GetBlake3Hash(this string stringToHash)
{
return GetOrComputeBlake3(stringToHash);
}
private static string GetOrComputeBlake3(string stringToCompute)
{
if (_hashListBlake3.TryGetValue(stringToCompute, out var hash))
return hash;
hash = ComputeBlake3Hex(stringToCompute);
_hashListBlake3[stringToCompute] = hash;
return hash;
}
private static string ComputeBlake3Hex(string input)
{
var bytes = Encoding.UTF8.GetBytes(input);
var hash = Hasher.Hash(bytes);
return Convert.ToHexString(hash.AsSpan());
}
public static string GetHash256(this (string, ushort) playerToHash) public static string GetHash256(this (string, ushort) playerToHash)
{ {
if (_hashListPlayersSHA256.TryGetValue(playerToHash, out var hash)) if (_hashListPlayersSHA256.TryGetValue(playerToHash, out var hash))
@@ -52,6 +215,11 @@ public static class Crypto
Convert.ToHexString(_sha256CryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(playerToHash.Item1 + playerToHash.Item2.ToString()))); Convert.ToHexString(_sha256CryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(playerToHash.Item1 + playerToHash.Item2.ToString())));
} }
/// <summary>
/// Computes or gets an SHA256 hash(ed) string.
/// </summary>
/// <param name="stringToHash">String that needs to be hashsed</param>
/// <returns>Hashed string</returns>
public static string GetHash256(this string stringToHash) public static string GetHash256(this string stringToHash)
{ {
return GetOrComputeHashSHA256(stringToHash); return GetOrComputeHashSHA256(stringToHash);
@@ -64,6 +232,8 @@ public static class Crypto
return _hashListSHA256[stringToCompute] = return _hashListSHA256[stringToCompute] =
Convert.ToHexString(_sha256CryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToCompute))); Convert.ToHexString(_sha256CryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToCompute)));
} }
#endregion
#pragma warning restore SYSLIB0021 // Type or member is obsolete #pragma warning restore SYSLIB0021 // Type or member is obsolete
} }

View File

@@ -2,6 +2,12 @@
"version": 1, "version": 1,
"dependencies": { "dependencies": {
"net10.0-windows7.0": { "net10.0-windows7.0": {
"Blake3": {
"type": "Direct",
"requested": "[2.0.0, )",
"resolved": "2.0.0",
"contentHash": "v447kojeuNYSY5dvtVGG2bv1+M3vOWJXcrYWwXho/2uUpuwK6qPeu5WSMlqLm4VRJu96kysVO11La0zN3dLAuQ=="
},
"DalamudPackager": { "DalamudPackager": {
"type": "Direct", "type": "Direct",
"requested": "[13.1.0, )", "requested": "[13.1.0, )",