Après les mises à jour cumulatives Windows de mai‑juin 2024, des applications .NET et des composants natifs se mettent à échouer avec CryptographicException: Unknown error -1073741816 lors d’un hachage SHA‑256/512. Voici la cause, des correctifs pérennes et des exemples de code prêts à l’emploi.
Erreur System.Security.Cryptography.CryptographicException: Unknown error "-1073741816" après mise à jour Windows
Vue d’ensemble de la question
- Après l’installation de mises à jour cumulatives Windows de mai‑juin 2024 (notamment KB5037771, KB5037782, KB5038282, KB5035432 et correctifs mensuels ultérieurs), de nombreuses applications .NET 4.x et composants natifs (SharePoint, Siemens TIA Portal, Windows Admin Center, solutions SFTP, etc.) commencent à lever l’exception ci‑dessus lors du calcul d’un hachage SHA‑256/SHA‑512.
 - Le même code fonctionnait auparavant : l’erreur n’apparaît qu’après les mises à jour. Le message « Unknown error » masque le code NTSTATUS 
0xC0000008(‑1073741816), c’est‑à‑dire STATUS_INVALID_HANDLE. - Le problème se déclenche souvent après un certain temps de fonctionnement : une fois qu’il survient, toutes les opérations de hachage continuent d’échouer jusqu’au redémarrage du processus.
 
Symptômes typiques et signaux faibles
- Une application .NET qui réalisait des hachages en parallèle devient instable après plusieurs minutes/heures de charge : l’exception apparaît de façon soudaine puis persiste.
 - Des services IIS retournent des erreurs 500/502 lorsque des middleware ou handlers calculent un hash (authentification, signature de payload, vérification d’intégrité).
 - Des outils d’administration, portails web ou SFTP échouent à des étapes qui incluent du hachage ou de la signature (vérification de packages, import/export, contrôle d’intégrité de fichiers).
 - Les journaux .NET ou Windows ne montrent que Unknown error avec le code négatif 
