{"record":{"id":"36d9e9c3b7b74d88","repo":"abpframework/abp","slug":"aes-gcm-is-not-available-on-net-standard-2-0","errorCode":null,"errorMessage":"AES-GCM is not available on .NET Standard 2.0!","messagePattern":"AES-GCM is not available on \\.NET Standard 2\\.0!","errorType":"exception","errorClass":"PlatformNotSupportedException","httpStatus":null,"severity":"critical","filePath":"framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobEncryptionCodec.cs","lineNumber":366,"sourceCode":"#else\n            using var password = new Rfc2898DeriveBytes(passwordBytes, salt, iterations, HashAlgorithmName.SHA256);\n            return password.GetBytes(32);\n#endif\n        }\n        finally\n        {\n            CryptographicOperations.ZeroMemory(passwordBytes);\n        }\n#endif\n    }\n\n    // One AES-GCM instance is bound to the per-BLOB key and reused for every chunk, so a\n    // stream sets up the key schedule once instead of per chunk. Typed as IDisposable so the\n    // streams that hold it still compile on netstandard2.0 (where creation throws first).\n    internal static IDisposable CreateChunkCipher(byte[] keyBytes)\n    {\n#if NETSTANDARD2_0\n        throw new PlatformNotSupportedException(\"AES-GCM is not available on .NET Standard 2.0!\");\n#else\n        return CreateAesGcm(keyBytes);\n#endif\n    }\n\n    internal static byte[] EncryptChunk(byte[] keyBytes, byte[] associatedDataPrefix, byte[] baseNonce, int chunkIndex, byte[] plainChunk, int plainChunkLength)\n    {\n        using (var cipher = CreateChunkCipher(keyBytes))\n        {\n            return EncryptChunkCore(cipher, CreateChunkAssociatedData(associatedDataPrefix, chunkIndex), CreateChunkNonce(baseNonce, chunkIndex), plainChunk, plainChunkLength);\n        }\n    }\n\n    // The cipher, associated data and nonce are passed in fully built so the streams can reuse\n    // one of each and only rewrite the trailing chunk index, instead of reconstructing the\n    // AES-GCM key schedule and reallocating the whole identity (which grows with the\n    // container/BLOB name) for every chunk\n    internal static byte[] EncryptChunkCore(IDisposable cipher, byte[] associatedData, byte[] nonce, byte[] plainChunk, int plainChunkLength)","sourceCodeStart":348,"sourceCodeEnd":384,"githubUrl":"https://github.com/abpframework/abp/blob/7ed43b1931b9df46a50c0c59148a18645641d0df/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobEncryptionCodec.cs#L348-L384","documentation":"BLOB encryption is built on AES-GCM in chunk mode, and AES-GCM has no implementation on .NET Standard 2.0. CreateChunkCipher is the single entry point that constructs the per-BLOB AesGcm instance; on netstandard2.0 it throws PlatformNotSupportedException before any crypto runs. The method is typed as IDisposable so the calling streams still compile on netstandard2.0.","triggerScenarios":"Running or unit-testing code that calls BlobEncryptionCodec.CreateChunkCipher (directly, or via the encrypting/decrypting streams that construct it in their ctor) while the executing assembly resolves to the netstandard2.0 TFM.","commonSituations":"A shared/legacy library targets only netstandard2.0 and enables BLOB encryption; an ABP upgrade pulled in encryption defaults on a project that still multi-targets down to netstandard2.0; tests run against the netstandard2.0 build.","solutions":["Retarget the project (or add a net6.0/net8.0 TFM in a multi-target) so the AES-GCM code path is selected at runtime.","Disable BLOB encryption for the container when running on netstandard2.0 (do not call UseEncryption, or gate it on the target framework).","Move encryption to a net6.0+ service/host that consumes the netstandard2.0 storage library and stores plaintext-free blobs.","Keep the storage library on netstandard2.0 but put a net6.0+ companion package in charge of encryption."],"exampleFix":"// before — encryption enabled unconditionally\nConfigure<AbpBlobStoringOptions>(o =>\n    o.Containers.Configure<UserContainer>(c =>\n        c.UseEncryption()));\n\n// after — gate on a TFM where AES-GCM exists\n#if !NETSTANDARD2_0\nConfigure<AbpBlobStoringOptions>(o =>\n    o.Containers.Configure<UserContainer>(c =>\n        c.UseEncryption()));\n#endif","handlingStrategy":"validation","validationCode":"// Detect netstandard2.0 at startup and refuse to enable encryption there.\nstatic bool AesGcmAvailable =>\n#if NETSTANDARD2_0\n    false;\n#else\n    true;\n#endif\n\nif (!AesGcmAvailable)\n    throw new PlatformNotSupportedException(\n        \"BLOB encryption requires a TFM with AES-GCM (net6.0+); this build is netstandard2.0.\");","typeGuard":"// Compile-time guard so the encryption call sites simply do not exist on netstandard2.0.\n#if !NETSTANDARD2_0\npublic static void EnableEncryption(BlobContainerConfiguration c) => c.UseEncryption();\n#else\npublic static void EnableEncryption(BlobContainerConfiguration c) =>\n    throw new PlatformNotSupportedException(\"Re-target to net6.0+ for BLOB encryption.\");\n#endif","tryCatchPattern":"try\n{\n    EnableEncryption(containerConfig);\n}\ncatch (PlatformNotSupportedException ex) when (ex.Message.Contains(\".NET Standard 2.0\"))\n{\n    logger.LogCritical(ex, \"Cannot enable encryption on this TFM; deploy on net6.0+.\");\n    throw;\n}","preventionTips":["Multi-target shared libraries and run crypto on a net6.0+ TFM.","Add a startup check that fails fast if encryption is requested on a build without AES-GCM.","Keep encryption configuration out of netstandard2.0-only projects.","Document the AES-GCM TFM requirement wherever encryption is enabled."],"tags":["crypto","platform","netstandard","tfm","aes-gcm"],"backgroundTag":null,"analyzedSha":"7ed43b1931b9df46a50c0c59148a18645641d0df","analyzedAt":"2026-08-13T16:26:11.351Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}