.NET 6升级后MD5生成ServicePartitionKey偶发WindowsCryptographicException
问题背景
我们使用MD5哈希算法生成PartitionKey用于路由至有状态服务,该方案长期运行无异常,但升级至.NET 6后,日志中偶尔出现WindowsCryptographicException错误(错误码0xc1000008)。
涉及组件
- 有状态服务
- PartitionKey生成器
重现代码
private readonly MD5 _md5 = MD5.Create(); public long GetPartitionKey(string Id) { var bytesToHash = Encoding.ASCII.GetBytes($"{Id}"); var hash = _md5.ComputeHash(bytesToHash); var key = BitConverter.ToInt64(hash, 0); return key; }
预期与实际行为
- 预期行为:无错误返回PartitionKey
- 实际行为:偶发异常,详情如下:
{"Type":"WindowsCryptographicException","Message":"Unknown error (0xc1000008)","StackTrace":" at Internal.Cryptography.HashProviderCng.AppendHashData(ReadOnlySpan`1 source) at Internal.Cryptography.HashProvider.AppendHashData(Byte[] data, Int32 offset, Int32 count) at System.Security.Cryptography.HashAlgorithm.ComputeHash(Byte[] buffer)
环境信息
- Service Fabric运行时版本:8.0
- 环境:Azure Linux
- 回归版本:升级至.NET 6后
问题原因与解决方案
问题原因
HashAlgorithm(包括MD5)的实例方法不是线程安全的,当多个线程并发调用共享的MD5实例的ComputeHash方法时,会导致内部加密状态混乱,进而触发WindowsCryptographicException。.NET 6对加密算法的底层实现做了调整,使得原本隐藏的线程安全问题暴露了出来。
解决方案
有两种可行的修复方式:
方式1:使用线程安全的静态HashData方法(推荐)
.NET 5及以上版本提供了静态的MD5.HashData方法,该方法是线程安全的,无需维护共享实例:
public long GetPartitionKey(string Id) { var bytesToHash = Encoding.ASCII.GetBytes(Id); var hash = MD5.HashData(bytesToHash); var key = BitConverter.ToInt64(hash, 0); return key; }
方式2:为共享实例添加线程锁
如果需要复用MD5实例以减少对象创建开销,需在调用ComputeHash时加锁,确保同一时间只有一个线程操作实例:
private readonly MD5 _md5 = MD5.Create(); private readonly object _lockObj = new object(); public long GetPartitionKey(string Id) { var bytesToHash = Encoding.ASCII.GetBytes(Id); byte[] hash; lock (_lockObj) { hash = _md5.ComputeHash(bytesToHash); } var key = BitConverter.ToInt64(hash, 0); return key; }
内容的提问来源于stack exchange,提问作者srkCodes1307