-1073741816. 
Cause racine identifiée
- Non‑respect du contrat de thread‑safety des classes 
HashAlgorithm/SHAxxx:- Le wrapper CNG (
SHA256Cng,SHA512Cng, etc.) n’a jamais été thread‑safe. - Des appels parallèles sur la même instance (ou un double appel à 
TransformFinalBlock) corrompent l’état natif ; CNG/BCrypt détecte désormais un handle invalide et jette l’exception plutôt que de retourner un résultat silencieusement corrompu. 
 - Le wrapper CNG (
 - Renforcement du comportement des bibliothèques CNG livrées avec les mises à jour : la validation des handles et la détection des mauvaises séquences d’appels ont été durcies. Le même code applicatif, incorrect mais toléré auparavant, échoue désormais explicitement.
 
Ce qui se passe sous le capot
Les implémentations Windows de SHA256/SHA512 reposent sur CNG (bcrypt.dll). Chaque instance .NET encapsule un BCRYPT_HASH_HANDLE. Lorsqu’une instance est utilisée concurremment, ou qu’on appelle deux fois TransformFinalBlock sur la même instance, la séquence natale (BCryptCreateHash → BCryptHashData → BCryptFinishHash) est violée ; CNG invalide le handle et remonte STATUS_INVALID_HANDLE (0xC0000008 → -1073741816 côté .NET).
Comment reproduire rapidement l’erreur (à but de diagnostic)
Ne pas utiliser ce modèle en production ; il illustre l’anti‑pattern : partage d’une même instance SHA256 entre threads.
// Anti‑pattern volontaire (repro)
using System.Security.Cryptography;
using System.Threading.Tasks;
var sha = SHA256.Create(); // UNE SEULE instance partagée
Parallel.For(0, 1000, i =>
{
var data = BitConverter.GetBytes(i);
// Appels concurrents sur la même instance = état corrompu et, après MAJ 2024, CryptographicException
var hash = sha.ComputeHash(data);
}); 
Exemples de correctifs côté .NET
Créer une instance par appel (recommandé)
byte[] HashOnce(ReadOnlySpan<byte> data)
{
    using var sha = SHA256.Create();
    return sha.ComputeHash(data.ToArray());
}
API modernes en .NET 5+ : HashData
byte[] HashOnceFast(ReadOnlySpan<byte> data)
{
    return SHA256.HashData(data); // crée et gère l'instance en interne
}
Flux incrémental sans erreur de finalisation
byte[] HashStream(Stream input)
{
    using var sha = SHA256.Create();
    var buffer = new byte[81920];
    int read;
    while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
    {
        sha.TransformBlock(buffer, 0, read, null, 0);
    }
    sha.TransformFinalBlock(Array.Empty<byte>(), 0, 0); // UNE seule fois
    return sha.Hash!;
}
Synchroniser l’accès si un singleton doit rester (option tolérée)
private static readonly object ShaLock = new();
private static readonly SHA256 ShaSingleton = SHA256.Create();
byte[] HashWithLock(byte[] data)
{
lock (ShaLock)
{
return ShaSingleton.ComputeHash(data);
}
} 
IncrementalHash pour les pipelines
byte[] HashWithIncremental(ReadOnlySpan<byte> data)
{
    using var ih = IncrementalHash.CreateHash(HashAlgorithmName.SHA256);
    ih.AppendData(data);
    return ih.GetHashAndReset(); // finalise une seule fois
}
Mise en pool d’instances pour charges parallèles
Évitez un verrou global en empruntant une instance par requête ; retournez‑la ensuite au pool. Ne partagez jamais la même instance entre deux threads au même moment.
using System.Collections.Concurrent;
using System.Security.Cryptography;
public static class Sha256Pool
{
private static readonly ConcurrentBag _pool = new();
```
public static byte[] ComputeHashPooled(ReadOnlySpan<byte> data)
{
    if (!_pool.TryTake(out var sha))
        sha = SHA256.Create();
    try
    {
        return sha.ComputeHash(data.ToArray());
    }
    finally
    {
        _pool.Add(sha); // l'instance redevient disponible pour un AUTRE thread
    }
}
```
} 
Correctifs côté natif C/C++ (CNG)
En natif, créez un hash handle par appel ou par thread ; ne réutilisez jamais le même BCRYPT_HASH_HANDLE en parallèle, et appelez BCryptFinishHash une seule fois avant BCryptDestroyHash.
NTSTATUS HashDataSha256(_In_reads_bytes_(len) const BYTE* data, ULONG len, _Out_writes_bytes_(32) BYTE* out)
{
    BCRYPT_ALG_HANDLE hAlg = NULL;
    BCRYPT_HASH_HANDLE hHash = NULL;
    DWORD cbHashObject = 0, cbData = 0, cbHash = 0;
    PBYTE pbHashObject = NULL;
    NTSTATUS status = 0;
```
status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA256_ALGORITHM, NULL, 0);
if (status < 0) goto Cleanup;
status = BCryptGetProperty(hAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&cbHashObject, sizeof(cbHashObject), &cbData, 0);
if (status < 0) goto Cleanup;
pbHashObject = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbHashObject);
if (!pbHashObject) { status = STATUS_NO_MEMORY; goto Cleanup; }
status = BCryptGetProperty(hAlg, BCRYPT_HASH_LENGTH, (PBYTE)&cbHash, sizeof(cbHash), &cbData, 0);
if (status < 0 || cbHash != 32) goto Cleanup;
// Handle par APPEL (pas partagé)
status = BCryptCreateHash(hAlg, &hHash, pbHashObject, cbHashObject, NULL, 0, 0);
if (status < 0) goto Cleanup;
status = BCryptHashData(hHash, (PUCHAR)data, len, 0);
if (status < 0) goto Cleanup;
status = BCryptFinishHash(hHash, out, cbHash, 0); // finalise UNE fois
```
Cleanup:
if (hHash) BCryptDestroyHash(hHash);
if (pbHashObject) HeapFree(GetProcessHeap(), 0, pbHashObject);
if (hAlg) BCryptCloseAlgorithmProvider(hAlg, 0);
return status;
} 
Traduire le code négatif en hexadécimal
// -1073741816 = 0xC0000008 (STATUS_INVALID_HANDLE)
int hr = -1073741816;
string hex = $"0x{hr:X8}"; // 0xC0000008
Solutions et contournements
| Objectif | Action recommandée | Commentaires | 
|---|---|---|
| Rendre le code sûr et pérenne | Créer une instance de SHA256/SHA512 par thread ou par appel :using var sha = SHA256.Create(); | Idéal : évite tout partage d’état. | 
Synchroniser l’accès si l’on doit conserver une instance statique :lock (shaLock) { sha.ComputeHash(buf); } | Le verrou doit englober l’appel complet, y compris TransformFinalBlock. | |
Ne jamais appeler TransformFinalBlock plus d’une fois sur la même instance. | Problème souvent rencontré dans les flux incrémentaux. | |
| Limiter l’impact immédiat | Redémarrer l’application/service lorsque l’erreur apparaît. | Remède temporaire. | 
| Installer les correctifs applicatifs fournis par l’éditeur tiers (ex. Siemens). | Microsoft ne peut pas corriger le code tiers. | |
| Revenir à l’ancien comportement (non recommandé en production) | Désinstaller les KB incriminés ou appliquer un GPO de blocage. | Élimine l’exception… mais réintroduit le risque de hachage incorrect et expose à d’autres failles corrigées par les mises à jour. | 
Informations complémentaires utiles
- Les implémentations gérées pures (
SHA256Managed,SHA512Managed, ou la classeIncrementalHash) sont intrinsèquement sûres si vous créez une instance par appel ; elles peuvent servir de remplacement si la performance CNG n’est pas critique. - Le code d’erreur négatif 
-1073741816correspond à l’hexadécimal0xC0000008; connaître cette correspondance facilite le diagnostic dans les journaux. - Sur des serveurs fortement parallèles, la mise en pool d’instances (via 
ConcurrentBag<T>+SHA256.Create()par requête) offre de meilleures performances qu’un verrou global. - Si vous exploitez un framework externe qui n’est pas encore patché :
- Limitez la concurrence (ex. règles de crawl/BDC dans SharePoint, réduction du parallèle dans des jobs).
 - Surveillez la publication de correctifs propres à l’éditeur et appliquez‑les dès disponibilité.
 
 
Checklist d’audit de code
- Rechercher toute variable statique de type 
SHA256/SHA512/HashAlgorithmet vérifier si elle est utilisée depuis plusieurs threads. - Inspecter les pipelines de streaming : la paire 
TransformBlock/TransformFinalBlockne doit être appelée qu’une seule fois par instance. - Vérifier que chaque tâche asynchrone qui calcule un hash possède sa propre instance (ne jamais passer une instance à travers un 
awaitsi un autre code peut l’utiliser en parallèle). - Contrôler les bibliothèques internes : adapters, helpers de sécurité, middlewares d’authentification, calculs d’ETag, signatures de webhooks.
 - Dans le code natif, confirmer que 
BCryptCreateHashest invoqué par appel/fil (et non partagé), et queBCryptFinishHashest appelé exactement une fois avantBCryptDestroyHash. 
Stratégies de performance sûres
| Stratégie | Concurrence | Latence | Complexité | Remarques | 
|---|---|---|---|---|
| Instance par appel | Excellente | Faible à modérée | Faible | Choix par défaut recommandé ; allocation/déallocation simple. | 
Singleton + lock | Faible (sérialisation) | Peut augmenter sous charge | Faible | Acceptable pour faible trafic ; goulot d’étranglement sous forte charge. | 
| Pool d’instances | Très bonne | Faible | Moyenne | Bien adapté aux services multi‑threads ; attention à remettre l’instance dans le pool. | 
ThreadLocal<SHA256> | Bonne (usage synchrone) | Faible | Moyenne | Réservé aux scénarios purs synchrones ; éviter si le contexte peut changer de thread. | 
Detection et monitoring en production
Améliorer les logs d’exception
try
{
    using var sha = SHA256.Create();
    var hash = sha.ComputeHash(data);
}
catch (CryptographicException ex)
{
    // Ex.HResult est souvent l'NTSTATUS converti en int signé
    Console.Error.WriteLine($"Crypto error: {ex.Message}, HResult={ex.HResult} (0x{ex.HResult:X8})");
    // Attendu en cas de problème: -1073741816 (0xC0000008)
}
Recherche rapide des machines potentiellement touchées
# Rechercher l'erreur dans les journaux d'application
Get-WinEvent -LogName Application -ErrorAction SilentlyContinue |
  Where-Object { $_.Message -match "CryptographicException" -and $_.Message -match "-1073741816" } |
  Select-Object TimeCreated, ProviderName, Id, Message
# Lister la présence des KB connues
Get-HotFix | Where-Object { $_.HotFixID -match "KB5037771|KB5037782|KB5038282|KB5035432" } |
Select-Object HotFixID, InstalledOn 
Foire aux questions
Est‑ce un bug Windows ?
Plutôt un durcissement : des séquences d’utilisation incorrectes (non thread‑safe, double finalisation) étaient parfois tolérées. Les mises à jour 2024 les font échouer explicitement au lieu de produire un hash potentiellement erroné.
Pourquoi l’erreur n’apparaît‑elle qu’après un certain temps ?
Parce que la course entre threads finit par invalider l’état interne. Une fois le handle corrompu, toutes les opérations suivantes échouent jusqu’au redémarrage du processus ou la recréation propre de l’instance.
Utiliser SHA256Managed résout‑il le problème ?
Cela peut contourner temporairement l’erreur CNG, mais la règle fondamentale demeure : une instance par appel. Si vous partagez quand même une instance, vous retombez sur des comportements indéfinis.
Puis‑je désinstaller les mises à jour KB ?
C’est un palliatif risqué. Vous perdrez des correctifs de sécurité et, pire, vous pourriez revenir à des résultats de hachage silencieusement incorrects. Privilégiez le correctif applicatif.
Un verrou global est‑il acceptable ?
Oui pour un faible trafic. Pour la haute concurrence, préférez un pool d’instances ou une instance par appel, qui éliminent la contention.
Et côté natif ?
Créez un BCRYPT_HASH_HANDLE distinct par opération, ne réutilisez pas un handle en parallèle, finalisez une seule fois, puis détruisez proprement.
Plan d’action recommandé
- Isoler les emplacements de calcul de hash (auditer utilitaires, middleware, bibliothèques).
 - Remplacer toute instance partagée par une instance par appel ou une mise en pool d’instances bien confinée.
 - Corriger les pipelines incrémentaux pour garantir 
TransformFinalBlockunique. - Renforcer la journalisation (log hex/HResult) et ajouter des tests de charge multi‑threads.
 - Déployer progressivement, surveiller les métriques d’erreurs et performances, puis retirer d’éventuels contournements temporaires.
 
Exemples de tests de non‑régression
[Fact]
public void Hash_IsolatedInstance_IsThreadSafe()
{
    var data = Enumerable.Range(0, 10000).Select(i => BitConverter.GetBytes(i)).ToArray();
    Parallel.ForEach(data, d =>
    {
        var _ = SHA256.HashData(d); // instance gérée en interne
    });
}
[Fact]
public void Hash_TransformFinalBlock_Once()
{
using var sha = SHA256.Create();
sha.TransformBlock(new byte[] {1,2,3}, 0, 3, null, 0);
sha.TransformFinalBlock(Array.Empty(), 0, 0);
Assert.Throws(() => // finalisation double interdite
sha.TransformFinalBlock(Array.Empty(), 0, 0)
);
} 
Cas particuliers et bonnes pratiques
- Async/await : si vous conservez un 
HashAlgorithmdans un champ et vous l’utilisez après unawait, il peut être utilisé depuis un autre thread. Créez l’instance à l’intérieur de la méthode ou passez‑la par paramètre à un bloc local. - Durée de vie : 
HashAlgorithmestIDisposable. Libérez systématiquement les instances (using). - Interop : évitez de mélanger des appels managés et natifs sur la même instance/handle ; encapsulez‑les proprement derrière une API.
 - ETag / Web API : calculez l’ETag avec une instance jetable par requête ou via 
HashData. 
Résumé exécutif
L’exception -1073741816 révèle une utilisation concurrente non autorisée des objets de hachage. Les mises à jour Windows 2024 rendent cette erreur explicite et plus sûre. La correction durable n’est ni un rollback KB ni un contournement fragile : c’est la discipline d’accès suivante : une instance de hachage = un seul thread ou une section critique. En modernisant le code (instances locales, HashData, IncrementalHash, pool d’instances), vous restaurez la stabilité tout en renforçant l’intégrité cryptographique.
Annexe : aide‑mémoire rapide
- Erreur : 
CryptographicException: Unknown error -1073741816→0xC0000008(STATUS_INVALID_HANDLE). - Déclencheur : partage d’instance 
HashAlgorithmentre threads / double finalisation. - Correctif : instance par appel, ou 
lockintégral si singleton imposé. - API modernes : 
SHA256.HashData,IncrementalHash. - Natif : un 
BCRYPT_HASH_HANDLEpar opération, finaliser une fois, détruire. 

