diff --git a/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml b/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml
index 85c92f292323f9..ff5db8cf02512c 100644
--- a/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml
+++ b/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml
@@ -200,6 +200,14 @@ extends:
             SYSTEM_ACCESSTOKEN: $(System.AccessToken)
           displayName: Send Utf8JsonWriterFuzzer to OneFuzz
 
+        - task: onefuzz-task@0
+          inputs:
+            onefuzzOSes: 'Windows'
+          env:
+            onefuzzDropDirectory: $(fuzzerProject)/deployment/WinZipAesStreamFuzzer
+            SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+          displayName: Send WinZipAesStreamFuzzer to OneFuzz
+
         - task: onefuzz-task@0
           inputs:
             onefuzzOSes: 'Windows'
@@ -207,4 +215,12 @@ extends:
             onefuzzDropDirectory: $(fuzzerProject)/deployment/ZipArchiveFuzzer
             SYSTEM_ACCESSTOKEN: $(System.AccessToken)
           displayName: Send ZipArchiveFuzzer to OneFuzz
+
+        - task: onefuzz-task@0
+          inputs:
+            onefuzzOSes: 'Windows'
+          env:
+            onefuzzDropDirectory: $(fuzzerProject)/deployment/ZipCryptoStreamFuzzer
+            SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+          displayName: Send ZipCryptoStreamFuzzer to OneFuzz
 # ONEFUZZ_TASK_WORKAROUND_END
diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeX509Handles.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeX509Handles.Unix.cs
index 7017405cb4a6a0..45b426661eeaa9 100644
--- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeX509Handles.Unix.cs
+++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeX509Handles.Unix.cs
@@ -13,8 +13,10 @@ internal sealed class SafeX509Handle : SafeHandle
         private static readonly bool s_captureTrace =
             Environment.GetEnvironmentVariable("DEBUG_SAFEX509HANDLE_FINALIZATION") != null;
 
-        private readonly StackTrace? _stacktrace =
-            s_captureTrace ? new StackTrace(fNeedFileInfo: true) : null;
+        // Using reflection to avoid a hard dependency on System.Diagnostics.StackTrace, which prevents
+        // System.IO.Compression from referencing this assembly.
+        private readonly object? _stacktrace =
+            s_captureTrace ? Activator.CreateInstance(Type.GetType("System.Diagnostics.StackTrace")!, true) : null;
 
         ~SafeX509Handle()
         {
diff --git a/src/libraries/Common/tests/System/IO/Compression/ZipTestHelper.cs b/src/libraries/Common/tests/System/IO/Compression/ZipTestHelper.cs
index a3ccd52901601e..c7b8ab960d2d4e 100644
--- a/src/libraries/Common/tests/System/IO/Compression/ZipTestHelper.cs
+++ b/src/libraries/Common/tests/System/IO/Compression/ZipTestHelper.cs
@@ -14,6 +14,7 @@ public partial class ZipFileTestBase : FileCleanupTestBase
     {
         public static string bad(string filename) => Path.Combine("ZipTestData", "badzipfiles", filename);
         public static string compat(string filename) => Path.Combine("ZipTestData", "compat", filename);
+        public static string passwordProtected(string filename) => Path.Combine("ZipTestData", "PasswordProtectedZipArchives", filename);
         public static string strange(string filename) => Path.Combine("ZipTestData", "StrangeZipFiles", filename);
         public static string zfile(string filename) => Path.Combine("ZipTestData", "refzipfiles", filename);
         public static string zfolder(string filename) => Path.Combine("ZipTestData", "refzipfolders", filename);
@@ -562,6 +563,11 @@ public static async Task<Stream> OpenEntryStream(bool async, ZipArchiveEntry ent
             return async ? await entry.OpenAsync() : entry.Open();
         }
 
+        public static Task<Stream> OpenEntryStream(bool async, ZipArchiveEntry entry, string password)
+        {
+            return async ? entry.OpenAsync(password) : Task.FromResult(entry.Open(password));
+        }
+
         public static async Task DisposeStream(bool async, Stream stream)
         {
             if (async)
diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/WinZipAesStreamFuzzer.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/WinZipAesStreamFuzzer.cs
new file mode 100644
index 00000000000000..9c66cfc64576f2
--- /dev/null
+++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/WinZipAesStreamFuzzer.cs
@@ -0,0 +1,179 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers;
+using System.IO.Compression;
+using System.Reflection;
+using System.Runtime.Versioning;
+using System.Security.Cryptography;
+using System.Threading.Tasks;
+
+namespace DotnetFuzzing.Fuzzers;
+
+[UnsupportedOSPlatform("browser")]
+internal sealed class WinZipAesStreamFuzzer : IFuzzer
+{
+    public string[] TargetAssemblies { get; } = ["System.IO.Compression"];
+    public string[] TargetCoreLibPrefixes => [];
+    public string Corpus => "winzipaesstream";
+
+    // AES-256 key size in bits; salt size = keySizeBits / 16 = 16 bytes.
+    private const int KeySizeBits = 256;
+
+    // ReadOnlySpan<char> is a ref struct and cannot be boxed for MethodInfo.Invoke,
+    // and CreateDelegate cannot handle struct-to-object return covariance.
+    // Use DynamicMethod to emit a wrapper that boxes the struct return value.
+    private delegate object CreateKeyDelegate(ReadOnlySpan<char> password, byte[]? salt, int keySizeBits);
+
+    private static readonly CreateKeyDelegate _createKey;
+    private static readonly MethodInfo _createMethod;
+
+    // The salt and password verifier properties are needed to prepend a valid header
+    // so the stream's ReadAndValidateHeaderCore succeeds and decryption logic is reached.
+    private static readonly PropertyInfo _saltProp;
+    private static readonly PropertyInfo _verifierProp;
+
+    // Pre-derive key material once with a fixed password and no salt so the fuzzer focuses
+    // on the stream's decryption/HMAC logic rather than key derivation.
+    private static readonly object s_keyMaterial;
+
+    // Cache the salt and password verifier bytes for prepending to the fuzz input.
+    private static readonly byte[] s_salt;
+    private static readonly byte[] s_verifier;
+
+    static WinZipAesStreamFuzzer()
+    {
+        Type winZipAesStreamType = Type.GetType("System.IO.Compression.WinZipAesStream, System.IO.Compression")!;
+        Type winZipAesKeyMaterialType = Type.GetType("System.IO.Compression.WinZipAesKeyMaterial, System.IO.Compression")!;
+
+#pragma warning disable IL3050 // RequiresDynamicCode: DynamicMethod is not AOT-compatible; fuzzers run under CoreCLR only.
+        _createKey = CreateBoxingDelegate(winZipAesKeyMaterialType);
+#pragma warning restore IL3050
+
+        _createMethod = winZipAesStreamType.GetMethod(
+            "Create",
+            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static,
+            binder: null,
+            types: [typeof(Stream), winZipAesKeyMaterialType, typeof(long), typeof(bool), typeof(bool)],
+            modifiers: null)!;
+
+        _saltProp = winZipAesKeyMaterialType.GetProperty(
+            "Salt",
+            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)!;
+
+        _verifierProp = winZipAesKeyMaterialType.GetProperty(
+            "PasswordVerifier",
+            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)!;
+
+        s_keyMaterial = _createKey("fuzz", null, KeySizeBits);
+        s_salt = (byte[])_saltProp.GetValue(s_keyMaterial)!;
+        s_verifier = (byte[])_verifierProp.GetValue(s_keyMaterial)!;
+    }
+
+    private static CreateKeyDelegate CreateBoxingDelegate(Type winZipAesKeyMaterialType)
+    {
+        MethodInfo createKeyMethod = winZipAesKeyMaterialType.GetMethod(
+            "Create",
+            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static,
+            binder: null,
+            types: [typeof(ReadOnlySpan<char>), typeof(byte[]), typeof(int)],
+            modifiers: null)!;
+
+        var dm = new System.Reflection.Emit.DynamicMethod(
+            "CreateKeyWrapper",
+            typeof(object),
+            [typeof(ReadOnlySpan<char>), typeof(byte[]), typeof(int)],
+            typeof(WinZipAesStreamFuzzer).Module,
+            skipVisibility: true);
+        var il = dm.GetILGenerator();
+        il.Emit(System.Reflection.Emit.OpCodes.Ldarg_0);
+        il.Emit(System.Reflection.Emit.OpCodes.Ldarg_1);
+        il.Emit(System.Reflection.Emit.OpCodes.Ldarg_2);
+        il.Emit(System.Reflection.Emit.OpCodes.Call, createKeyMethod);
+        il.Emit(System.Reflection.Emit.OpCodes.Box, winZipAesKeyMaterialType);
+        il.Emit(System.Reflection.Emit.OpCodes.Ret);
+        return dm.CreateDelegate<CreateKeyDelegate>();
+    }
+
+    // Minimum fuzz input: at least 1 byte of encrypted data beyond the header.
+    // The header (salt + verifier) is prepended by CreateStream, so the fuzz input
+    // only needs to supply encrypted data + the 10-byte auth code.
+    private const int MinInputLength = 11; // 1 byte data + 10 bytes HMAC
+
+    public void FuzzTarget(ReadOnlySpan<byte> bytes)
+    {
+        if (bytes.Length < MinInputLength)
+        {
+            return;
+        }
+
+        TestStream(CopyToRentedArray(bytes), bytes.Length, async: false).GetAwaiter().GetResult();
+        TestStream(CopyToRentedArray(bytes), bytes.Length, async: true).GetAwaiter().GetResult();
+    }
+
+    private static Stream CreateStream(byte[] bytes, int length)
+    {
+        // Prepend the valid salt + password verifier so ReadAndValidateHeaderCore passes,
+        // allowing the fuzzer to exercise the CTR decryption and HMAC validation paths.
+        int headerSize = s_salt.Length + s_verifier.Length;
+        int totalSize = headerSize + length;
+        byte[] combined = new byte[totalSize];
+        s_salt.CopyTo(combined, 0);
+        s_verifier.CopyTo(combined, s_salt.Length);
+        Buffer.BlockCopy(bytes, 0, combined, headerSize, length);
+
+#pragma warning disable IL2072 // dynamic invocation
+        return (Stream)_createMethod.Invoke(
+            obj: null,
+            parameters: [new MemoryStream(combined), s_keyMaterial, (long)totalSize, /*encrypting*/ false, /*leaveOpen*/ false])!;
+#pragma warning restore IL2072
+    }
+
+    private byte[] CopyToRentedArray(ReadOnlySpan<byte> bytes)
+    {
+        byte[] buffer = ArrayPool<byte>.Shared.Rent(bytes.Length);
+        try
+        {
+            bytes.CopyTo(buffer);
+            return buffer;
+        }
+        catch
+        {
+            ArrayPool<byte>.Shared.Return(buffer);
+            throw;
+        }
+    }
+
+    private async Task TestStream(byte[] buffer, int length, bool async)
+    {
+        try
+        {
+            using var stream = CreateStream(buffer, length);
+            if (async)
+            {
+                await stream.CopyToAsync(Stream.Null);
+            }
+            else
+            {
+                stream.CopyTo(Stream.Null);
+            }
+        }
+        catch (InvalidDataException)
+        {
+            // ignore, this exception is expected for invalid/corrupted data.
+        }
+        catch (CryptographicException)
+        {
+            // ignore, crypto failures are expected for random fuzz input.
+        }
+        catch (TargetInvocationException ex) when (ex.InnerException is InvalidDataException or CryptographicException)
+        {
+            // The reflected WinZipAesStream.Create call wraps exceptions
+            // in TargetInvocationException when header validation fails.
+        }
+        finally
+        {
+            ArrayPool<byte>.Shared.Return(buffer);
+        }
+    }
+}
diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/ZipCryptoStreamFuzzer.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/ZipCryptoStreamFuzzer.cs
new file mode 100644
index 00000000000000..0765bd6c680525
--- /dev/null
+++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/ZipCryptoStreamFuzzer.cs
@@ -0,0 +1,135 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers;
+using System.IO.Compression;
+using System.Reflection;
+using System.Threading.Tasks;
+
+namespace DotnetFuzzing.Fuzzers;
+
+internal sealed class ZipCryptoStreamFuzzer : IFuzzer
+{
+    public string[] TargetAssemblies { get; } = ["System.IO.Compression"];
+    public string[] TargetCoreLibPrefixes => [];
+    public string Corpus => "zipcryptostream";
+
+    public void FuzzTarget(ReadOnlySpan<byte> bytes)
+    {
+        // ZipCryptoStream.Create reads a 12-byte header from the stream and validates the
+        // last decrypted byte against the expected check byte. Require at least 13 bytes
+        // (1 check byte + 12 header bytes) so the fuzzer can reach past the header.
+        if (bytes.Length < 13)
+        {
+            return;
+        }
+
+        TestStream(CopyToRentedArray(bytes), bytes.Length, async: false).GetAwaiter().GetResult();
+        TestStream(CopyToRentedArray(bytes), bytes.Length, async: true).GetAwaiter().GetResult();
+    }
+
+    // ReadOnlySpan<char> is a ref struct and cannot be boxed for MethodInfo.Invoke,
+    // and CreateDelegate cannot handle struct-to-object return covariance.
+    // Use DynamicMethod to emit a wrapper that boxes the struct return value.
+    private delegate object CreateKeyDelegate(ReadOnlySpan<char> password);
+
+    private static readonly CreateKeyDelegate _createKey;
+    private static readonly MethodInfo _createMethod;
+    private static readonly object s_keys;
+
+    static ZipCryptoStreamFuzzer()
+    {
+        Type zipCryptoStreamType = Type.GetType("System.IO.Compression.ZipCryptoStream, System.IO.Compression")!;
+        Type zipCryptoKeysType = Type.GetType("System.IO.Compression.ZipCryptoKeys, System.IO.Compression")!;
+
+#pragma warning disable IL3050 // RequiresDynamicCode: DynamicMethod is not AOT-compatible; fuzzers run under CoreCLR only.
+        _createKey = CreateBoxingDelegate(zipCryptoStreamType, zipCryptoKeysType);
+#pragma warning restore IL3050
+
+        _createMethod = zipCryptoStreamType.GetMethod(
+            "Create",
+            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static,
+            binder: null,
+            types: [typeof(Stream), zipCryptoKeysType, typeof(byte), typeof(bool), typeof(bool)],
+            modifiers: null)!;
+
+        s_keys = _createKey("fuzz");
+    }
+
+    private static CreateKeyDelegate CreateBoxingDelegate(Type zipCryptoStreamType, Type zipCryptoKeysType)
+    {
+        MethodInfo createKeyMethod = zipCryptoStreamType.GetMethod(
+            "CreateKey",
+            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)!;
+
+        var dm = new System.Reflection.Emit.DynamicMethod(
+            "CreateKeyWrapper",
+            typeof(object),
+            [typeof(ReadOnlySpan<char>)],
+            typeof(ZipCryptoStreamFuzzer).Module,
+            skipVisibility: true);
+        var il = dm.GetILGenerator();
+        il.Emit(System.Reflection.Emit.OpCodes.Ldarg_0);
+        il.Emit(System.Reflection.Emit.OpCodes.Call, createKeyMethod);
+        il.Emit(System.Reflection.Emit.OpCodes.Box, zipCryptoKeysType);
+        il.Emit(System.Reflection.Emit.OpCodes.Ret);
+        return dm.CreateDelegate<CreateKeyDelegate>();
+    }
+
+    private static Stream CreateStream(byte[] bytes, int length)
+    {
+        // Use the first byte of the input as the "expected check byte" so that the
+        // header validation path is exercised with varying values.
+        byte expectedCheckByte = bytes[0];
+        var baseStream = new MemoryStream(bytes, 1, length - 1);
+#pragma warning disable IL2072 // dynamic invocation
+        return (Stream)_createMethod.Invoke(
+            obj: null,
+            parameters: [baseStream, s_keys, expectedCheckByte, /*encrypting*/ false, /*leaveOpen*/ false])!;
+#pragma warning restore IL2072
+    }
+
+    private byte[] CopyToRentedArray(ReadOnlySpan<byte> bytes)
+    {
+        byte[] buffer = ArrayPool<byte>.Shared.Rent(bytes.Length);
+        try
+        {
+            bytes.CopyTo(buffer);
+            return buffer;
+        }
+        catch
+        {
+            ArrayPool<byte>.Shared.Return(buffer);
+            throw;
+        }
+    }
+
+    private async Task TestStream(byte[] buffer, int length, bool async)
+    {
+        try
+        {
+            using var stream = CreateStream(buffer, length);
+            if (async)
+            {
+                await stream.CopyToAsync(Stream.Null);
+            }
+            else
+            {
+                stream.CopyTo(Stream.Null);
+            }
+        }
+        catch (InvalidDataException)
+        {
+            // ignore, this exception is expected for invalid/corrupted data.
+        }
+        catch (TargetInvocationException ex) when (ex.InnerException is InvalidDataException)
+        {
+            // The reflected ZipCryptoStream.Create call wraps InvalidDataException
+            // (e.g. password mismatch, truncated header) in TargetInvocationException.
+        }
+        finally
+        {
+            ArrayPool<byte>.Shared.Return(buffer);
+        }
+    }
+}
diff --git a/src/libraries/System.IO.Compression.ZipFile/ref/System.IO.Compression.ZipFile.cs b/src/libraries/System.IO.Compression.ZipFile/ref/System.IO.Compression.ZipFile.cs
index f0948420b9eaf9..1efc88fcc77748 100644
--- a/src/libraries/System.IO.Compression.ZipFile/ref/System.IO.Compression.ZipFile.cs
+++ b/src/libraries/System.IO.Compression.ZipFile/ref/System.IO.Compression.ZipFile.cs
@@ -6,33 +6,48 @@
 
 namespace System.IO.Compression
 {
+    public sealed partial class ZipExtractionOptions
+    {
+        public ZipExtractionOptions() { }
+        public System.Text.Encoding? EntryNameEncoding { get { throw null; } set { } }
+        public bool OverwriteFiles { get { throw null; } set { } }
+        public System.ReadOnlyMemory<char> Password { get { throw null; } set { } }
+    }
     public static partial class ZipFile
     {
         public static void CreateFromDirectory(string sourceDirectoryName, System.IO.Stream destination) { }
         public static void CreateFromDirectory(string sourceDirectoryName, System.IO.Stream destination, System.IO.Compression.CompressionLevel compressionLevel, bool includeBaseDirectory) { }
         public static void CreateFromDirectory(string sourceDirectoryName, System.IO.Stream destination, System.IO.Compression.CompressionLevel compressionLevel, bool includeBaseDirectory, System.Text.Encoding? entryNameEncoding) { }
+        public static void CreateFromDirectory(string sourceDirectoryName, System.IO.Stream destination, System.IO.Compression.ZipFileCreationOptions options) { }
         public static void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName) { }
         public static void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, System.IO.Compression.CompressionLevel compressionLevel, bool includeBaseDirectory) { }
         public static void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, System.IO.Compression.CompressionLevel compressionLevel, bool includeBaseDirectory, System.Text.Encoding? entryNameEncoding) { }
+        public static void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, System.IO.Compression.ZipFileCreationOptions options) { }
         public static System.Threading.Tasks.Task CreateFromDirectoryAsync(string sourceDirectoryName, System.IO.Stream destination, System.IO.Compression.CompressionLevel compressionLevel, bool includeBaseDirectory, System.Text.Encoding? entryNameEncoding, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static System.Threading.Tasks.Task CreateFromDirectoryAsync(string sourceDirectoryName, System.IO.Stream destination, System.IO.Compression.CompressionLevel compressionLevel, bool includeBaseDirectory, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public static System.Threading.Tasks.Task CreateFromDirectoryAsync(string sourceDirectoryName, System.IO.Stream destination, System.IO.Compression.ZipFileCreationOptions options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static System.Threading.Tasks.Task CreateFromDirectoryAsync(string sourceDirectoryName, System.IO.Stream destination, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static System.Threading.Tasks.Task CreateFromDirectoryAsync(string sourceDirectoryName, string destinationArchiveFileName, System.IO.Compression.CompressionLevel compressionLevel, bool includeBaseDirectory, System.Text.Encoding? entryNameEncoding, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static System.Threading.Tasks.Task CreateFromDirectoryAsync(string sourceDirectoryName, string destinationArchiveFileName, System.IO.Compression.CompressionLevel compressionLevel, bool includeBaseDirectory, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public static System.Threading.Tasks.Task CreateFromDirectoryAsync(string sourceDirectoryName, string destinationArchiveFileName, System.IO.Compression.ZipFileCreationOptions options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static System.Threading.Tasks.Task CreateFromDirectoryAsync(string sourceDirectoryName, string destinationArchiveFileName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static void ExtractToDirectory(System.IO.Stream source, string destinationDirectoryName) { }
         public static void ExtractToDirectory(System.IO.Stream source, string destinationDirectoryName, bool overwriteFiles) { }
+        public static void ExtractToDirectory(System.IO.Stream source, string destinationDirectoryName, System.IO.Compression.ZipExtractionOptions options) { }
         public static void ExtractToDirectory(System.IO.Stream source, string destinationDirectoryName, System.Text.Encoding? entryNameEncoding) { }
         public static void ExtractToDirectory(System.IO.Stream source, string destinationDirectoryName, System.Text.Encoding? entryNameEncoding, bool overwriteFiles) { }
         public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName) { }
         public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, bool overwriteFiles) { }
+        public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, System.IO.Compression.ZipExtractionOptions options) { }
         public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, System.Text.Encoding? entryNameEncoding) { }
         public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, System.Text.Encoding? entryNameEncoding, bool overwriteFiles) { }
         public static System.Threading.Tasks.Task ExtractToDirectoryAsync(System.IO.Stream source, string destinationDirectoryName, bool overwriteFiles, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public static System.Threading.Tasks.Task ExtractToDirectoryAsync(System.IO.Stream source, string destinationDirectoryName, System.IO.Compression.ZipExtractionOptions options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static System.Threading.Tasks.Task ExtractToDirectoryAsync(System.IO.Stream source, string destinationDirectoryName, System.Text.Encoding? entryNameEncoding, bool overwriteFiles, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static System.Threading.Tasks.Task ExtractToDirectoryAsync(System.IO.Stream source, string destinationDirectoryName, System.Text.Encoding? entryNameEncoding, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static System.Threading.Tasks.Task ExtractToDirectoryAsync(System.IO.Stream source, string destinationDirectoryName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static System.Threading.Tasks.Task ExtractToDirectoryAsync(string sourceArchiveFileName, string destinationDirectoryName, bool overwriteFiles, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public static System.Threading.Tasks.Task ExtractToDirectoryAsync(string sourceArchiveFileName, string destinationDirectoryName, System.IO.Compression.ZipExtractionOptions options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static System.Threading.Tasks.Task ExtractToDirectoryAsync(string sourceArchiveFileName, string destinationDirectoryName, System.Text.Encoding? entryNameEncoding, bool overwriteFiles, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static System.Threading.Tasks.Task ExtractToDirectoryAsync(string sourceArchiveFileName, string destinationDirectoryName, System.Text.Encoding? entryNameEncoding, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static System.Threading.Tasks.Task ExtractToDirectoryAsync(string sourceArchiveFileName, string destinationDirectoryName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
@@ -43,20 +58,37 @@ public static void ExtractToDirectory(string sourceArchiveFileName, string desti
         public static System.IO.Compression.ZipArchive OpenRead(string archiveFileName) { throw null; }
         public static System.Threading.Tasks.Task<System.IO.Compression.ZipArchive> OpenReadAsync(string archiveFileName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
     }
+    public sealed partial class ZipFileCreationOptions
+    {
+        public ZipFileCreationOptions() { }
+        public System.IO.Compression.CompressionLevel CompressionLevel { get { throw null; } set { } }
+        public System.IO.Compression.ZipEncryptionMethod EncryptionMethod { get { throw null; } set { } }
+        public System.Text.Encoding? EntryNameEncoding { get { throw null; } set { } }
+        public bool IncludeBaseDirectory { get { throw null; } set { } }
+        public System.ReadOnlyMemory<char> Password { get { throw null; } set { } }
+    }
     [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
     public static partial class ZipFileExtensions
     {
         public static System.IO.Compression.ZipArchiveEntry CreateEntryFromFile(this System.IO.Compression.ZipArchive destination, string sourceFileName, string entryName) { throw null; }
         public static System.IO.Compression.ZipArchiveEntry CreateEntryFromFile(this System.IO.Compression.ZipArchive destination, string sourceFileName, string entryName, System.IO.Compression.CompressionLevel compressionLevel) { throw null; }
+        public static System.IO.Compression.ZipArchiveEntry CreateEntryFromFile(this System.IO.Compression.ZipArchive destination, string sourceFileName, string entryName, System.IO.Compression.CompressionLevel compressionLevel, System.ReadOnlySpan<char> password, System.IO.Compression.ZipEncryptionMethod encryption) { throw null; }
+        public static System.IO.Compression.ZipArchiveEntry CreateEntryFromFile(this System.IO.Compression.ZipArchive destination, string sourceFileName, string entryName, System.ReadOnlySpan<char> password, System.IO.Compression.ZipEncryptionMethod encryption) { throw null; }
+        public static System.Threading.Tasks.Task<System.IO.Compression.ZipArchiveEntry> CreateEntryFromFileAsync(this System.IO.Compression.ZipArchive destination, string sourceFileName, string entryName, System.IO.Compression.CompressionLevel compressionLevel, System.ReadOnlyMemory<char> password, System.IO.Compression.ZipEncryptionMethod encryption, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static System.Threading.Tasks.Task<System.IO.Compression.ZipArchiveEntry> CreateEntryFromFileAsync(this System.IO.Compression.ZipArchive destination, string sourceFileName, string entryName, System.IO.Compression.CompressionLevel compressionLevel, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public static System.Threading.Tasks.Task<System.IO.Compression.ZipArchiveEntry> CreateEntryFromFileAsync(this System.IO.Compression.ZipArchive destination, string sourceFileName, string entryName, System.ReadOnlyMemory<char> password, System.IO.Compression.ZipEncryptionMethod encryption, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static System.Threading.Tasks.Task<System.IO.Compression.ZipArchiveEntry> CreateEntryFromFileAsync(this System.IO.Compression.ZipArchive destination, string sourceFileName, string entryName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static void ExtractToDirectory(this System.IO.Compression.ZipArchive source, string destinationDirectoryName) { }
         public static void ExtractToDirectory(this System.IO.Compression.ZipArchive source, string destinationDirectoryName, bool overwriteFiles) { }
+        public static void ExtractToDirectory(this System.IO.Compression.ZipArchive source, string destinationDirectoryName, System.IO.Compression.ZipExtractionOptions options) { }
         public static System.Threading.Tasks.Task ExtractToDirectoryAsync(this System.IO.Compression.ZipArchive source, string destinationDirectoryName, bool overwriteFiles, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public static System.Threading.Tasks.Task ExtractToDirectoryAsync(this System.IO.Compression.ZipArchive source, string destinationDirectoryName, System.IO.Compression.ZipExtractionOptions options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static System.Threading.Tasks.Task ExtractToDirectoryAsync(this System.IO.Compression.ZipArchive source, string destinationDirectoryName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static void ExtractToFile(this System.IO.Compression.ZipArchiveEntry source, string destinationFileName) { }
         public static void ExtractToFile(this System.IO.Compression.ZipArchiveEntry source, string destinationFileName, bool overwrite) { }
+        public static void ExtractToFile(this System.IO.Compression.ZipArchiveEntry source, string destinationFileName, System.IO.Compression.ZipExtractionOptions options) { }
         public static System.Threading.Tasks.Task ExtractToFileAsync(this System.IO.Compression.ZipArchiveEntry source, string destinationFileName, bool overwrite, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public static System.Threading.Tasks.Task ExtractToFileAsync(this System.IO.Compression.ZipArchiveEntry source, string destinationFileName, System.IO.Compression.ZipExtractionOptions options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static System.Threading.Tasks.Task ExtractToFileAsync(this System.IO.Compression.ZipArchiveEntry source, string destinationFileName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
     }
 }
diff --git a/src/libraries/System.IO.Compression.ZipFile/src/Resources/Strings.resx b/src/libraries/System.IO.Compression.ZipFile/src/Resources/Strings.resx
index 2a361b9d8ca4f9..4dc66287ab2067 100644
--- a/src/libraries/System.IO.Compression.ZipFile/src/Resources/Strings.resx
+++ b/src/libraries/System.IO.Compression.ZipFile/src/Resources/Strings.resx
@@ -112,4 +112,7 @@
   <data name="UnwritableStream" xml:space="preserve">
     <value>The stream is unwritable.</value>
   </data>
+  <data name="EmptyPassword" xml:space="preserve">
+    <value>The password cannot be empty.</value>
+  </data>
 </root>
diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj b/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj
index 1709589cc4d076..2c2d4f2517ec0f 100644
--- a/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj
+++ b/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj
@@ -17,18 +17,16 @@
     <Compile Include="System\IO\Compression\ZipFileExtensions.ZipArchive.Extract.Async.cs" />
     <Compile Include="System\IO\Compression\ZipFileExtensions.ZipArchiveEntry.Extract.cs" />
     <Compile Include="System\IO\Compression\ZipFileExtensions.ZipArchiveEntry.Extract.Async.cs" />
-    <Compile Include="$(CommonPath)DisableRuntimeMarshalling.cs"
-             Link="Common\DisableRuntimeMarshalling.cs" />
-    <Compile Include="$(CommonPath)System\IO\Archiving.Utils.cs"
-             Link="Common\System\IO\Archiving.Utils.cs" />
-    <Compile Include="$(CommonPath)System\IO\PathInternal.CaseSensitivity.cs"
-             Link="Common\System\IO\PathInternal.CaseSensitivity.cs" />
+    <Compile Include="System\IO\Compression\ZipExtractionOptions.cs" />
+    <Compile Include="System\IO\Compression\ZipFileCreationOptions.cs" />
+    <Compile Include="$(CommonPath)DisableRuntimeMarshalling.cs" Link="Common\DisableRuntimeMarshalling.cs" />
+    <Compile Include="$(CommonPath)System\IO\Archiving.Utils.cs" Link="Common\System\IO\Archiving.Utils.cs" />
+    <Compile Include="$(CommonPath)System\IO\PathInternal.CaseSensitivity.cs" Link="Common\System\IO\PathInternal.CaseSensitivity.cs" />
   </ItemGroup>
 
   <!-- Windows specific files -->
   <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'windows'">
-    <Compile Include="$(CommonPath)System\IO\Archiving.Utils.Windows.cs"
-             Link="Common\System\IO\Archiving.Utils.Windows.cs" />
+    <Compile Include="$(CommonPath)System\IO\Archiving.Utils.Windows.cs" Link="Common\System\IO\Archiving.Utils.Windows.cs" />
     <Compile Include="System\IO\Compression\ZipFile.Create.Windows.cs" />
   </ItemGroup>
 
@@ -37,16 +35,11 @@
     <Compile Include="System\IO\Compression\ZipFile.Create.Unix.cs" />
     <Compile Include="System\IO\Compression\ZipFileExtensions.ZipArchive.Create.Unix.cs" />
     <Compile Include="$(CommonPath)System\IO\Compression\ZipArchiveEntryConstants.Unix.cs" />
-    <Compile Include="$(CommonPath)Interop\Unix\Interop.IOErrors.cs"
-             Link="Common\Interop\Unix\Interop.IOErrors.cs" />
-    <Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs"
-             Link="Common\Interop\Unix\Interop.Libraries.cs" />
-    <Compile Include="$(CommonPath)Interop\Unix\Interop.Errors.cs"
-             Link="Common\Interop\Unix\System.Native\Interop.Errors.cs" />
-    <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs"
-             Link="Common\Interop\Unix\System.Native\Interop.Stat.cs" />
-    <Compile Include="$(CommonPath)System\IO\Archiving.Utils.Unix.cs"
-             Link="Common\System\IO\Archiving.Utils.Unix.cs" />
+    <Compile Include="$(CommonPath)Interop\Unix\Interop.IOErrors.cs" Link="Common\Interop\Unix\Interop.IOErrors.cs" />
+    <Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs" Link="Common\Interop\Unix\Interop.Libraries.cs" />
+    <Compile Include="$(CommonPath)Interop\Unix\Interop.Errors.cs" Link="Common\Interop\Unix\System.Native\Interop.Errors.cs" />
+    <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs" Link="Common\Interop\Unix\System.Native\Interop.Stat.cs" />
+    <Compile Include="$(CommonPath)System\IO\Archiving.Utils.Unix.cs" Link="Common\System\IO\Archiving.Utils.Unix.cs" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipExtractionOptions.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipExtractionOptions.cs
new file mode 100644
index 00000000000000..c46b44a66a6924
--- /dev/null
+++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipExtractionOptions.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text;
+
+namespace System.IO.Compression;
+
+/// <summary>
+/// Options for extracting entries from a zip archive.
+/// </summary>
+public sealed class ZipExtractionOptions
+{
+    /// <summary>
+    /// Gets or sets the password used to decrypt encrypted entries in the archive.
+    /// </summary>
+    public ReadOnlyMemory<char> Password { get; set; }
+
+    /// <summary>
+    /// Gets or sets the encoding to use when reading entry names and comments.
+    /// </summary>
+    public Encoding? EntryNameEncoding { get; set; }
+
+    /// <summary>
+    /// Gets or sets a value indicating whether to overwrite existing files during extraction.
+    /// </summary>
+    public bool OverwriteFiles { get; set; }
+}
diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Create.Async.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Create.Async.cs
index 61ca3d237d156c..8d2bd86ee2427f 100644
--- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Create.Async.cs
+++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Create.Async.cs
@@ -430,6 +430,56 @@ public static Task CreateFromDirectoryAsync(string sourceDirectoryName, Stream d
                                            CompressionLevel compressionLevel, bool includeBaseDirectory, Encoding? entryNameEncoding, CancellationToken cancellationToken = default) =>
         DoCreateFromDirectoryAsync(sourceDirectoryName, destination, compressionLevel, includeBaseDirectory, entryNameEncoding, cancellationToken);
 
+    /// <summary>
+    /// Asynchronously creates a zip archive at the specified path containing the files and directories from the specified directory,
+    /// using the specified creation options.
+    /// </summary>
+    /// <param name="sourceDirectoryName">The path to the directory to be archived.</param>
+    /// <param name="destinationArchiveFileName">The path of the archive to be created.</param>
+    /// <param name="options">The creation options including compression level, encryption, encoding, and whether to include the base directory.</param>
+    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+    /// <exception cref="ArgumentNullException"><paramref name="sourceDirectoryName"/>, <paramref name="destinationArchiveFileName"/>, or <paramref name="options"/> is <see langword="null"/>.</exception>
+    public static async Task CreateFromDirectoryAsync(string sourceDirectoryName, string destinationArchiveFileName, ZipFileCreationOptions options, CancellationToken cancellationToken = default)
+    {
+        ArgumentNullException.ThrowIfNull(options);
+        if (options.EncryptionMethod != ZipEncryptionMethod.None && options.Password.IsEmpty)
+            throw new ArgumentException(SR.EmptyPassword, nameof(options));
+        cancellationToken.ThrowIfCancellationRequested();
+
+        (sourceDirectoryName, destinationArchiveFileName) = GetFullPathsForDoCreateFromDirectory(sourceDirectoryName, destinationArchiveFileName);
+
+        ZipArchive archive = await OpenAsync(destinationArchiveFileName, ZipArchiveMode.Create, options.EntryNameEncoding, cancellationToken).ConfigureAwait(false);
+        await using (archive)
+        {
+            await CreateZipArchiveFromDirectoryAsync(sourceDirectoryName, archive, options.CompressionLevel, options.IncludeBaseDirectory, options.Password, options.EncryptionMethod, cancellationToken).ConfigureAwait(false);
+        }
+    }
+
+    /// <summary>
+    /// Asynchronously creates a zip archive in the specified stream containing the files and directories from the specified directory,
+    /// using the specified creation options.
+    /// </summary>
+    /// <param name="sourceDirectoryName">The path to the directory to be archived.</param>
+    /// <param name="destination">The stream where the zip archive is to be stored.</param>
+    /// <param name="options">The creation options including compression level, encryption, encoding, and whether to include the base directory.</param>
+    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+    /// <exception cref="ArgumentNullException"><paramref name="sourceDirectoryName"/>, <paramref name="destination"/>, or <paramref name="options"/> is <see langword="null"/>.</exception>
+    public static async Task CreateFromDirectoryAsync(string sourceDirectoryName, Stream destination, ZipFileCreationOptions options, CancellationToken cancellationToken = default)
+    {
+        ArgumentNullException.ThrowIfNull(options);
+        if (options.EncryptionMethod != ZipEncryptionMethod.None && options.Password.IsEmpty)
+            throw new ArgumentException(SR.EmptyPassword, nameof(options));
+        cancellationToken.ThrowIfCancellationRequested();
+
+        sourceDirectoryName = ValidateAndGetFullPathForDoCreateFromDirectory(sourceDirectoryName, destination, options.CompressionLevel);
+
+        ZipArchive archive = await ZipArchive.CreateAsync(destination, ZipArchiveMode.Create, leaveOpen: true, options.EntryNameEncoding, cancellationToken).ConfigureAwait(false);
+        await using (archive)
+        {
+            await CreateZipArchiveFromDirectoryAsync(sourceDirectoryName, archive, options.CompressionLevel, options.IncludeBaseDirectory, options.Password, options.EncryptionMethod, cancellationToken).ConfigureAwait(false);
+        }
+    }
+
     private static async Task DoCreateFromDirectoryAsync(string sourceDirectoryName, string destinationArchiveFileName,
                                               CompressionLevel? compressionLevel, bool includeBaseDirectory, Encoding? entryNameEncoding, CancellationToken cancellationToken)
 
@@ -445,7 +495,7 @@ private static async Task DoCreateFromDirectoryAsync(string sourceDirectoryName,
         ZipArchive archive = await OpenAsync(destinationArchiveFileName, ZipArchiveMode.Create, entryNameEncoding, cancellationToken).ConfigureAwait(false);
         await using (archive)
         {
-            await CreateZipArchiveFromDirectoryAsync(sourceDirectoryName, archive, compressionLevel, includeBaseDirectory, cancellationToken).ConfigureAwait(false);
+            await CreateZipArchiveFromDirectoryAsync(sourceDirectoryName, archive, compressionLevel, includeBaseDirectory, cancellationToken: cancellationToken).ConfigureAwait(false);
         }
     }
 
@@ -459,12 +509,13 @@ private static async Task DoCreateFromDirectoryAsync(string sourceDirectoryName,
         ZipArchive archive = await ZipArchive.CreateAsync(destination, ZipArchiveMode.Create, leaveOpen: true, entryNameEncoding, cancellationToken).ConfigureAwait(false);
         await using (archive)
         {
-            await CreateZipArchiveFromDirectoryAsync(sourceDirectoryName, archive, compressionLevel, includeBaseDirectory, cancellationToken).ConfigureAwait(false);
+            await CreateZipArchiveFromDirectoryAsync(sourceDirectoryName, archive, compressionLevel, includeBaseDirectory, cancellationToken: cancellationToken).ConfigureAwait(false);
         }
     }
 
     private static async Task CreateZipArchiveFromDirectoryAsync(string sourceDirectoryName, ZipArchive archive,
-                                                      CompressionLevel? compressionLevel, bool includeBaseDirectory, CancellationToken cancellationToken)
+                                                      CompressionLevel? compressionLevel, bool includeBaseDirectory,
+                                                      ReadOnlyMemory<char> password = default, ZipEncryptionMethod encryptionMethod = ZipEncryptionMethod.None, CancellationToken cancellationToken = default)
     {
         cancellationToken.ThrowIfCancellationRequested();
 
@@ -479,17 +530,13 @@ private static async Task CreateZipArchiveFromDirectoryAsync(string sourceDirect
             {
                 case CreateEntryType.File:
                 {
-                    // Create entry for file:
                     string entryName = ArchivingUtils.EntryFromPath(fullPath.AsSpan(basePath.Length));
-                    await ZipFileExtensions.DoCreateEntryFromFileAsync(archive, fullPath, entryName, compressionLevel, cancellationToken).ConfigureAwait(false);
+                    await ZipFileExtensions.DoCreateEntryFromFileAsync(archive, fullPath, entryName, compressionLevel, password, encryptionMethod, cancellationToken).ConfigureAwait(false);
                 }
                 break;
                 case CreateEntryType.Directory:
                     if (ArchivingUtils.IsDirEmpty(fullPath))
                     {
-                        // Create entry marking an empty dir:
-                        // FullName never returns a directory separator character on the end,
-                        // but Zip archives require it to specify an explicit directory:
                         string entryName = ArchivingUtils.EntryFromPath(fullPath.AsSpan(basePath.Length), appendPathSeparator: true);
                         archive.CreateEntry(entryName);
                     }
diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Create.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Create.cs
index cd6d977ae1c0d2..36b11871eefa8c 100644
--- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Create.cs
+++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Create.cs
@@ -400,6 +400,46 @@ public static void CreateFromDirectory(string sourceDirectoryName, Stream destin
                                                CompressionLevel compressionLevel, bool includeBaseDirectory, Encoding? entryNameEncoding) =>
             DoCreateFromDirectory(sourceDirectoryName, destination, compressionLevel, includeBaseDirectory, entryNameEncoding);
 
+        /// <summary>
+        /// Creates a zip archive at the specified path containing the files and directories from the specified directory,
+        /// using the specified creation options.
+        /// </summary>
+        /// <param name="sourceDirectoryName">The path to the directory to be archived.</param>
+        /// <param name="destinationArchiveFileName">The path of the archive to be created.</param>
+        /// <param name="options">The creation options including compression level, encryption, encoding, and whether to include the base directory.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="sourceDirectoryName"/>, <paramref name="destinationArchiveFileName"/>, or <paramref name="options"/> is <see langword="null"/>.</exception>
+        public static void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, ZipFileCreationOptions options)
+        {
+            ArgumentNullException.ThrowIfNull(options);
+            if (options.EncryptionMethod != ZipEncryptionMethod.None && options.Password.IsEmpty)
+                throw new ArgumentException(SR.EmptyPassword, nameof(options));
+
+            (sourceDirectoryName, destinationArchiveFileName) = GetFullPathsForDoCreateFromDirectory(sourceDirectoryName, destinationArchiveFileName);
+
+            using ZipArchive archive = Open(destinationArchiveFileName, ZipArchiveMode.Create, options.EntryNameEncoding);
+            CreateZipArchiveFromDirectory(sourceDirectoryName, archive, options.CompressionLevel, options.IncludeBaseDirectory, options.Password.Span, options.EncryptionMethod);
+        }
+
+        /// <summary>
+        /// Creates a zip archive in the specified stream containing the files and directories from the specified directory,
+        /// using the specified creation options.
+        /// </summary>
+        /// <param name="sourceDirectoryName">The path to the directory to be archived.</param>
+        /// <param name="destination">The stream where the zip archive is to be stored.</param>
+        /// <param name="options">The creation options including compression level, encryption, encoding, and whether to include the base directory.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="sourceDirectoryName"/>, <paramref name="destination"/>, or <paramref name="options"/> is <see langword="null"/>.</exception>
+        public static void CreateFromDirectory(string sourceDirectoryName, Stream destination, ZipFileCreationOptions options)
+        {
+            ArgumentNullException.ThrowIfNull(options);
+            if (options.EncryptionMethod != ZipEncryptionMethod.None && options.Password.IsEmpty)
+                throw new ArgumentException(SR.EmptyPassword, nameof(options));
+
+            sourceDirectoryName = ValidateAndGetFullPathForDoCreateFromDirectory(sourceDirectoryName, destination, options.CompressionLevel);
+
+            using ZipArchive archive = new ZipArchive(destination, ZipArchiveMode.Create, leaveOpen: true, options.EntryNameEncoding);
+            CreateZipArchiveFromDirectory(sourceDirectoryName, archive, options.CompressionLevel, options.IncludeBaseDirectory, options.Password.Span, options.EncryptionMethod);
+        }
+
         private static void DoCreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName,
                                                   CompressionLevel? compressionLevel, bool includeBaseDirectory, Encoding? entryNameEncoding)
 
@@ -424,7 +464,8 @@ private static void DoCreateFromDirectory(string sourceDirectoryName, Stream des
         }
 
         private static void CreateZipArchiveFromDirectory(string sourceDirectoryName, ZipArchive archive,
-                                                          CompressionLevel? compressionLevel, bool includeBaseDirectory)
+                                                          CompressionLevel? compressionLevel, bool includeBaseDirectory,
+                                                          ReadOnlySpan<char> password = default, ZipEncryptionMethod encryptionMethod = ZipEncryptionMethod.None)
         {
             (bool directoryIsEmpty, string basePath, DirectoryInfo di, FileSystemEnumerable<(string, CreateEntryType)> fse) =
                 InitializeCreateZipArchiveFromDirectory(sourceDirectoryName, includeBaseDirectory);
@@ -437,17 +478,13 @@ private static void CreateZipArchiveFromDirectory(string sourceDirectoryName, Zi
                 {
                     case CreateEntryType.File:
                         {
-                            // Create entry for file:
                             string entryName = ArchivingUtils.EntryFromPath(fullPath.AsSpan(basePath.Length));
-                            ZipFileExtensions.DoCreateEntryFromFile(archive, fullPath, entryName, compressionLevel);
+                            ZipFileExtensions.DoCreateEntryFromFile(archive, fullPath, entryName, compressionLevel, password, encryptionMethod);
                         }
                         break;
                     case CreateEntryType.Directory:
                         if (ArchivingUtils.IsDirEmpty(fullPath))
                         {
-                            // Create entry marking an empty dir:
-                            // FullName never returns a directory separator character on the end,
-                            // but Zip archives require it to specify an explicit directory:
                             string entryName = ArchivingUtils.EntryFromPath(fullPath.AsSpan(basePath.Length), appendPathSeparator: true);
                             archive.CreateEntry(entryName);
                         }
diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.Async.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.Async.cs
index 90baf60eac4706..4c09066df95da9 100644
--- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.Async.cs
+++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.Async.cs
@@ -111,7 +111,7 @@ public static Task ExtractToDirectoryAsync(string sourceArchiveFileName, string
     /// <param name="sourceArchiveFileName">The path to the archive on the file system that is to be extracted.</param>
     /// <param name="destinationDirectoryName">The path to the directory on the file system. The directory specified must not exist, but the directory that it is contained in must exist.</param>
     /// <param name="entryNameEncoding">The encoding to use when reading or writing entry names and comments in this ZipArchive.
-    ///         ///     <para>NOTE: Specifying this parameter to values other than <c>null</c> is discouraged.
+    ///     <para>NOTE: Specifying this parameter to values other than <c>null</c> is discouraged.
     ///         However, this may be necessary for interoperability with ZIP archive tools and libraries that do not correctly support
     ///         UTF-8 encoding for entry names or comments.<br />
     ///         This value is used as follows:</para>
@@ -169,7 +169,7 @@ public static Task ExtractToDirectoryAsync(string sourceArchiveFileName, string
     /// <param name="destinationDirectoryName">The path to the directory in which to place the extracted files, specified as a relative or absolute path. A relative path is interpreted as relative to the current working directory.</param>
     /// <param name="overwriteFiles">True to indicate overwrite.</param>
     /// <param name="entryNameEncoding">The encoding to use when reading or writing entry names and comments in this ZipArchive.
-    ///         ///     <para>NOTE: Specifying this parameter to values other than <c>null</c> is discouraged.
+    ///     <para>NOTE: Specifying this parameter to values other than <c>null</c> is discouraged.
     ///         However, this may be necessary for interoperability with ZIP archive tools and libraries that do not correctly support
     ///         UTF-8 encoding for entry names or comments.<br />
     ///         This value is used as follows:</para>
@@ -205,6 +205,79 @@ public static async Task ExtractToDirectoryAsync(string sourceArchiveFileName, s
         }
     }
 
+    /// <summary>
+    /// Asynchronously extracts all of the files in the specified password-protected archive to a directory on the file system.
+    /// The specified directory must not exist. This method will create all subdirectories and the specified directory.
+    /// If there is an error while extracting the archive, the archive will remain partially extracted. Each entry will
+    /// be extracted such that the extracted file has the same relative path to the destinationDirectoryName as the entry
+    /// has to the archive. The path is permitted to specify relative or absolute path information. Relative path information
+    /// is interpreted as relative to the current working directory. If a file to be archived has an invalid last modified
+    /// time, the first datetime representable in the Zip timestamp format (midnight on January 1, 1980) will be used.
+    /// </summary>
+    ///
+    /// <exception cref="ArgumentException">sourceArchive or destinationDirectoryName is a zero-length string, contains only whitespace,
+    /// or contains one or more invalid characters as defined by InvalidPathChars.</exception>
+    /// <exception cref="ArgumentNullException">sourceArchive or destinationDirectoryName is null.</exception>
+    /// <exception cref="PathTooLongException">sourceArchive or destinationDirectoryName specifies a path, file name,
+    /// or both exceed the system-defined maximum length. For example, on Windows-based platforms, paths must be less than 248 characters,
+    /// and file names must be less than 260 characters.</exception>
+    /// <exception cref="DirectoryNotFoundException">The path specified by sourceArchive or destinationDirectoryName is invalid,
+    /// (for example, it is on an unmapped drive).</exception>
+    /// <exception cref="IOException">An I/O error has occurred. -or- An archive entry's name is zero-length, contains only whitespace, or contains one or
+    /// more invalid characters as defined by InvalidPathChars. -or- Extracting an archive entry would result in a file destination that is outside the destination directory (for example, because of parent directory accessors). -or- An archive entry has the same name as an already extracted entry from the same archive.</exception>
+    /// <exception cref="UnauthorizedAccessException">The caller does not have the required permission.</exception>
+    /// <exception cref="NotSupportedException">sourceArchive or destinationDirectoryName is in an invalid format. </exception>
+    /// <exception cref="FileNotFoundException">sourceArchive was not found.</exception>
+    /// <exception cref="InvalidDataException">The archive specified by sourceArchive: Is not a valid ZipArchive
+    /// -or- An archive entry was not found or was corrupt. -or- An archive entry has been compressed using a compression method
+    /// that is not supported.</exception>
+    /// <exception cref="OperationCanceledException">An asynchronous operation is cancelled.</exception>
+    ///
+    /// <param name="sourceArchiveFileName">The path to the archive on the file system that is to be extracted.</param>
+    /// <param name="destinationDirectoryName">The path to the directory in which to place the extracted files, specified as a relative or absolute path. A relative path is interpreted as relative to the current working directory.</param>
+    /// <param name="overwriteFiles">True to indicate overwrite.</param>
+    /// <param name="entryNameEncoding">The encoding to use when reading or writing entry names and comments in this ZipArchive.
+    ///     <para>NOTE: Specifying this parameter to values other than <c>null</c> is discouraged.
+    ///         However, this may be necessary for interoperability with ZIP archive tools and libraries that do not correctly support
+    ///         UTF-8 encoding for entry names or comments.<br />
+    ///         This value is used as follows:</para>
+    ///     <para>If <c>entryNameEncoding</c> is not specified (<c>== null</c>):</para>
+    ///     <list>
+    ///         <item>For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is <em>not</em> set,
+    ///         use the current system default code page (<c>Encoding.Default</c>) in order to decode the entry name and comment.</item>
+    ///         <item>For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header <em>is</em> set,
+    ///         use UTF-8 (<c>Encoding.UTF8</c>) in order to decode the entry name and comment.</item>
+    ///     </list>
+    ///     <para>If <c>entryNameEncoding</c> is specified (<c>!= null</c>):</para>
+    ///     <list>
+    ///         <item>For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is <em>not</em> set,
+    ///         use the specified <c>entryNameEncoding</c> in order to decode the entry name and comment.</item>
+    ///         <item>For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header <em>is</em> set,
+    ///         use UTF-8 (<c>Encoding.UTF8</c>) in order to decode the entry name and comment.</item>
+    ///     </list>
+    ///     <para>Note that Unicode encodings other than UTF-8 may not be currently used for the <c>entryNameEncoding</c>,
+    ///     otherwise an <see cref="ArgumentException"/> is thrown.</para>
+    /// </param>
+    /// <param name="password">The password used to decrypt the encrypted entries in the archive.</param>
+    /// <param name="cancellationToken">The cancellation token to monitor for cancellation requests.</param>
+    /// <returns>A task that represents the asynchronous extract operation. The task completes when all entries have been extracted or an error occurs.</returns>
+    private static async Task ExtractToDirectoryAsync(string sourceArchiveFileName, string destinationDirectoryName, Encoding? entryNameEncoding, bool overwriteFiles, ReadOnlyMemory<char> password, CancellationToken cancellationToken = default)
+    {
+        cancellationToken.ThrowIfCancellationRequested();
+
+        ArgumentNullException.ThrowIfNull(sourceArchiveFileName);
+
+        ZipArchive archive = await OpenAsync(sourceArchiveFileName, ZipArchiveMode.Read, entryNameEncoding, cancellationToken).ConfigureAwait(false);
+        await using (archive)
+        {
+            foreach (ZipArchiveEntry entry in archive.Entries)
+            {
+                cancellationToken.ThrowIfCancellationRequested();
+                await entry.ExtractRelativeToDirectoryAsync(destinationDirectoryName, overwriteFiles, password, cancellationToken).ConfigureAwait(false);
+            }
+        }
+    }
+
     /// <summary>
     /// Asynchronously extracts all the files from the zip archive stored in the specified stream and places them in the specified destination directory on the file system.
     /// </summary>
@@ -215,7 +288,7 @@ public static async Task ExtractToDirectoryAsync(string sourceArchiveFileName, s
     /// Exceptions related to validating the paths in the <paramref name="destinationDirectoryName"/> or the files in the zip archive contained in <paramref name="source"/> parameters are thrown before extraction. Otherwise, if an error occurs during extraction, the archive remains partially extracted.
     /// Each extracted file has the same relative path to the directory specified by <paramref name="destinationDirectoryName"/> as its source entry has to the root of the archive.
     /// If a file to be archived has an invalid last modified time, the first date and time representable in the Zip timestamp format (midnight on January 1, 1980) will be used.</remarks>
-    /// <exception cref="ArgumentException"><paramref name="destinationDirectoryName" />> is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.</exception>
+    /// <exception cref="ArgumentException"><paramref name="destinationDirectoryName" /> is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.</exception>
     /// <exception cref="ArgumentNullException"><paramref name="destinationDirectoryName" /> or <paramref name="source" /> is <see langword="null" />.</exception>
     /// <exception cref="PathTooLongException">The specified path in <paramref name="destinationDirectoryName" /> exceeds the system-defined maximum length.</exception>
     /// <exception cref="DirectoryNotFoundException">The specified path is invalid (for example, it is on an unmapped drive).</exception>
@@ -247,7 +320,7 @@ public static Task ExtractToDirectoryAsync(Stream source, string destinationDire
     /// Exceptions related to validating the paths in the <paramref name="destinationDirectoryName"/> or the files in the zip archive contained in <paramref name="source"/> parameters are thrown before extraction. Otherwise, if an error occurs during extraction, the archive remains partially extracted.
     /// Each extracted file has the same relative path to the directory specified by <paramref name="destinationDirectoryName"/> as its source entry has to the root of the archive.
     /// If a file to be archived has an invalid last modified time, the first date and time representable in the Zip timestamp format (midnight on January 1, 1980) will be used.</remarks>
-    /// <exception cref="ArgumentException"><paramref name="destinationDirectoryName" />> is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.</exception>
+    /// <exception cref="ArgumentException"><paramref name="destinationDirectoryName" /> is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.</exception>
     /// <exception cref="ArgumentNullException"><paramref name="destinationDirectoryName" /> or <paramref name="source" /> is <see langword="null" />.</exception>
     /// <exception cref="PathTooLongException">The specified path in <paramref name="destinationDirectoryName" /> exceeds the system-defined maximum length.</exception>
     /// <exception cref="DirectoryNotFoundException">The specified path is invalid (for example, it is on an unmapped drive).</exception>
@@ -285,7 +358,7 @@ public static Task ExtractToDirectoryAsync(Stream source, string destinationDire
     /// If <paramref name="entryNameEncoding"/> is set to <see langword="null"/>, entry names and comments are decoded according to the following rules:
     /// - For entries where the language encoding flag (in the general-purpose bit flag of the local file header) is not set, entry names and comments are decoded by using the current system default code page.
     /// - For entries where the language encoding flag is set, the entry names and comments are decoded by using UTF-8.
-    /// <exception cref="ArgumentException"><paramref name="destinationDirectoryName" />> is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.
+    /// <exception cref="ArgumentException"><paramref name="destinationDirectoryName" /> is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.
     /// -or-
     /// <paramref name="entryNameEncoding"/> is set to a Unicode encoding other than UTF-8.</exception>
     /// <exception cref="ArgumentNullException"><paramref name="destinationDirectoryName" /> or <paramref name="source" /> is <see langword="null" />.</exception>
@@ -326,7 +399,7 @@ public static Task ExtractToDirectoryAsync(Stream source, string destinationDire
     /// If <paramref name="entryNameEncoding"/> is set to <see langword="null"/>, entry names and comments are decoded according to the following rules:
     /// - For entries where the language encoding flag (in the general-purpose bit flag of the local file header) is not set, entry names are decoded by using the current system default code page.
     /// - For entries where the language encoding flag is set, the entry names and comments are decoded by using UTF-8.
-    /// <exception cref="ArgumentException"><paramref name="destinationDirectoryName" />> is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.
+    /// <exception cref="ArgumentException"><paramref name="destinationDirectoryName" /> is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.
     /// -or-
     /// <paramref name="entryNameEncoding"/> is set to a Unicode encoding other than UTF-8.</exception>
     /// <exception cref="ArgumentNullException"><paramref name="destinationDirectoryName" /> or <paramref name="source" /> is <see langword="null" />.</exception>
@@ -362,4 +435,94 @@ public static async Task ExtractToDirectoryAsync(Stream source, string destinati
             await archive.ExtractToDirectoryAsync(destinationDirectoryName, overwriteFiles, cancellationToken).ConfigureAwait(false);
         }
     }
+
+    /// <summary>
+    /// Asynchronously extracts all the files from the password-protected zip archive stored in the specified stream and places them in the specified destination directory on the file system, uses the specified character encoding for entry names, and optionally allows choosing if the files in the destination directory should be overwritten.
+    /// </summary>
+    /// <param name="source">The stream from which the zip archive is to be extracted.</param>
+    /// <param name="destinationDirectoryName">The path to the directory in which to place the extracted files, specified as a relative or absolute path. A relative path is interpreted as relative to the current working directory.</param>
+    /// <param name="entryNameEncoding">The encoding to use when reading or writing entry names and comments in this archive. Specify a value for this parameter only when an encoding is required for interoperability with zip archive tools and libraries that do not support UTF-8 encoding for entry names or comments.</param>
+    /// <param name="overwriteFiles"><see langword="true" /> to overwrite files; <see langword="false" /> otherwise.</param>
+    /// <param name="password">The password used to decrypt the encrypted entries in the archive.</param>
+    /// <param name="cancellationToken">The cancellation token to monitor for cancellation requests.</param>
+    /// <remarks> This method creates the specified directory and all subdirectories. The destination directory cannot already exist.
+    /// Exceptions related to validating the paths in the <paramref name="destinationDirectoryName"/> or the files in the zip archive contained in <paramref name="source"/> parameters are thrown before extraction. Otherwise, if an error occurs during extraction, the archive remains partially extracted.
+    /// Each extracted file has the same relative path to the directory specified by <paramref name="destinationDirectoryName"/> as its source entry has to the root of the archive.
+    /// If a file to be archived has an invalid last modified time, the first date and time representable in the Zip timestamp format (midnight on January 1, 1980) will be used.</remarks>
+    /// If <paramref name="entryNameEncoding"/> is set to a value other than <see langword="null"/>, entry names and comments are decoded according to the following rules:
+    /// - For entry names and comments where the language encoding flag (in the general-purpose bit flag of the local file header) is not set, the entry names and comments are decoded by using the specified encoding.
+    /// - For entries where the language encoding flag is set, the entry names and comments are decoded by using UTF-8.
+    /// If <paramref name="entryNameEncoding"/> is set to <see langword="null"/>, entry names and comments are decoded according to the following rules:
+    /// - For entries where the language encoding flag (in the general-purpose bit flag of the local file header) is not set, entry names are decoded by using the current system default code page.
+    /// - For entries where the language encoding flag is set, the entry names and comments are decoded by using UTF-8.
+    /// <exception cref="ArgumentException"><paramref name="destinationDirectoryName" /> is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.
+    /// -or-
+    /// <paramref name="entryNameEncoding"/> is set to a Unicode encoding other than UTF-8.</exception>
+    /// <exception cref="ArgumentNullException"><paramref name="destinationDirectoryName" /> or <paramref name="source" /> is <see langword="null" />.</exception>
+    /// <exception cref="PathTooLongException">The specified path in <paramref name="destinationDirectoryName" /> exceeds the system-defined maximum length.</exception>
+    /// <exception cref="DirectoryNotFoundException">The specified path is invalid (for example, it is on an unmapped drive).</exception>
+    /// <exception cref="IOException">The name of an entry in the archive is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.
+    /// -or-
+    /// Extracting an archive entry would create a file that is outside the directory specified by <paramref name="destinationDirectoryName" />. (For example, this might happen if the entry name contains parent directory accessors.)
+    /// -or-
+    /// <paramref name="overwriteFiles" /> is <see langword="false" /> and an archive entry to extract has the same name as an entry that has already been extracted or that exists in <paramref name="destinationDirectoryName" />.</exception>
+    /// <exception cref="UnauthorizedAccessException">The caller does not have the required permission to access the archive or the destination directory.</exception>
+    /// <exception cref="NotSupportedException"><paramref name="destinationDirectoryName" /> contains an invalid format.</exception>
+    /// <exception cref="InvalidDataException">The archive contained in the <paramref name="source" /> stream is not a valid zip archive.
+    /// -or-
+    /// An archive entry was not found or was corrupt.
+    /// -or-
+    /// An archive entry was compressed by using a compression method that is not supported.</exception>
+    /// <exception cref="OperationCanceledException">An asynchronous operation is cancelled.</exception>
+    /// <returns>A task that represents the asynchronous extract operation. The task completes when all entries have been extracted or an error occurs.</returns>
+    private static async Task ExtractToDirectoryAsync(Stream source, string destinationDirectoryName, Encoding? entryNameEncoding, bool overwriteFiles, ReadOnlyMemory<char> password, CancellationToken cancellationToken = default)
+    {
+        cancellationToken.ThrowIfCancellationRequested();
+
+        ArgumentNullException.ThrowIfNull(source);
+        if (!source.CanRead)
+        {
+            throw new ArgumentException(SR.UnreadableStream, nameof(source));
+        }
+
+        ZipArchive archive = await ZipArchive.CreateAsync(source, ZipArchiveMode.Read, leaveOpen: true, entryNameEncoding, cancellationToken).ConfigureAwait(false);
+        await using (archive)
+        {
+            foreach (ZipArchiveEntry entry in archive.Entries)
+            {
+                cancellationToken.ThrowIfCancellationRequested();
+                await entry.ExtractRelativeToDirectoryAsync(destinationDirectoryName, overwriteFiles, password, cancellationToken).ConfigureAwait(false);
+            }
+        }
+    }
+
+    /// <summary>
+    /// Asynchronously extracts all of the files in the specified archive to a directory on the file system using the specified options.
+    /// </summary>
+    /// <param name="sourceArchiveFileName">The path to the archive on the file system that is to be extracted.</param>
+    /// <param name="destinationDirectoryName">The path to the directory in which to place the extracted files.</param>
+    /// <param name="options">The extraction options including password, encoding, and overwrite behavior.</param>
+    /// <param name="cancellationToken">The cancellation token to monitor for cancellation requests.</param>
+    /// <exception cref="ArgumentNullException"><paramref name="sourceArchiveFileName"/>, <paramref name="destinationDirectoryName"/>, or <paramref name="options"/> is <see langword="null"/>.</exception>
+    public static Task ExtractToDirectoryAsync(string sourceArchiveFileName, string destinationDirectoryName, ZipExtractionOptions options, CancellationToken cancellationToken = default)
+    {
+        ArgumentNullException.ThrowIfNull(options);
+
+        return ExtractToDirectoryAsync(sourceArchiveFileName, destinationDirectoryName, options.EntryNameEncoding, options.OverwriteFiles, options.Password, cancellationToken);
+    }
+
+    /// <summary>
+    /// Asynchronously extracts all of the files in the specified stream to a directory on the file system using the specified options.
+    /// </summary>
+    /// <param name="source">The stream containing the archive to extract.</param>
+    /// <param name="destinationDirectoryName">The path to the directory in which to place the extracted files.</param>
+    /// <param name="options">The extraction options including password, encoding, and overwrite behavior.</param>
+    /// <param name="cancellationToken">The cancellation token to monitor for cancellation requests.</param>
+    /// <exception cref="ArgumentNullException"><paramref name="source"/>, <paramref name="destinationDirectoryName"/>, or <paramref name="options"/> is <see langword="null"/>.</exception>
+    public static Task ExtractToDirectoryAsync(Stream source, string destinationDirectoryName, ZipExtractionOptions options, CancellationToken cancellationToken = default)
+    {
+        ArgumentNullException.ThrowIfNull(options);
+
+        return ExtractToDirectoryAsync(source, destinationDirectoryName, options.EntryNameEncoding, options.OverwriteFiles, options.Password, cancellationToken);
+    }
 }
diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.cs
index cd2b78e64eb558..8328d119cc80ef 100644
--- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.cs
+++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.cs
@@ -102,7 +102,7 @@ public static void ExtractToDirectory(string sourceArchiveFileName, string desti
         /// <param name="sourceArchiveFileName">The path to the archive on the file system that is to be extracted.</param>
         /// <param name="destinationDirectoryName">The path to the directory on the file system. The directory specified must not exist, but the directory that it is contained in must exist.</param>
         /// <param name="entryNameEncoding">The encoding to use when reading or writing entry names and comments in this ZipArchive.
-        ///         ///     <para>NOTE: Specifying this parameter to values other than <c>null</c> is discouraged.
+        ///     <para>NOTE: Specifying this parameter to values other than <c>null</c> is discouraged.
         ///         However, this may be necessary for interoperability with ZIP archive tools and libraries that do not correctly support
         ///         UTF-8 encoding for entry names or comments.<br />
         ///         This value is used as follows:</para>
@@ -157,7 +157,7 @@ public static void ExtractToDirectory(string sourceArchiveFileName, string desti
         /// <param name="destinationDirectoryName">The path to the directory in which to place the extracted files, specified as a relative or absolute path. A relative path is interpreted as relative to the current working directory.</param>
         /// <param name="overwriteFiles">True to indicate overwrite.</param>
         /// <param name="entryNameEncoding">The encoding to use when reading or writing entry names and comments in this ZipArchive.
-        ///         ///     <para>NOTE: Specifying this parameter to values other than <c>null</c> is discouraged.
+        ///     <para>NOTE: Specifying this parameter to values other than <c>null</c> is discouraged.
         ///         However, this may be necessary for interoperability with ZIP archive tools and libraries that do not correctly support
         ///         UTF-8 encoding for entry names or comments.<br />
         ///         This value is used as follows:</para>
@@ -188,6 +188,70 @@ public static void ExtractToDirectory(string sourceArchiveFileName, string desti
             }
         }
 
+        /// <summary>
+        /// Extracts all of the files in the specified password-protected archive to a directory on the file system.
+        /// The specified directory must not exist. This method will create all subdirectories and the specified directory.
+        /// If there is an error while extracting the archive, the archive will remain partially extracted. Each entry will
+        /// be extracted such that the extracted file has the same relative path to the destinationDirectoryName as the entry
+        /// has to the archive. The path is permitted to specify relative or absolute path information. Relative path information
+        /// is interpreted as relative to the current working directory. If a file to be archived has an invalid last modified
+        /// time, the first datetime representable in the Zip timestamp format (midnight on January 1, 1980) will be used.
+        /// </summary>
+        ///
+        /// <exception cref="ArgumentException">sourceArchive or destinationDirectoryName is a zero-length string, contains only whitespace,
+        /// or contains one or more invalid characters as defined by InvalidPathChars.</exception>
+        /// <exception cref="ArgumentNullException">sourceArchive or destinationDirectoryName is null.</exception>
+        /// <exception cref="PathTooLongException">sourceArchive or destinationDirectoryName specifies a path, file name,
+        /// or both exceed the system-defined maximum length. For example, on Windows-based platforms, paths must be less than 248 characters,
+        /// and file names must be less than 260 characters.</exception>
+        /// <exception cref="DirectoryNotFoundException">The path specified by sourceArchive or destinationDirectoryName is invalid,
+        /// (for example, it is on an unmapped drive).</exception>
+        /// <exception cref="IOException">An I/O error has occurred. -or- An archive entry's name is zero-length, contains only whitespace, or contains one or
+        /// more invalid characters as defined by InvalidPathChars. -or- Extracting an archive entry would result in a file destination that is outside the destination directory (for example, because of parent directory accessors). -or- An archive entry has the same name as an already extracted entry from the same archive.</exception>
+        /// <exception cref="UnauthorizedAccessException">The caller does not have the required permission.</exception>
+        /// <exception cref="NotSupportedException">sourceArchive or destinationDirectoryName is in an invalid format. </exception>
+        /// <exception cref="FileNotFoundException">sourceArchive was not found.</exception>
+        /// <exception cref="InvalidDataException">The archive specified by sourceArchive: Is not a valid ZipArchive
+        /// -or- An archive entry was not found or was corrupt. -or- An archive entry has been compressed using a compression method
+        /// that is not supported.</exception>
+        ///
+        /// <param name="sourceArchiveFileName">The path to the archive on the file system that is to be extracted.</param>
+        /// <param name="destinationDirectoryName">The path to the directory in which to place the extracted files, specified as a relative or absolute path. A relative path is interpreted as relative to the current working directory.</param>
+        /// <param name="overwriteFiles">True to indicate overwrite.</param>
+        /// <param name="entryNameEncoding">The encoding to use when reading or writing entry names and comments in this ZipArchive.
+        ///     <para>NOTE: Specifying this parameter to values other than <c>null</c> is discouraged.
+        ///         However, this may be necessary for interoperability with ZIP archive tools and libraries that do not correctly support
+        ///         UTF-8 encoding for entry names or comments.<br />
+        ///         This value is used as follows:</para>
+        ///     <para>If <c>entryNameEncoding</c> is not specified (<c>== null</c>):</para>
+        ///     <list>
+        ///         <item>For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is <em>not</em> set,
+        ///         use the current system default code page (<c>Encoding.Default</c>) in order to decode the entry name and comment.</item>
+        ///         <item>For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header <em>is</em> set,
+        ///         use UTF-8 (<c>Encoding.UTF8</c>) in order to decode the entry name and comment.</item>
+        ///     </list>
+        ///     <para>If <c>entryNameEncoding</c> is specified (<c>!= null</c>):</para>
+        ///     <list>
+        ///         <item>For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is <em>not</em> set,
+        ///         use the specified <c>entryNameEncoding</c> in order to decode the entry name and comment.</item>
+        ///         <item>For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header <em>is</em> set,
+        ///         use UTF-8 (<c>Encoding.UTF8</c>) in order to decode the entry name and comment.</item>
+        ///     </list>
+        ///     <para>Note that Unicode encodings other than UTF-8 may not be currently used for the <c>entryNameEncoding</c>,
+        ///     otherwise an <see cref="ArgumentException"/> is thrown.</para>
+        /// </param>
+        /// <param name="password">The password used to decrypt the encrypted entries in the archive.</param>
+        private static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, Encoding? entryNameEncoding, bool overwriteFiles, ReadOnlySpan<char> password)
+        {
+            ArgumentNullException.ThrowIfNull(sourceArchiveFileName);
+
+            using ZipArchive archive = Open(sourceArchiveFileName, ZipArchiveMode.Read, entryNameEncoding);
+            foreach (ZipArchiveEntry entry in archive.Entries)
+            {
+                entry.ExtractRelativeToDirectory(destinationDirectoryName, overwriteFiles, password);
+            }
+        }
+
         /// <summary>
         /// Extracts all the files from the zip archive stored in the specified stream and places them in the specified destination directory on the file system.
         /// </summary>
@@ -197,7 +261,7 @@ public static void ExtractToDirectory(string sourceArchiveFileName, string desti
         /// Exceptions related to validating the paths in the <paramref name="destinationDirectoryName"/> or the files in the zip archive contained in <paramref name="source"/> parameters are thrown before extraction. Otherwise, if an error occurs during extraction, the archive remains partially extracted.
         /// Each extracted file has the same relative path to the directory specified by <paramref name="destinationDirectoryName"/> as its source entry has to the root of the archive.
         /// If a file to be archived has an invalid last modified time, the first date and time representable in the Zip timestamp format (midnight on January 1, 1980) will be used.</remarks>
-        /// <exception cref="ArgumentException"><paramref name="destinationDirectoryName" />> is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.</exception>
+        /// <exception cref="ArgumentException"><paramref name="destinationDirectoryName" /> is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.</exception>
         /// <exception cref="ArgumentNullException"><paramref name="destinationDirectoryName" /> or <paramref name="source" /> is <see langword="null" />.</exception>
         /// <exception cref="PathTooLongException">The specified path in <paramref name="destinationDirectoryName" /> exceeds the system-defined maximum length.</exception>
         /// <exception cref="DirectoryNotFoundException">The specified path is invalid (for example, it is on an unmapped drive).</exception>
@@ -226,7 +290,7 @@ public static void ExtractToDirectory(Stream source, string destinationDirectory
         /// Exceptions related to validating the paths in the <paramref name="destinationDirectoryName"/> or the files in the zip archive contained in <paramref name="source"/> parameters are thrown before extraction. Otherwise, if an error occurs during extraction, the archive remains partially extracted.
         /// Each extracted file has the same relative path to the directory specified by <paramref name="destinationDirectoryName"/> as its source entry has to the root of the archive.
         /// If a file to be archived has an invalid last modified time, the first date and time representable in the Zip timestamp format (midnight on January 1, 1980) will be used.</remarks>
-        /// <exception cref="ArgumentException"><paramref name="destinationDirectoryName" />> is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.</exception>
+        /// <exception cref="ArgumentException"><paramref name="destinationDirectoryName" /> is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.</exception>
         /// <exception cref="ArgumentNullException"><paramref name="destinationDirectoryName" /> or <paramref name="source" /> is <see langword="null" />.</exception>
         /// <exception cref="PathTooLongException">The specified path in <paramref name="destinationDirectoryName" /> exceeds the system-defined maximum length.</exception>
         /// <exception cref="DirectoryNotFoundException">The specified path is invalid (for example, it is on an unmapped drive).</exception>
@@ -261,7 +325,7 @@ public static void ExtractToDirectory(Stream source, string destinationDirectory
         /// If <paramref name="entryNameEncoding"/> is set to <see langword="null"/>, entry names and comments are decoded according to the following rules:
         /// - For entries where the language encoding flag (in the general-purpose bit flag of the local file header) is not set, entry names and comments are decoded by using the current system default code page.
         /// - For entries where the language encoding flag is set, the entry names and comments are decoded by using UTF-8.
-        /// <exception cref="ArgumentException"><paramref name="destinationDirectoryName" />> is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.
+        /// <exception cref="ArgumentException"><paramref name="destinationDirectoryName" /> is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.
         /// -or-
         /// <paramref name="entryNameEncoding"/> is set to a Unicode encoding other than UTF-8.</exception>
         /// <exception cref="ArgumentNullException"><paramref name="destinationDirectoryName" /> or <paramref name="source" /> is <see langword="null" />.</exception>
@@ -299,7 +363,7 @@ public static void ExtractToDirectory(Stream source, string destinationDirectory
         /// If <paramref name="entryNameEncoding"/> is set to <see langword="null"/>, entry names and comments are decoded according to the following rules:
         /// - For entries where the language encoding flag (in the general-purpose bit flag of the local file header) is not set, entry names are decoded by using the current system default code page.
         /// - For entries where the language encoding flag is set, the entry names and comments are decoded by using UTF-8.
-        /// <exception cref="ArgumentException"><paramref name="destinationDirectoryName" />> is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.
+        /// <exception cref="ArgumentException"><paramref name="destinationDirectoryName" /> is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.
         /// -or-
         /// <paramref name="entryNameEncoding"/> is set to a Unicode encoding other than UTF-8.</exception>
         /// <exception cref="ArgumentNullException"><paramref name="destinationDirectoryName" /> or <paramref name="source" /> is <see langword="null" />.</exception>
@@ -328,5 +392,84 @@ public static void ExtractToDirectory(Stream source, string destinationDirectory
             using ZipArchive archive = new ZipArchive(source, ZipArchiveMode.Read, leaveOpen: true, entryNameEncoding);
             archive.ExtractToDirectory(destinationDirectoryName, overwriteFiles);
         }
+
+        /// <summary>
+        /// Extracts all the files from the password-protected zip archive stored in the specified stream and places them in the specified destination directory on the file system, uses the specified character encoding for entry names, and optionally allows choosing if the files in the destination directory should be overwritten.
+        /// </summary>
+        /// <param name="source">The stream from which the zip archive is to be extracted.</param>
+        /// <param name="destinationDirectoryName">The path to the directory in which to place the extracted files, specified as a relative or absolute path. A relative path is interpreted as relative to the current working directory.</param>
+        /// <param name="entryNameEncoding">The encoding to use when reading or writing entry names and comments in this archive. Specify a value for this parameter only when an encoding is required for interoperability with zip archive tools and libraries that do not support UTF-8 encoding for entry names or comments.</param>
+        /// <param name="overwriteFiles"><see langword="true" /> to overwrite files; <see langword="false" /> otherwise.</param>
+        /// <param name="password">The password used to decrypt the encrypted entries in the archive.</param>
+        /// <remarks> This method creates the specified directory and all subdirectories. The destination directory cannot already exist.
+        /// Exceptions related to validating the paths in the <paramref name="destinationDirectoryName"/> or the files in the zip archive contained in <paramref name="source"/> parameters are thrown before extraction. Otherwise, if an error occurs during extraction, the archive remains partially extracted.
+        /// Each extracted file has the same relative path to the directory specified by <paramref name="destinationDirectoryName"/> as its source entry has to the root of the archive.
+        /// If a file to be archived has an invalid last modified time, the first date and time representable in the Zip timestamp format (midnight on January 1, 1980) will be used.</remarks>
+        /// If <paramref name="entryNameEncoding"/> is set to a value other than <see langword="null"/>, entry names and comments are decoded according to the following rules:
+        /// - For entry names and comments where the language encoding flag (in the general-purpose bit flag of the local file header) is not set, the entry names and comments are decoded by using the specified encoding.
+        /// - For entries where the language encoding flag is set, the entry names and comments are decoded by using UTF-8.
+        /// If <paramref name="entryNameEncoding"/> is set to <see langword="null"/>, entry names and comments are decoded according to the following rules:
+        /// - For entries where the language encoding flag (in the general-purpose bit flag of the local file header) is not set, entry names are decoded by using the current system default code page.
+        /// - For entries where the language encoding flag is set, the entry names and comments are decoded by using UTF-8.
+        /// <exception cref="ArgumentException"><paramref name="destinationDirectoryName" /> is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.
+        /// -or-
+        /// <paramref name="entryNameEncoding"/> is set to a Unicode encoding other than UTF-8.</exception>
+        /// <exception cref="ArgumentNullException"><paramref name="destinationDirectoryName" /> or <paramref name="source" /> is <see langword="null" />.</exception>
+        /// <exception cref="PathTooLongException">The specified path in <paramref name="destinationDirectoryName" /> exceeds the system-defined maximum length.</exception>
+        /// <exception cref="DirectoryNotFoundException">The specified path is invalid (for example, it is on an unmapped drive).</exception>
+        /// <exception cref="IOException">The name of an entry in the archive is <see cref="string.Empty" />, contains only white space, or contains at least one invalid character.
+        /// -or-
+        /// Extracting an archive entry would create a file that is outside the directory specified by <paramref name="destinationDirectoryName" />. (For example, this might happen if the entry name contains parent directory accessors.)
+        /// -or-
+        /// <paramref name="overwriteFiles" /> is <see langword="false" /> and an archive entry to extract has the same name as an entry that has already been extracted or that exists in <paramref name="destinationDirectoryName" />.</exception>
+        /// <exception cref="UnauthorizedAccessException">The caller does not have the required permission to access the archive or the destination directory.</exception>
+        /// <exception cref="NotSupportedException"><paramref name="destinationDirectoryName" /> contains an invalid format.</exception>
+        /// <exception cref="InvalidDataException">The archive contained in the <paramref name="source" /> stream is not a valid zip archive.
+        /// -or-
+        /// An archive entry was not found or was corrupt.
+        /// -or-
+        /// An archive entry was compressed by using a compression method that is not supported.</exception>
+        private static void ExtractToDirectory(Stream source, string destinationDirectoryName, Encoding? entryNameEncoding, bool overwriteFiles, ReadOnlySpan<char> password)
+        {
+            ArgumentNullException.ThrowIfNull(source);
+            if (!source.CanRead)
+            {
+                throw new ArgumentException(SR.UnreadableStream, nameof(source));
+            }
+
+            using ZipArchive archive = new ZipArchive(source, ZipArchiveMode.Read, leaveOpen: true, entryNameEncoding);
+            foreach (ZipArchiveEntry entry in archive.Entries)
+            {
+                entry.ExtractRelativeToDirectory(destinationDirectoryName, overwriteFiles, password);
+            }
+        }
+
+        /// <summary>
+        /// Extracts all of the files in the specified archive to a directory on the file system using the specified options.
+        /// </summary>
+        /// <param name="sourceArchiveFileName">The path to the archive on the file system that is to be extracted.</param>
+        /// <param name="destinationDirectoryName">The path to the directory in which to place the extracted files.</param>
+        /// <param name="options">The extraction options including password, encoding, and overwrite behavior.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="sourceArchiveFileName"/>, <paramref name="destinationDirectoryName"/>, or <paramref name="options"/> is <see langword="null"/>.</exception>
+        public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, ZipExtractionOptions options)
+        {
+            ArgumentNullException.ThrowIfNull(options);
+
+            ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, options.EntryNameEncoding, options.OverwriteFiles, options.Password.Span);
+        }
+
+        /// <summary>
+        /// Extracts all of the files in the specified stream to a directory on the file system using the specified options.
+        /// </summary>
+        /// <param name="source">The stream containing the archive to extract.</param>
+        /// <param name="destinationDirectoryName">The path to the directory in which to place the extracted files.</param>
+        /// <param name="options">The extraction options including password, encoding, and overwrite behavior.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="source"/>, <paramref name="destinationDirectoryName"/>, or <paramref name="options"/> is <see langword="null"/>.</exception>
+        public static void ExtractToDirectory(Stream source, string destinationDirectoryName, ZipExtractionOptions options)
+        {
+            ArgumentNullException.ThrowIfNull(options);
+
+            ExtractToDirectory(source, destinationDirectoryName, options.EntryNameEncoding, options.OverwriteFiles, options.Password.Span);
+        }
     }
 }
diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileCreationOptions.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileCreationOptions.cs
new file mode 100644
index 00000000000000..06ecdeb5203659
--- /dev/null
+++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileCreationOptions.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text;
+
+namespace System.IO.Compression;
+
+/// <summary>
+/// Options for creating a zip archive from a directory.
+/// </summary>
+public sealed class ZipFileCreationOptions
+{
+    /// <summary>
+    /// Gets or sets the password used to encrypt entries in the archive.
+    /// </summary>
+    public ReadOnlyMemory<char> Password { get; set; }
+
+    /// <summary>
+    /// Gets or sets the encryption method to use when creating encrypted entries.
+    /// </summary>
+    public ZipEncryptionMethod EncryptionMethod { get; set; }
+
+    /// <summary>
+    /// Gets or sets the compression level to use when creating entries.
+    /// </summary>
+    public CompressionLevel CompressionLevel { get; set; }
+
+    /// <summary>
+    /// Gets or sets the encoding to use for entry names.
+    /// </summary>
+    public Encoding? EntryNameEncoding { get; set; }
+
+    /// <summary>
+    /// Gets or sets a value indicating whether to include the base directory name as a prefix in the entry names.
+    /// </summary>
+    public bool IncludeBaseDirectory { get; set; }
+}
diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.Async.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.Async.cs
index dd60c9c768986e..ce890f32dbc4e3 100644
--- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.Async.cs
+++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.Async.cs
@@ -44,7 +44,36 @@ public static partial class ZipFileExtensions
     /// <param name="cancellationToken">The cancellation token to monitor for cancellation requests.</param>
     /// <returns>A task that represents the asynchronous operation. The value of the task is the newly created entry.</returns>
     public static Task<ZipArchiveEntry> CreateEntryFromFileAsync(this ZipArchive destination, string sourceFileName, string entryName, CancellationToken cancellationToken = default) =>
-        DoCreateEntryFromFileAsync(destination, sourceFileName, entryName, null, cancellationToken);
+        DoCreateEntryFromFileAsync(destination, sourceFileName, entryName, null, cancellationToken: cancellationToken);
+
+    /// <summary>
+    /// <p>Asynchronously adds a file from the file system to the archive under the specified entry name using encryption.</p>
+    /// </summary>
+    /// <exception cref="ArgumentException">
+    /// sourceFileName is a zero-length string, contains only whitespace, or contains invalid characters.
+    /// -or- entryName is a zero-length string.
+    /// -or- password is null or empty when encryption is not None.
+    /// </exception>
+    /// <exception cref="ArgumentNullException">sourceFileName or entryName is null.</exception>
+    /// <exception cref="PathTooLongException">In sourceFileName, the specified path, file name, or both exceed the system-defined maximum length.</exception>
+    /// <exception cref="DirectoryNotFoundException">The specified sourceFileName is invalid.</exception>
+    /// <exception cref="IOException">An I/O error occurred while opening the file specified by sourceFileName.</exception>
+    /// <exception cref="UnauthorizedAccessException">sourceFileName specified a directory, or the caller lacks permission.</exception>
+    /// <exception cref="FileNotFoundException">The file specified in sourceFileName was not found.</exception>
+    /// <exception cref="NotSupportedException">sourceFileName is in an invalid format or the ZipArchive does not support writing.</exception>
+    /// <exception cref="ObjectDisposedException">The ZipArchive has already been closed.</exception>
+    /// <exception cref="OperationCanceledException">An asynchronous operation is cancelled.</exception>
+    ///
+    /// <param name="destination">The zip archive to add the file to.</param>
+    /// <param name="sourceFileName">The path to the file on the file system to be copied from.</param>
+    /// <param name="entryName">The name of the entry to be created.</param>
+    /// <param name="password">The password to use for encrypting the entry.</param>
+    /// <param name="encryption">The encryption method to use.</param>
+    /// <param name="cancellationToken">The cancellation token to monitor for cancellation requests.</param>
+    /// <returns>A task that represents the asynchronous operation. The value of the task is the newly created entry.</returns>
+    public static Task<ZipArchiveEntry> CreateEntryFromFileAsync(this ZipArchive destination,
+        string sourceFileName, string entryName, ReadOnlyMemory<char> password, ZipEncryptionMethod encryption, CancellationToken cancellationToken = default) =>
+        DoCreateEntryFromFileAsync(destination, sourceFileName, entryName, null, password, encryption, cancellationToken);
 
     /// <summary>
     /// <p>Asynchronously adds a file from the file system to the archive under the specified entry name.
@@ -78,18 +107,49 @@ public static Task<ZipArchiveEntry> CreateEntryFromFileAsync(this ZipArchive des
     /// <returns>A task that represents the asynchronous operation. The value of the task is the newly created entry.</returns>
     public static Task<ZipArchiveEntry> CreateEntryFromFileAsync(this ZipArchive destination,
                                                       string sourceFileName, string entryName, CompressionLevel compressionLevel, CancellationToken cancellationToken = default) =>
-        DoCreateEntryFromFileAsync(destination, sourceFileName, entryName, compressionLevel, cancellationToken);
+        DoCreateEntryFromFileAsync(destination, sourceFileName, entryName, compressionLevel, cancellationToken: cancellationToken);
+
+    /// <summary>
+    /// <p>Asynchronously adds a file from the file system to the archive under the specified entry name using the specified compression level and encryption.</p>
+    /// </summary>
+    /// <exception cref="ArgumentException">
+    /// sourceFileName is a zero-length string, contains only whitespace, or contains invalid characters.
+    /// -or- entryName is a zero-length string.
+    /// -or- password is null or empty when encryption is not None.
+    /// </exception>
+    /// <exception cref="ArgumentNullException">sourceFileName or entryName is null.</exception>
+    /// <exception cref="PathTooLongException">In sourceFileName, the specified path, file name, or both exceed the system-defined maximum length.</exception>
+    /// <exception cref="DirectoryNotFoundException">The specified sourceFileName is invalid.</exception>
+    /// <exception cref="IOException">An I/O error occurred while opening the file specified by sourceFileName.</exception>
+    /// <exception cref="UnauthorizedAccessException">sourceFileName specified a directory, or the caller lacks permission.</exception>
+    /// <exception cref="FileNotFoundException">The file specified in sourceFileName was not found.</exception>
+    /// <exception cref="NotSupportedException">sourceFileName is in an invalid format or the ZipArchive does not support writing.</exception>
+    /// <exception cref="ObjectDisposedException">The ZipArchive has already been closed.</exception>
+    /// <exception cref="OperationCanceledException">An asynchronous operation is cancelled.</exception>
+    ///
+    /// <param name="destination">The zip archive to add the file to.</param>
+    /// <param name="sourceFileName">The path to the file on the file system to be copied from.</param>
+    /// <param name="entryName">The name of the entry to be created.</param>
+    /// <param name="compressionLevel">The level of the compression (speed/memory vs. compressed size trade-off).</param>
+    /// <param name="password">The password to use for encrypting the entry.</param>
+    /// <param name="encryption">The encryption method to use.</param>
+    /// <param name="cancellationToken">The cancellation token to monitor for cancellation requests.</param>
+    /// <returns>A task that represents the asynchronous operation. The value of the task is the newly created entry.</returns>
+    public static Task<ZipArchiveEntry> CreateEntryFromFileAsync(this ZipArchive destination,
+        string sourceFileName, string entryName, CompressionLevel compressionLevel, ReadOnlyMemory<char> password, ZipEncryptionMethod encryption, CancellationToken cancellationToken = default) =>
+        DoCreateEntryFromFileAsync(destination, sourceFileName, entryName, compressionLevel, password, encryption, cancellationToken);
 
     internal static async Task<ZipArchiveEntry> DoCreateEntryFromFileAsync(this ZipArchive destination, string sourceFileName, string entryName,
-                                                    CompressionLevel? compressionLevel, CancellationToken cancellationToken)
+        CompressionLevel? compressionLevel, ReadOnlyMemory<char> password = default, ZipEncryptionMethod encryption = ZipEncryptionMethod.None, CancellationToken cancellationToken = default)
     {
         cancellationToken.ThrowIfCancellationRequested();
 
-        (FileStream fs, ZipArchiveEntry entry) = InitializeDoCreateEntryFromFile(destination, sourceFileName, entryName, compressionLevel, useAsync: true);
+        (FileStream fs, ZipArchiveEntry entry) = InitializeDoCreateEntryFromFile(destination, sourceFileName, entryName, compressionLevel, useAsync: true, password.Span, encryption);
 
         await using (fs)
         {
             Stream es = await entry.OpenAsync(cancellationToken).ConfigureAwait(false);
+
             await using (es)
             {
                 await fs.CopyToAsync(es, cancellationToken).ConfigureAwait(false);
@@ -98,5 +158,4 @@ internal static async Task<ZipArchiveEntry> DoCreateEntryFromFileAsync(this ZipA
 
         return entry;
     }
-
 }
diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs
index a523577247fd43..89f998cff8812c 100644
--- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs
+++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs
@@ -77,10 +77,62 @@ public static ZipArchiveEntry CreateEntryFromFile(this ZipArchive destination,
                                                           string sourceFileName, string entryName, CompressionLevel compressionLevel) =>
             DoCreateEntryFromFile(destination, sourceFileName, entryName, compressionLevel);
 
+        /// <summary>
+        /// Adds a file from the file system to the archive under the specified entry name with encryption.
+        /// The new entry in the archive will contain the contents of the file.
+        /// The last write time of the archive entry is set to the last write time of the file on the file system.
+        /// </summary>
+        /// <param name="destination">The zip archive to add the file to.</param>
+        /// <param name="sourceFileName">The path to the file on the file system to be copied from.</param>
+        /// <param name="entryName">The name of the entry to be created.</param>
+        /// <param name="password">The password used to encrypt the entry.</param>
+        /// <param name="encryption">The encryption method to use.</param>
+        /// <returns>A wrapper for the newly created entry.</returns>
+        public static ZipArchiveEntry CreateEntryFromFile(this ZipArchive destination, string sourceFileName, string entryName, ReadOnlySpan<char> password, ZipEncryptionMethod encryption) =>
+            DoCreateEntryFromFile(destination, sourceFileName, entryName, null, password, encryption);
+
+
+        /// <summary>
+        /// <p>Adds a file from the file system to the archive under the specified entry name with encryption.
+        /// The new entry in the archive will contain the contents of the file.
+        /// The last write time of the archive entry is set to the last write time of the file on the file system.
+        /// If an entry with the specified name already exists in the archive, a second entry will be created that has an identical name.
+        /// If the specified source file has an invalid last modified time, the first datetime representable in the Zip timestamp format
+        /// (midnight on January 1, 1980) will be used.</p>
+        /// <p>If an entry with the specified name already exists in the archive, a second entry will be created that has an identical name.</p>
+        /// </summary>
+        /// <exception cref="ArgumentException">sourceFileName is a zero-length string, contains only whitespace, or contains one or more
+        /// invalid characters as defined by InvalidPathChars. -or- entryName is a zero-length string. -or- password is null or empty when encryption is not None.</exception>
+        /// <exception cref="ArgumentNullException">sourceFileName or entryName is null.</exception>
+        /// <exception cref="PathTooLongException">In sourceFileName, the specified path, file name, or both exceed the system-defined maximum length.
+        /// For example, on Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 characters.</exception>
+        /// <exception cref="DirectoryNotFoundException">The specified sourceFileName is invalid, (for example, it is on an unmapped drive).</exception>
+        /// <exception cref="IOException">An I/O error occurred while opening the file specified by sourceFileName.</exception>
+        /// <exception cref="UnauthorizedAccessException">sourceFileName specified a directory.
+        /// -or- The caller does not have the required permission.</exception>
+        /// <exception cref="FileNotFoundException">The file specified in sourceFileName was not found. </exception>
+        /// <exception cref="NotSupportedException">sourceFileName is in an invalid format or the ZipArchive does not support writing.</exception>
+        /// <exception cref="ObjectDisposedException">The ZipArchive has already been closed.</exception>
+        ///
+        /// <param name="destination">The zip archive to add the file to.</param>
+        /// <param name="sourceFileName">The path to the file on the file system to be copied from. The path is permitted to specify relative
+        /// or absolute path information. Relative path information is interpreted as relative to the current working directory.</param>
+        /// <param name="entryName">The name of the entry to be created.</param>
+        /// <param name="compressionLevel">The level of the compression (speed/memory vs. compressed size trade-off).</param>
+        /// <param name="password">The password to use for encrypting the entry.</param>
+        /// <param name="encryption">The encryption method to use.</param>
+        /// <returns>A wrapper for the newly created entry.</returns>
+        public static ZipArchiveEntry CreateEntryFromFile(this ZipArchive destination,
+                                                          string sourceFileName, string entryName, CompressionLevel compressionLevel,
+                                                          ReadOnlySpan<char> password, ZipEncryptionMethod encryption) =>
+            DoCreateEntryFromFile(destination, sourceFileName, entryName, compressionLevel, password, encryption);
+
         internal static ZipArchiveEntry DoCreateEntryFromFile(this ZipArchive destination,
-                                                              string sourceFileName, string entryName, CompressionLevel? compressionLevel)
+                                                              string sourceFileName, string entryName, CompressionLevel? compressionLevel,
+                                                              ReadOnlySpan<char> password = default, ZipEncryptionMethod encryption = ZipEncryptionMethod.None)
         {
-            (FileStream fs, ZipArchiveEntry entry) = InitializeDoCreateEntryFromFile(destination, sourceFileName, entryName, compressionLevel, useAsync: true);
+
+            (FileStream fs, ZipArchiveEntry entry) = InitializeDoCreateEntryFromFile(destination, sourceFileName, entryName, compressionLevel, useAsync: false, password, encryption);
 
             using (fs)
             {
@@ -93,7 +145,7 @@ internal static ZipArchiveEntry DoCreateEntryFromFile(this ZipArchive destinatio
             return entry;
         }
 
-        private static (FileStream, ZipArchiveEntry) InitializeDoCreateEntryFromFile(ZipArchive destination, string sourceFileName, string entryName, CompressionLevel? compressionLevel, bool useAsync)
+        private static (FileStream, ZipArchiveEntry) InitializeDoCreateEntryFromFile(ZipArchive destination, string sourceFileName, string entryName, CompressionLevel? compressionLevel, bool useAsync, ReadOnlySpan<char> password = default, ZipEncryptionMethod encryption = ZipEncryptionMethod.None)
         {
             ArgumentNullException.ThrowIfNull(destination);
             ArgumentNullException.ThrowIfNull(sourceFileName);
@@ -106,9 +158,25 @@ private static (FileStream, ZipArchiveEntry) InitializeDoCreateEntryFromFile(Zip
 
             FileStream fs = new FileStream(sourceFileName, FileMode.Open, FileAccess.Read, FileShare.Read, ZipFile.FileStreamBufferSize, useAsync);
 
-            ZipArchiveEntry entry = compressionLevel.HasValue ?
-                                    destination.CreateEntry(entryName, compressionLevel.Value) :
-                                    destination.CreateEntry(entryName);
+            if (encryption != ZipEncryptionMethod.None && password.IsEmpty)
+            {
+                fs.Dispose();
+                throw new ArgumentException(SR.EmptyPassword, nameof(password));
+            }
+
+            ZipArchiveEntry entry;
+            if (!password.IsEmpty && encryption != ZipEncryptionMethod.None)
+            {
+                entry = compressionLevel.HasValue
+                    ? destination.CreateEntry(entryName, compressionLevel.Value, password, encryption)
+                    : destination.CreateEntry(entryName, password, encryption);
+            }
+            else
+            {
+                entry = compressionLevel.HasValue
+                    ? destination.CreateEntry(entryName, compressionLevel.Value)
+                    : destination.CreateEntry(entryName);
+            }
 
             DateTime lastWrite = File.GetLastWriteTime(sourceFileName);
 
diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.Async.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.Async.cs
index 8b42376813a2da..12a1788559f3d4 100644
--- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.Async.cs
+++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.Async.cs
@@ -79,7 +79,25 @@ public static async Task ExtractToDirectoryAsync(this ZipArchive source, string
 
         foreach (ZipArchiveEntry entry in source.Entries)
         {
-            await entry.ExtractRelativeToDirectoryAsync(destinationDirectoryName, overwriteFiles, cancellationToken).ConfigureAwait(false);
+            await entry.ExtractRelativeToDirectoryAsync(destinationDirectoryName, overwriteFiles, cancellationToken: cancellationToken).ConfigureAwait(false);
+        }
+    }
+
+    /// <summary>
+    /// Asynchronously extracts all of the files in the archive to a directory on the file system using the specified options.
+    /// </summary>
+    public static async Task ExtractToDirectoryAsync(this ZipArchive source, string destinationDirectoryName, ZipExtractionOptions options, CancellationToken cancellationToken = default)
+    {
+        ArgumentNullException.ThrowIfNull(source);
+        ArgumentNullException.ThrowIfNull(destinationDirectoryName);
+        ArgumentNullException.ThrowIfNull(options);
+
+        cancellationToken.ThrowIfCancellationRequested();
+
+        foreach (ZipArchiveEntry entry in source.Entries)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+            await entry.ExtractRelativeToDirectoryAsync(destinationDirectoryName, options.OverwriteFiles, options.Password, cancellationToken).ConfigureAwait(false);
         }
     }
 }
diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.cs
index ad9cfd4a6c2e63..2fd56fc2bfa60f 100644
--- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.cs
+++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.cs
@@ -20,7 +20,7 @@ public static partial class ZipFileExtensions
         /// <exception cref="PathTooLongException">The specified path, file name, or both exceed the system-defined maximum length.
         /// For example, on Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 characters.</exception>
         /// <exception cref="DirectoryNotFoundException">The specified path is invalid, (for example, it is on an unmapped drive).</exception>
-        /// <exception cref="IOException">An archive entry?s name is zero-length, contains only whitespace, or contains one or more invalid
+        /// <exception cref="IOException">An archive entry's name is zero-length, contains only whitespace, or contains one or more invalid
         /// characters as defined by InvalidPathChars. -or- Extracting an archive entry would have resulted in a destination
         /// file that is outside destinationDirectoryName (for example, if the entry name contains parent directory accessors).
         /// -or- An archive entry has the same name as an already extracted entry from the same archive.</exception>
@@ -50,7 +50,7 @@ public static void ExtractToDirectory(this ZipArchive source, string destination
         /// <exception cref="PathTooLongException">The specified path, file name, or both exceed the system-defined maximum length.
         /// For example, on Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 characters.</exception>
         /// <exception cref="DirectoryNotFoundException">The specified path is invalid, (for example, it is on an unmapped drive).</exception>
-        /// <exception cref="IOException">An archive entry?s name is zero-length, contains only whitespace, or contains one or more invalid
+        /// <exception cref="IOException">An archive entry's name is zero-length, contains only whitespace, or contains one or more invalid
         /// characters as defined by InvalidPathChars. -or- Extracting an archive entry would have resulted in a destination
         /// file that is outside destinationDirectoryName (for example, if the entry name contains parent directory accessors).
         /// -or- An archive entry has the same name as an already extracted entry from the same archive.</exception>
@@ -73,5 +73,24 @@ public static void ExtractToDirectory(this ZipArchive source, string destination
                 entry.ExtractRelativeToDirectory(destinationDirectoryName, overwriteFiles);
             }
         }
+
+        /// <summary>
+        /// Extracts all of the files in the archive to a directory on the file system using the specified options.
+        /// </summary>
+        /// <param name="source">The zip archive to extract files from.</param>
+        /// <param name="destinationDirectoryName">The path to the directory in which to place the extracted files.</param>
+        /// <param name="options">The extraction options including password, encoding, and overwrite behavior.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="source"/>, <paramref name="destinationDirectoryName"/>, or <paramref name="options"/> is <see langword="null"/>.</exception>
+        public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName, ZipExtractionOptions options)
+        {
+            ArgumentNullException.ThrowIfNull(source);
+            ArgumentNullException.ThrowIfNull(destinationDirectoryName);
+            ArgumentNullException.ThrowIfNull(options);
+
+            foreach (ZipArchiveEntry entry in source.Entries)
+            {
+                entry.ExtractRelativeToDirectory(destinationDirectoryName, options.OverwriteFiles, options.Password.Span);
+            }
+        }
     }
 }
diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Async.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Async.cs
index 591e51687e9225..45b6f77e983ec3 100644
--- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Async.cs
+++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Async.cs
@@ -118,7 +118,65 @@ public static async Task ExtractToFileAsync(this ZipArchiveEntry source, string
         }
     }
 
-    internal static async Task ExtractRelativeToDirectoryAsync(this ZipArchiveEntry source, string destinationDirectoryName, bool overwrite, CancellationToken cancellationToken = default)
+    /// <summary>
+    /// Asynchronously creates a file on the file system with the entry's contents using the specified extraction options.
+    /// </summary>
+    public static Task ExtractToFileAsync(this ZipArchiveEntry source, string destinationFileName, ZipExtractionOptions options, CancellationToken cancellationToken = default)
+    {
+        ArgumentNullException.ThrowIfNull(options);
+
+        return ExtractToFileAsync(source, destinationFileName, options.OverwriteFiles, options.Password, cancellationToken);
+    }
+
+    private static async Task ExtractToFileAsync(ZipArchiveEntry source, string destinationFileName, bool overwrite, ReadOnlyMemory<char> password, CancellationToken cancellationToken = default)
+    {
+        cancellationToken.ThrowIfCancellationRequested();
+
+        ExtractToFileInitialize(source, destinationFileName, overwrite, useAsync: true, out FileStreamOptions fileStreamOptions);
+
+        // When overwriting, extract to a temporary file first to avoid corrupting the destination file
+        // if an exception occurs during extraction (e.g., password-protected archive, corrupted data).
+        string extractPath = destinationFileName;
+        string? tempPath = null;
+
+        if (overwrite && File.Exists(destinationFileName))
+        {
+            tempPath = Path.GetTempFileName();
+            extractPath = tempPath;
+        }
+
+        try
+        {
+            FileStream fs = new FileStream(extractPath, fileStreamOptions);
+            await using (fs.ConfigureAwait(false))
+            {
+                Stream es = await source.OpenAsync(password.Span, cancellationToken: cancellationToken).ConfigureAwait(false);
+                await using (es.ConfigureAwait(false))
+                {
+                    await es.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
+                }
+            }
+
+            // Move the temporary file to the destination only after successful extraction
+            if (tempPath is not null)
+            {
+                File.Move(tempPath, destinationFileName, overwrite: true);
+            }
+
+            ExtractToFileFinalize(source, destinationFileName);
+        }
+        catch
+        {
+            // Clean up the temporary file if extraction failed
+            if (tempPath is not null && File.Exists(tempPath))
+            {
+                try { File.Delete(tempPath); } catch { }
+            }
+            throw;
+        }
+    }
+
+    internal static async Task ExtractRelativeToDirectoryAsync(this ZipArchiveEntry source, string destinationDirectoryName, bool overwrite, ReadOnlyMemory<char> password = default, CancellationToken cancellationToken = default)
     {
         cancellationToken.ThrowIfCancellationRequested();
 
@@ -127,7 +185,7 @@ internal static async Task ExtractRelativeToDirectoryAsync(this ZipArchiveEntry
             // If it is a file:
             // Create containing directory:
             Directory.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!);
-            await source.ExtractToFileAsync(fileDestinationPath, overwrite: overwrite, cancellationToken).ConfigureAwait(false);
+            await ExtractToFileAsync(source, fileDestinationPath, overwrite, password, cancellationToken).ConfigureAwait(false);
         }
     }
 }
diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs
index dfc670c3394c02..7cfd6df9c39ff1 100644
--- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs
+++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs
@@ -105,6 +105,62 @@ public static void ExtractToFile(this ZipArchiveEntry source, string destination
             }
         }
 
+        /// <summary>
+        /// Creates a file on the file system with the entry's contents using the specified extraction options.
+        /// </summary>
+        /// <param name="source">The zip archive entry to extract a file from.</param>
+        /// <param name="destinationFileName">The name of the file that will hold the contents of the entry.</param>
+        /// <param name="options">The extraction options including password and overwrite behavior.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="source"/>, <paramref name="destinationFileName"/>, or <paramref name="options"/> is <see langword="null"/>.</exception>
+        public static void ExtractToFile(this ZipArchiveEntry source, string destinationFileName, ZipExtractionOptions options)
+        {
+            ArgumentNullException.ThrowIfNull(options);
+
+            ExtractToFile(source, destinationFileName, options.OverwriteFiles, options.Password.Span);
+        }
+
+        private static void ExtractToFile(ZipArchiveEntry source, string destinationFileName, bool overwrite, ReadOnlySpan<char> password)
+        {
+            ExtractToFileInitialize(source, destinationFileName, overwrite, useAsync: false, out FileStreamOptions fileStreamOptions);
+
+            // When overwriting, extract to a temporary file first to avoid corrupting the destination file
+            // if an exception occurs during extraction (e.g., password-protected archive, corrupted data).
+            string extractPath = destinationFileName;
+            string? tempPath = null;
+
+            if (overwrite && File.Exists(destinationFileName))
+            {
+                tempPath = Path.GetTempFileName();
+                extractPath = tempPath;
+            }
+
+            try
+            {
+                using (FileStream fs = new FileStream(extractPath, fileStreamOptions))
+                {
+                    using (Stream es = source.Open(password))
+                        es.CopyTo(fs);
+                }
+
+                // Move the temporary file to the destination only after successful extraction
+                if (tempPath is not null)
+                {
+                    File.Move(tempPath, destinationFileName, overwrite: true);
+                }
+
+                ExtractToFileFinalize(source, destinationFileName);
+            }
+            catch
+            {
+                // Clean up the temporary file if extraction failed
+                if (tempPath is not null && File.Exists(tempPath))
+                {
+                    try { File.Delete(tempPath); } catch { }
+                }
+                throw;
+            }
+        }
+
         private static void ExtractToFileInitialize(ZipArchiveEntry source, string destinationFileName, bool overwrite, bool useAsync, out FileStreamOptions fileStreamOptions)
         {
             ArgumentNullException.ThrowIfNull(source);
@@ -185,14 +241,17 @@ private static bool ExtractRelativeToDirectoryCheckIfFile(ZipArchiveEntry source
             return true; // It is a file
         }
 
-        internal static void ExtractRelativeToDirectory(this ZipArchiveEntry source, string destinationDirectoryName, bool overwrite)
+        internal static void ExtractRelativeToDirectory(this ZipArchiveEntry source, string destinationDirectoryName, bool overwrite, ReadOnlySpan<char> password = default)
         {
             if (ExtractRelativeToDirectoryCheckIfFile(source, destinationDirectoryName, out string fileDestinationPath))
             {
                 // If it is a file:
                 // Create containing directory:
                 Directory.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!);
-                source.ExtractToFile(fileDestinationPath, overwrite: overwrite);
+                if (!password.IsEmpty)
+                    ExtractToFile(source, fileDestinationPath, overwrite, password);
+                else
+                    source.ExtractToFile(fileDestinationPath, overwrite: overwrite);
             }
         }
     }
diff --git a/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj b/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj
index 12005f1aae45aa..c936a7c15573fa 100644
--- a/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj
+++ b/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj
@@ -16,32 +16,23 @@
   <ItemGroup>
     <Compile Include="ZipFile.Create.cs" />
     <Compile Include="ZipFile.Create.Stream.cs" />
+    <Compile Include="ZipFile.Encryption.cs" />
     <Compile Include="ZipFile.Extract.cs" />
     <Compile Include="ZipFile.Extract.Stream.cs" />
     <Compile Include="ZipFile.Open.cs" />
     <Compile Include="ZipFileExtensions.ZipArchive.Create.cs" />
     <Compile Include="ZipFileExtensions.ZipArchiveEntry.Extract.cs" />
     <Compile Include="ZipFileExtensions.ZipArchive.Extract.cs" />
-    <Compile Include="$(CommonPath)DisableRuntimeMarshalling.cs"
-             Link="Common\DisableRuntimeMarshalling.cs" />
-    <Compile Include="$(CommonTestPath)System\IO\TempFile.cs"
-             Link="Common\System\IO\TempFile.cs" />
-    <Compile Include="$(CommonTestPath)System\IO\TempDirectory.cs"
-             Link="Common\System\IO\TempDirectory.cs" />
-    <Compile Include="$(CommonTestPath)System\IO\WrappedStream.cs"
-             Link="Common\System\IO\WrappedStream.cs" />
-    <Compile Include="$(CommonTestPath)System\IO\Compression\CRC.cs"
-             Link="Common\System\IO\Compression\CRC.cs" />
-    <Compile Include="$(CommonTestPath)System\IO\Compression\FileData.cs"
-             Link="Common\System\IO\Compression\FileData.cs" />
-    <Compile Include="$(CommonTestPath)System\IO\Compression\LocalMemoryStream.cs"
-             Link="Common\System\IO\Compression\LocalMemoryStream.cs" />
-    <Compile Include="$(CommonTestPath)System\IO\Compression\StreamHelpers.cs"
-             Link="Common\System\IO\Compression\StreamHelpers.cs" />
-    <Compile Include="$(CommonTestPath)System\IO\Compression\ZipTestHelper.cs"
-             Link="Common\System\IO\Compression\ZipTestHelper.cs" />
-    <Compile Include="$(CommonTestPath)System\IO\Compression\ZipTestHelper.ZipFile.cs"
-             Link="Common\System\IO\Compression\ZipTestHelper.ZipFile.cs" />
+    <Compile Include="$(CommonPath)DisableRuntimeMarshalling.cs" Link="Common\DisableRuntimeMarshalling.cs" />
+    <Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
+    <Compile Include="$(CommonTestPath)System\IO\TempDirectory.cs" Link="Common\System\IO\TempDirectory.cs" />
+    <Compile Include="$(CommonTestPath)System\IO\WrappedStream.cs" Link="Common\System\IO\WrappedStream.cs" />
+    <Compile Include="$(CommonTestPath)System\IO\Compression\CRC.cs" Link="Common\System\IO\Compression\CRC.cs" />
+    <Compile Include="$(CommonTestPath)System\IO\Compression\FileData.cs" Link="Common\System\IO\Compression\FileData.cs" />
+    <Compile Include="$(CommonTestPath)System\IO\Compression\LocalMemoryStream.cs" Link="Common\System\IO\Compression\LocalMemoryStream.cs" />
+    <Compile Include="$(CommonTestPath)System\IO\Compression\StreamHelpers.cs" Link="Common\System\IO\Compression\StreamHelpers.cs" />
+    <Compile Include="$(CommonTestPath)System\IO\Compression\ZipTestHelper.cs" Link="Common\System\IO\Compression\ZipTestHelper.cs" />
+    <Compile Include="$(CommonTestPath)System\IO\Compression\ZipTestHelper.ZipFile.cs" Link="Common\System\IO\Compression\ZipTestHelper.ZipFile.cs" />
   </ItemGroup>
   <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'unix' or '$(TargetPlatformIdentifier)' == 'browser'">
     <Compile Include="ZipFile.Unix.cs" />
@@ -51,4 +42,7 @@
   <ItemGroup>
     <PackageReference Include="System.IO.Compression.TestData" Version="$(SystemIOCompressionTestDataVersion)" />
   </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\System.IO.Compression\ref\System.IO.Compression.csproj" />
+  </ItemGroup>
 </Project>
diff --git a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Encryption.cs b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Encryption.cs
new file mode 100644
index 00000000000000..4774b1c9386154
--- /dev/null
+++ b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Encryption.cs
@@ -0,0 +1,1607 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.IO.Compression.Tests
+{
+    public class ZipFile_EncryptionTests : ZipFileTestBase
+    {
+        public static IEnumerable<object[]> EncryptionMethodAndBoolTestData()
+        {
+            foreach (var method in new[]
+            {
+                ZipEncryptionMethod.ZipCrypto,
+                ZipEncryptionMethod.Aes128,
+                ZipEncryptionMethod.Aes192,
+                ZipEncryptionMethod.Aes256
+            })
+            {
+                yield return new object[] { method, false };
+                yield return new object[] { method, true };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(EncryptionMethodAndBoolTestData))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task Encryption_SingleEntry_RoundTrip(ZipEncryptionMethod encryptionMethod, bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string entryName = "test.txt";
+            string content = "Secret Content";
+            string password = "password123";
+
+            var entries = new[] { (entryName, content, (string?)password, (ZipEncryptionMethod?)encryptionMethod) };
+
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                ZipArchiveEntry entry = archive.GetEntry(entryName);
+                Assert.NotNull(entry);
+
+                await AssertEntryTextEquals(entry, content, password, async);
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(EncryptionMethodAndBoolTestData))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task Encryption_MultipleEntries_SamePassword_RoundTrip(ZipEncryptionMethod encryptionMethod, bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "SharedPassword";
+            var entries = new[]
+            {
+                ("file1.txt", "Content 1", (string?)password, (ZipEncryptionMethod?)encryptionMethod),
+                ("folder/file2.txt", "Content 2", (string?)password, (ZipEncryptionMethod?)encryptionMethod)
+            };
+
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                foreach (var (name, content, pwd, _) in entries)
+                {
+                    var entry = archive.GetEntry(name);
+                    Assert.NotNull(entry);
+                    await AssertEntryTextEquals(entry, content, pwd, async);
+                }
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(EncryptionMethodAndBoolTestData))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task Encryption_MixedPlainAndEncrypted_RoundTrip(ZipEncryptionMethod encryptionMethod, bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            var entries = new[]
+            {
+                ("plain.txt", "Plain Content", (string?)null, (ZipEncryptionMethod?)null),
+                ("encrypted.txt", "Encrypted Content", (string?)"pass", (ZipEncryptionMethod?)encryptionMethod)
+            };
+
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                // Check plain
+                var plainEntry = archive.GetEntry("plain.txt");
+                Assert.NotNull(plainEntry);
+                await AssertEntryTextEquals(plainEntry, "Plain Content", null, async);
+
+                // Check encrypted
+                var encEntry = archive.GetEntry("encrypted.txt");
+                Assert.NotNull(encEntry);
+                await AssertEntryTextEquals(encEntry, "Encrypted Content", "pass", async);
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task Encryption_Combinations_RoundTrip(bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            var entries = new[]
+            {
+                ("zipcrypto.txt", "ZipCrypto Content", (string?)"pass1", (ZipEncryptionMethod?)ZipEncryptionMethod.ZipCrypto),
+                ("aes128.txt", "AES128 Content", (string?)"pass2", (ZipEncryptionMethod?)ZipEncryptionMethod.Aes128),
+                ("aes256.txt", "AES256 Content", (string?)"pass3", (ZipEncryptionMethod?)ZipEncryptionMethod.Aes256)
+            };
+
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                foreach (var (name, content, pwd, _) in entries)
+                {
+                    var entry = archive.GetEntry(name);
+                    Assert.NotNull(entry);
+                    await AssertEntryTextEquals(entry, content, pwd, async);
+                }
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task Encryption_LargeFile_RoundTrip(bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string entryName = "large.bin";
+            int size = 1024 * 1024; // 1MB
+            byte[] content = new byte[size];
+            new Random(42).NextBytes(content);
+            string password = "password123";
+            var encryptionMethod = ZipEncryptionMethod.Aes256;
+
+            using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Create))
+            {
+                ZipArchiveEntry entry = archive.CreateEntry(entryName, password, encryptionMethod);
+                Stream s = entry.Open();
+                using (s)
+                {
+                    if (async)
+                        await s.WriteAsync(content, 0, content.Length);
+                    else
+                        s.Write(content, 0, content.Length);
+                }
+            }
+
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                ZipArchiveEntry entry = archive.GetEntry(entryName);
+                Assert.NotNull(entry);
+
+                Stream s = entry.Open(password);
+                using (s)
+                using (MemoryStream ms = new MemoryStream())
+                {
+                    if (async)
+                        await s.CopyToAsync(ms);
+                    else
+                        s.CopyTo(ms);
+
+                    Assert.Equal(content, ms.ToArray());
+                }
+            }
+        }
+
+        [Fact]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public void WrongPassword_Throws_InvalidDataException()
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "correct";
+            var entries = new[] { ("test.txt", "content", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.Aes256) };
+            CreateArchiveWithEntries(archivePath, entries, async: false).GetAwaiter().GetResult();
+
+            using (ZipArchive archive = ZipFile.OpenRead(archivePath))
+            {
+                var entry = archive.GetEntry("test.txt");
+                Assert.Throws<InvalidDataException>(() => entry.Open("wrong"));
+            }
+        }
+
+        [Fact]
+        public void MissingPassword_Throws_InvalidDataException()
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "correct";
+            var entries = new[] { ("test.txt", "content", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.ZipCrypto) };
+            CreateArchiveWithEntries(archivePath, entries, async: false).GetAwaiter().GetResult();
+
+            using (ZipArchive archive = ZipFile.OpenRead(archivePath))
+            {
+                var entry = archive.GetEntry("test.txt");
+                Assert.Throws<InvalidDataException>(() => entry.Open());
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        public async Task OpeningPlainEntryWithPassword_Succeeds(bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            var entries = new[] { ("plain.txt", "content", (string?)null, (ZipEncryptionMethod?)null) };
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                ZipArchiveEntry entry = archive.GetEntry("plain.txt");
+                Assert.NotNull(entry);
+
+                // Password is ignored for unencrypted entries
+                using Stream s = entry.Open("password");
+                using var reader = new StreamReader(s);
+                Assert.Equal("content", reader.ReadToEnd());
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task ExtractToFile_Encrypted_Success(bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "pass";
+            var entries = new[] { ("test.txt", "content", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.Aes256) };
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                var entry = archive.GetEntry("test.txt");
+                string destFile = GetTestFilePath();
+
+                if (async)
+                {
+                    await entry.ExtractToFileAsync(destFile, new ZipExtractionOptions { OverwriteFiles = true, Password = password.AsMemory() });
+                    Assert.Equal("content", await File.ReadAllTextAsync(destFile));
+                }
+                else
+                {
+                    entry.ExtractToFile(destFile, new ZipExtractionOptions { OverwriteFiles = true, Password = password.AsMemory() });
+                    Assert.Equal("content", File.ReadAllText(destFile));
+                }
+            }
+        }
+
+        private string GetTempArchivePath() => GetTestFilePath();
+
+        private async Task CreateArchiveWithEntries(string archivePath, (string Name, string Content, string? Password, ZipEncryptionMethod? Encryption)[] entries, bool async)
+        {
+            using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Create))
+            {
+                foreach (var (name, content, password, encryption) in entries)
+                {
+                    ZipArchiveEntry entry;
+                    if (password != null && encryption.HasValue)
+                    {
+                        entry = archive.CreateEntry(name, password, encryption.Value);
+                    }
+                    else
+                    {
+                        entry = archive.CreateEntry(name);
+                    }
+                    Stream s = await OpenEntryStream(async, entry);
+
+                    using (s)
+                    using (StreamWriter w = new StreamWriter(s, Encoding.UTF8))
+                    {
+                        if (async)
+                            await w.WriteAsync(content);
+                        else
+                            w.Write(content);
+                    }
+                }
+            }
+        }
+
+        private async Task AssertEntryTextEquals(ZipArchiveEntry entry, string expected, string? password, bool async)
+        {
+            Stream s;
+            if (password != null)
+            {
+                s = entry.Open(password);
+            }
+            else
+            {
+                s = await OpenEntryStream(async, entry);
+            }
+
+            using (s)
+            using (StreamReader r = new StreamReader(s, Encoding.UTF8))
+            {
+                string actual;
+                if (async)
+                    actual = await r.ReadToEndAsync();
+                else
+                    actual = r.ReadToEnd();
+
+                Assert.Equal(expected, actual);
+            }
+        }
+
+        #region Update Mode Tests for Encrypted Entries
+
+        [Theory]
+        [MemberData(nameof(EncryptionMethodAndBoolTestData))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task UpdateMode_ModifyEncryptedEntry_RoundTrip(ZipEncryptionMethod encryptionMethod, bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string entryName = "test.txt";
+            string originalContent = "Original Content";
+            string modifiedContent = "Modified Content After Update";
+            string password = "password123";
+
+            // Create archive with encrypted entry
+            var entries = new[] { (entryName, originalContent, (string?)password, (ZipEncryptionMethod?)encryptionMethod) };
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            // Verify original content
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                ZipArchiveEntry entry = archive.GetEntry(entryName);
+                Assert.NotNull(entry);
+                Assert.True(entry.IsEncrypted);
+                await AssertEntryTextEquals(entry, originalContent, password, async);
+            }
+
+            // Open in Update mode and modify the encrypted entry
+            using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Update))
+            {
+                ZipArchiveEntry entry = archive.GetEntry(entryName);
+                Assert.NotNull(entry);
+                Assert.True(entry.IsEncrypted);
+
+                // Open with password for editing
+                using (Stream stream = entry.Open(password))
+                {
+                    // Clear existing content and write new content
+                    stream.SetLength(0);
+                    byte[] newContentBytes = Encoding.UTF8.GetBytes(modifiedContent);
+                    if (async)
+                        await stream.WriteAsync(newContentBytes, 0, newContentBytes.Length);
+                    else
+                        stream.Write(newContentBytes, 0, newContentBytes.Length);
+                }
+            }
+
+            // Verify modified content
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                ZipArchiveEntry entry = archive.GetEntry(entryName);
+                Assert.NotNull(entry);
+                Assert.True(entry.IsEncrypted);
+                await AssertEntryTextEquals(entry, modifiedContent, password, async);
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(EncryptionMethodAndBoolTestData))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task UpdateMode_ReadOnlyEncryptedEntry_NoModification(ZipEncryptionMethod encryptionMethod, bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string entryName = "test.txt";
+            string content = "Unmodified Content";
+            string password = "password123";
+
+            // Create archive with encrypted entry
+            var entries = new[] { (entryName, content, (string?)password, (ZipEncryptionMethod?)encryptionMethod) };
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            // Open in Update mode, read the entry but don't modify it
+            using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Update))
+            {
+                ZipArchiveEntry entry = archive.GetEntry(entryName);
+                Assert.NotNull(entry);
+
+                using (Stream stream = entry.Open(password))
+                using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
+                {
+                    string readContent = async ? await reader.ReadToEndAsync() : reader.ReadToEnd();
+                    Assert.Equal(content, readContent);
+                }
+            }
+
+            // Verify content is still intact after update mode
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                ZipArchiveEntry entry = archive.GetEntry(entryName);
+                Assert.NotNull(entry);
+                Assert.True(entry.IsEncrypted);
+                await AssertEntryTextEquals(entry, content, password, async);
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task UpdateMode_MultipleEncryptedEntries_ModifyOne(bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "password123";
+            var encryptionMethod = ZipEncryptionMethod.Aes256;
+
+            var entries = new[]
+            {
+                ("file1.txt", "Content 1", (string?)password, (ZipEncryptionMethod?)encryptionMethod),
+                ("file2.txt", "Content 2", (string?)password, (ZipEncryptionMethod?)encryptionMethod),
+                ("file3.txt", "Content 3", (string?)password, (ZipEncryptionMethod?)encryptionMethod)
+            };
+
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            // Modify only file2.txt
+            using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Update))
+            {
+                ZipArchiveEntry entry = archive.GetEntry("file2.txt");
+                Assert.NotNull(entry);
+
+                using (Stream stream = entry.Open(password))
+                {
+                    stream.SetLength(0);
+                    byte[] newContent = Encoding.UTF8.GetBytes("Modified Content 2");
+                    if (async)
+                        await stream.WriteAsync(newContent, 0, newContent.Length);
+                    else
+                        stream.Write(newContent, 0, newContent.Length);
+                }
+            }
+
+            // Verify: file1 and file3 unchanged, file2 modified
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                await AssertEntryTextEquals(archive.GetEntry("file1.txt"), "Content 1", password, async);
+                await AssertEntryTextEquals(archive.GetEntry("file2.txt"), "Modified Content 2", password, async);
+                await AssertEntryTextEquals(archive.GetEntry("file3.txt"), "Content 3", password, async);
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task UpdateMode_MixedEncryption_ModifyEncrypted(bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "password123";
+
+            var entries = new[]
+            {
+                ("plain.txt", "Plain Content", (string?)null, (ZipEncryptionMethod?)null),
+                ("encrypted.txt", "Encrypted Content", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.Aes256)
+            };
+
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            // Modify the encrypted entry
+            using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Update))
+            {
+                ZipArchiveEntry entry = archive.GetEntry("encrypted.txt");
+                Assert.NotNull(entry);
+                Assert.True(entry.IsEncrypted);
+
+                using (Stream stream = entry.Open(password))
+                {
+                    stream.SetLength(0);
+                    byte[] newContent = Encoding.UTF8.GetBytes("Modified Encrypted Content");
+                    if (async)
+                        await stream.WriteAsync(newContent, 0, newContent.Length);
+                    else
+                        stream.Write(newContent, 0, newContent.Length);
+                }
+            }
+
+            // Verify both entries
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                // Plain entry should be unchanged
+                var plainEntry = archive.GetEntry("plain.txt");
+                Assert.NotNull(plainEntry);
+                Assert.False(plainEntry.IsEncrypted);
+                await AssertEntryTextEquals(plainEntry, "Plain Content", null, async);
+
+                // Encrypted entry should be modified
+                var encryptedEntry = archive.GetEntry("encrypted.txt");
+                Assert.NotNull(encryptedEntry);
+                Assert.True(encryptedEntry.IsEncrypted);
+                await AssertEntryTextEquals(encryptedEntry, "Modified Encrypted Content", password, async);
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task UpdateMode_LargeEncryptedEntry_Modify(bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string entryName = "large.bin";
+            int originalSize = 512 * 1024; // 512KB
+            int modifiedSize = 768 * 1024; // 768KB
+            byte[] originalContent = new byte[originalSize];
+            byte[] modifiedContent = new byte[modifiedSize];
+            new Random(42).NextBytes(originalContent);
+            new Random(43).NextBytes(modifiedContent);
+            string password = "password123";
+            var encryptionMethod = ZipEncryptionMethod.Aes256;
+
+            // Create archive with large encrypted entry
+            using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Create))
+            {
+                ZipArchiveEntry entry = archive.CreateEntry(entryName, password, encryptionMethod);
+                using (Stream s = entry.Open())
+                {
+                    if (async)
+                        await s.WriteAsync(originalContent, 0, originalContent.Length);
+                    else
+                        s.Write(originalContent, 0, originalContent.Length);
+                }
+            }
+
+            // Update with different content
+            using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Update))
+            {
+                ZipArchiveEntry entry = archive.GetEntry(entryName);
+                Assert.NotNull(entry);
+
+                using (Stream stream = entry.Open(password))
+                {
+                    stream.SetLength(0);
+                    if (async)
+                        await stream.WriteAsync(modifiedContent, 0, modifiedContent.Length);
+                    else
+                        stream.Write(modifiedContent, 0, modifiedContent.Length);
+                }
+            }
+
+            // Verify modified content
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                ZipArchiveEntry entry = archive.GetEntry(entryName);
+                Assert.NotNull(entry);
+                Assert.True(entry.IsEncrypted);
+
+                using (Stream s = entry.Open(password))
+                using (MemoryStream ms = new MemoryStream())
+                {
+                    if (async)
+                        await s.CopyToAsync(ms);
+                    else
+                        s.CopyTo(ms);
+
+                    Assert.Equal(modifiedContent, ms.ToArray());
+                }
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(EncryptionMethodAndBoolTestData))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task UpdateMode_EncryptedEntry_EmptyAfterModification(ZipEncryptionMethod encryptionMethod, bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string entryName = "test.txt";
+            string originalContent = "Original Content";
+            string password = "password123";
+
+            // Create archive with encrypted entry
+            var entries = new[] { (entryName, originalContent, (string?)password, (ZipEncryptionMethod?)encryptionMethod) };
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            // Open in Update mode and clear the content
+            using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Update))
+            {
+                ZipArchiveEntry entry = archive.GetEntry(entryName);
+                Assert.NotNull(entry);
+
+                using (Stream stream = entry.Open(password))
+                {
+                    stream.SetLength(0); // Make it empty
+                }
+            }
+
+            // Verify entry is now empty
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                ZipArchiveEntry entry = archive.GetEntry(entryName);
+                Assert.NotNull(entry);
+                Assert.True(entry.IsEncrypted);
+                await AssertEntryTextEquals(entry, "", password, async);
+            }
+        }
+
+        [Fact]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public void UpdateMode_EncryptedEntry_WrongPassword_Throws()
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "correct";
+            var entries = new[] { ("test.txt", "content", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.Aes256) };
+            CreateArchiveWithEntries(archivePath, entries, async: false).GetAwaiter().GetResult();
+
+            using (ZipArchive archive = ZipFile.Open(archivePath, ZipArchiveMode.Update))
+            {
+                var entry = archive.GetEntry("test.txt");
+                Assert.NotNull(entry);
+                Assert.Throws<InvalidDataException>(() => entry.Open("wrong"));
+            }
+        }
+
+        [Fact]
+        public void UpdateMode_EncryptedEntry_NoPassword_Throws()
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "correct";
+            var entries = new[] { ("test.txt", "content", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.ZipCrypto) };
+            CreateArchiveWithEntries(archivePath, entries, async: false).GetAwaiter().GetResult();
+
+            using (ZipArchive archive = ZipFile.Open(archivePath, ZipArchiveMode.Update))
+            {
+                var entry = archive.GetEntry("test.txt");
+                Assert.NotNull(entry);
+                // Opening an encrypted entry without password in update mode should throw
+                Assert.ThrowsAny<ArgumentException>(() => entry.Open());
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task UpdateMode_DeleteEntryAndModifyAnother(bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "password123";
+
+            var entries = new[]
+            {
+                ("keep.txt", "Keep This Content", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.Aes256),
+                ("delete.txt", "Delete This Content", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.Aes256),
+                ("modify.txt", "Original Content", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.Aes256)
+            };
+
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            // Verify initial state
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                Assert.Equal(3, archive.Entries.Count);
+            }
+
+            // Delete one entry and modify another
+            using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Update))
+            {
+                // Delete delete.txt
+                ZipArchiveEntry deleteEntry = archive.GetEntry("delete.txt");
+                Assert.NotNull(deleteEntry);
+                deleteEntry.Delete();
+
+                // Modify modify.txt
+                ZipArchiveEntry modifyEntry = archive.GetEntry("modify.txt");
+                Assert.NotNull(modifyEntry);
+                using (Stream stream = modifyEntry.Open(password))
+                {
+                    stream.SetLength(0);
+                    byte[] content = Encoding.UTF8.GetBytes("Modified Content");
+                    if (async)
+                        await stream.WriteAsync(content, 0, content.Length);
+                    else
+                        stream.Write(content, 0, content.Length);
+                }
+            }
+
+            // Verify final state
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                Assert.Equal(2, archive.Entries.Count);
+
+                // Verify deleted entry is gone
+                Assert.Null(archive.GetEntry("delete.txt"));
+
+                // Verify kept entry is unchanged
+                var keepEntry = archive.GetEntry("keep.txt");
+                Assert.NotNull(keepEntry);
+                Assert.True(keepEntry.IsEncrypted);
+                await AssertEntryTextEquals(keepEntry, "Keep This Content", password, async);
+
+                // Verify modified entry
+                var modifyEntry = archive.GetEntry("modify.txt");
+                Assert.NotNull(modifyEntry);
+                Assert.True(modifyEntry.IsEncrypted);
+                await AssertEntryTextEquals(modifyEntry, "Modified Content", password, async);
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task UpdateMode_DeleteEncryptedAndModifyPlain(bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "password123";
+
+            var entries = new[]
+            {
+                ("plain.txt", "Plain Content", (string?)null, (ZipEncryptionMethod?)null),
+                ("encrypted.txt", "Encrypted Content", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.Aes256)
+            };
+
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            // Delete encrypted entry and modify plain entry
+            using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Update))
+            {
+                // Delete encrypted entry
+                ZipArchiveEntry encryptedEntry = archive.GetEntry("encrypted.txt");
+                Assert.NotNull(encryptedEntry);
+                encryptedEntry.Delete();
+
+                // Modify plain entry
+                ZipArchiveEntry plainEntry = archive.GetEntry("plain.txt");
+                Assert.NotNull(plainEntry);
+                using (Stream stream = await OpenEntryStream(async, plainEntry))
+                {
+                    stream.SetLength(0);
+                    byte[] content = Encoding.UTF8.GetBytes("Modified Plain Content");
+                    if (async)
+                        await stream.WriteAsync(content, 0, content.Length);
+                    else
+                        stream.Write(content, 0, content.Length);
+                }
+            }
+
+            // Verify
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                Assert.Single(archive.Entries);
+                Assert.Null(archive.GetEntry("encrypted.txt"));
+
+                var plainEntry = archive.GetEntry("plain.txt");
+                Assert.NotNull(plainEntry);
+                Assert.False(plainEntry.IsEncrypted);
+                await AssertEntryTextEquals(plainEntry, "Modified Plain Content", null, async);
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task UpdateMode_DeletePlainAndModifyEncrypted(bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "password123";
+
+            var entries = new[]
+            {
+                ("plain.txt", "Plain Content", (string?)null, (ZipEncryptionMethod?)null),
+                ("encrypted.txt", "Encrypted Content", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.Aes256)
+            };
+
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            // Delete plain entry and modify encrypted entry
+            using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Update))
+            {
+                // Delete plain entry
+                ZipArchiveEntry plainEntry = archive.GetEntry("plain.txt");
+                Assert.NotNull(plainEntry);
+                plainEntry.Delete();
+
+                // Modify encrypted entry
+                ZipArchiveEntry encryptedEntry = archive.GetEntry("encrypted.txt");
+                Assert.NotNull(encryptedEntry);
+                using (Stream stream = encryptedEntry.Open(password))
+                {
+                    stream.SetLength(0);
+                    byte[] content = Encoding.UTF8.GetBytes("Modified Encrypted Content");
+                    if (async)
+                        await stream.WriteAsync(content, 0, content.Length);
+                    else
+                        stream.Write(content, 0, content.Length);
+                }
+            }
+
+            // Verify
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                Assert.Single(archive.Entries);
+                Assert.Null(archive.GetEntry("plain.txt"));
+
+                var encryptedEntry = archive.GetEntry("encrypted.txt");
+                Assert.NotNull(encryptedEntry);
+                Assert.True(encryptedEntry.IsEncrypted);
+                await AssertEntryTextEquals(encryptedEntry, "Modified Encrypted Content", password, async);
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task UpdateMode_DeleteMultipleEntriesAndModifyRemaining(bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "password123";
+
+            var entries = new[]
+            {
+                ("keep1.txt", "Keep 1", (string?)null, (ZipEncryptionMethod?)null),
+                ("delete1.txt", "Delete 1", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.Aes256),
+                ("keep2.txt", "Keep 2", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.ZipCrypto),
+                ("delete2.txt", "Delete 2", (string?)null, (ZipEncryptionMethod?)null),
+                ("modify.txt", "Original", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.Aes128)
+            };
+
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            // Delete some entries and modify one
+            using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Update))
+            {
+                archive.GetEntry("delete1.txt")?.Delete();
+                archive.GetEntry("delete2.txt")?.Delete();
+
+                ZipArchiveEntry modifyEntry = archive.GetEntry("modify.txt");
+                Assert.NotNull(modifyEntry);
+                using (Stream stream = modifyEntry.Open(password))
+                {
+                    stream.SetLength(0);
+                    byte[] content = Encoding.UTF8.GetBytes("Modified");
+                    if (async)
+                        await stream.WriteAsync(content, 0, content.Length);
+                    else
+                        stream.Write(content, 0, content.Length);
+                }
+            }
+
+            // Verify
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                Assert.Equal(3, archive.Entries.Count);
+
+                Assert.Null(archive.GetEntry("delete1.txt"));
+                Assert.Null(archive.GetEntry("delete2.txt"));
+
+                await AssertEntryTextEquals(archive.GetEntry("keep1.txt"), "Keep 1", null, async);
+                await AssertEntryTextEquals(archive.GetEntry("keep2.txt"), "Keep 2", password, async);
+                await AssertEntryTextEquals(archive.GetEntry("modify.txt"), "Modified", password, async);
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(EncryptionMethodAndBoolTestData))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task UpdateMode_AllEncryptionTypes_EditAllEntries(ZipEncryptionMethod encryptionMethod, bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "password123";
+
+            // Create archive with multiple entries using the same encryption method
+            var entries = new[]
+            {
+                ("entry1.txt", "Content 1", (string?)password, (ZipEncryptionMethod?)encryptionMethod),
+                ("entry2.txt", "Content 2", (string?)password, (ZipEncryptionMethod?)encryptionMethod),
+                ("entry3.txt", "Content 3", (string?)password, (ZipEncryptionMethod?)encryptionMethod)
+            };
+
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            // Edit all entries
+            using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Update))
+            {
+                for (int i = 1; i <= 3; i++)
+                {
+                    ZipArchiveEntry entry = archive.GetEntry($"entry{i}.txt");
+                    Assert.NotNull(entry);
+                    Assert.True(entry.IsEncrypted);
+
+                    using (Stream stream = entry.Open(password))
+                    {
+                        stream.SetLength(0);
+                        byte[] content = Encoding.UTF8.GetBytes($"Modified Content {i}");
+                        if (async)
+                            await stream.WriteAsync(content, 0, content.Length);
+                        else
+                            stream.Write(content, 0, content.Length);
+                    }
+                }
+            }
+
+            // Verify all modifications
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                for (int i = 1; i <= 3; i++)
+                {
+                    ZipArchiveEntry entry = archive.GetEntry($"entry{i}.txt");
+                    Assert.NotNull(entry);
+                    Assert.True(entry.IsEncrypted);
+                    await AssertEntryTextEquals(entry, $"Modified Content {i}", password, async);
+                }
+            }
+        }
+
+        #endregion
+
+        #region CompressionMethod Property Tests for Encrypted Entries
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task CompressionMethod_AesEncryptedEntries_ReturnsActualCompressionMethod(bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "password123";
+
+            // Create archive with entries using different AES strengths
+            var entries = new[]
+            {
+                ("aes128.txt", "AES-128 content", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.Aes128),
+                ("aes192.txt", "AES-192 content", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.Aes192),
+                ("aes256.txt", "AES-256 content", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.Aes256),
+                ("zipcrypto.txt", "ZipCrypto content", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.ZipCrypto),
+                ("plain.txt", "Plain content", (string?)null, (ZipEncryptionMethod?)null)
+            };
+
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            // Verify CompressionMethod without opening entry streams
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                // AES entries should report the actual compression method from the AES extra field (Deflate)
+                Assert.Equal(ZipCompressionMethod.Deflate, archive.GetEntry("aes128.txt")!.CompressionMethod);
+                Assert.Equal(ZipCompressionMethod.Deflate, archive.GetEntry("aes192.txt")!.CompressionMethod);
+                Assert.Equal(ZipCompressionMethod.Deflate, archive.GetEntry("aes256.txt")!.CompressionMethod);
+
+                // ZipCrypto uses actual compression method (Deflate)
+                Assert.Equal(ZipCompressionMethod.Deflate, archive.GetEntry("zipcrypto.txt")!.CompressionMethod);
+
+                // Plain entry uses actual compression method (Deflate)
+                Assert.Equal(ZipCompressionMethod.Deflate, archive.GetEntry("plain.txt")!.CompressionMethod);
+            }
+        }
+
+        #endregion
+
+        #region Zip64 Tests for Encrypted Entries
+
+        [Theory]
+        [SkipOnCI("Takes significant time and disk space to create 4GB+ files")]
+        [MemberData(nameof(EncryptionMethodAndBoolTestData))]
+        public async Task Encryption_TrueZip64_LargeEntry_RoundTrip(ZipEncryptionMethod encryptionMethod, bool async)
+        {
+            string archivePath = GetTempArchivePath();
+
+            try
+            {
+                // Skip if insufficient disk space
+                long requiredSpace = 5L * 1024 * 1024 * 1024; // 5GB
+                string? root = Path.GetPathRoot(Path.GetFullPath(archivePath));
+                if (root is null)
+                    return;
+
+                DriveInfo drive = new DriveInfo(root);
+                if (drive.AvailableFreeSpace < requiredSpace * 2)
+                    return;
+
+                string entryName = "zip64_large.bin";
+                long size = (long)uint.MaxValue + (1024 * 1024); // Just over 4GB
+                string password = "Zip64Password!";
+                int bufferSize = 64 * 1024 * 1024; // 64MB buffer
+
+                byte[] buffer = new byte[bufferSize];
+                new Random(42).NextBytes(buffer);
+
+                // Create archive
+                using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Create))
+                {
+                    ZipArchiveEntry entry = archive.CreateEntry(entryName, CompressionLevel.NoCompression, password, encryptionMethod);
+                    using (Stream s = entry.Open())
+                    {
+                        long written = 0;
+                        while (written < size)
+                        {
+                            int toWrite = (int)Math.Min(buffer.Length, size - written);
+                            if (async)
+                                await s.WriteAsync(buffer.AsMemory(0, toWrite));
+                            else
+                                s.Write(buffer, 0, toWrite);
+                            written += toWrite;
+                        }
+                    }
+                }
+
+                // Verify
+                using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+                {
+                    ZipArchiveEntry entry = archive.GetEntry(entryName);
+                    Assert.NotNull(entry);
+                    Assert.True(entry.IsEncrypted);
+                    Assert.Equal(size, entry.Length);
+
+                    // Only verify first and last chunks to reduce test time
+                    using (Stream s = entry.Open(password))
+                    {
+                        byte[] readBuffer = new byte[bufferSize];
+
+                        // Verify first chunk
+                        int firstRead = async
+                            ? await s.ReadAsync(readBuffer)
+                            : s.Read(readBuffer);
+                        Assert.Equal(bufferSize, firstRead);
+                        Assert.True(readBuffer.AsSpan().SequenceEqual(buffer.AsSpan()));
+
+                        // Skip to near the end (seek not supported, so we read through)
+                        long toSkip = size - (2L * bufferSize);
+                        while (toSkip > 0)
+                        {
+                            int skipRead = async
+                                ? await s.ReadAsync(readBuffer.AsMemory(0, (int)Math.Min(bufferSize, toSkip)))
+                                : s.Read(readBuffer, 0, (int)Math.Min(bufferSize, toSkip));
+                            if (skipRead == 0) break;
+                            toSkip -= skipRead;
+                        }
+
+                        // Verify we can still read (stream integrity)
+                        int lastRead = async
+                            ? await s.ReadAsync(readBuffer)
+                            : s.Read(readBuffer);
+                        Assert.True(lastRead > 0);
+                    }
+                }
+            }
+            finally
+            {
+                if (File.Exists(archivePath))
+                    File.Delete(archivePath);
+            }
+        }
+
+        [Theory]
+        [SkipOnCI("Takes significant time and disk space to create 4GB+ files")]
+        [MemberData(nameof(EncryptionMethodAndBoolTestData))]
+        public async Task Encryption_TrueZip64_LargeEntry_UpdateMode_Throws(ZipEncryptionMethod encryptionMethod, bool async)
+        {
+            string archivePath = GetTempArchivePath();
+
+            try
+            {
+                long requiredSpace = 5L * 1024 * 1024 * 1024;
+                string? root = Path.GetPathRoot(Path.GetFullPath(archivePath));
+                if (root is null)
+                    return;
+
+                DriveInfo drive = new DriveInfo(root);
+                if (drive.AvailableFreeSpace < requiredSpace * 2)
+                    return;
+
+                string entryName = "zip64_large.bin";
+                long size = (long)uint.MaxValue + (1024 * 1024);
+                string password = "Zip64Password!";
+                int bufferSize = 64 * 1024 * 1024;
+
+                byte[] buffer = new byte[bufferSize];
+                new Random(42).NextBytes(buffer);
+
+                using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Create))
+                {
+                    ZipArchiveEntry entry = archive.CreateEntry(entryName, CompressionLevel.NoCompression, password, encryptionMethod);
+                    using (Stream s = entry.Open())
+                    {
+                        long written = 0;
+                        while (written < size)
+                        {
+                            int toWrite = (int)Math.Min(buffer.Length, size - written);
+                            if (async)
+                                await s.WriteAsync(buffer.AsMemory(0, toWrite));
+                            else
+                                s.Write(buffer, 0, toWrite);
+                            written += toWrite;
+                        }
+                    }
+                }
+
+                using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Update))
+                {
+                    ZipArchiveEntry entry = archive.GetEntry(entryName);
+                    Assert.NotNull(entry);
+                    Assert.True(entry.IsEncrypted);
+                    Assert.Throws<InvalidDataException>(() => entry.Open(password));
+                }
+            }
+            finally
+            {
+                if (File.Exists(archivePath))
+                    File.Delete(archivePath);
+            }
+        }
+
+        #endregion
+
+        #region ExtractToFile Tests for Encrypted Entries
+
+        [Theory]
+        [MemberData(nameof(EncryptionMethodAndBoolTestData))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task ExtractToFile_EncryptedEntry_Success(ZipEncryptionMethod encryptionMethod, bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "TestPassword123";
+            string content = "Encrypted content for ExtractToFile test";
+            var entries = new[] { ("encrypted.txt", content, (string?)password, (ZipEncryptionMethod?)encryptionMethod) };
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                ZipArchiveEntry entry = archive.GetEntry("encrypted.txt");
+                Assert.NotNull(entry);
+                Assert.True(entry.IsEncrypted);
+
+                string destFile = GetTestFilePath();
+
+                if (async)
+                {
+                    await entry.ExtractToFileAsync(destFile, new ZipExtractionOptions { OverwriteFiles = false, Password = password.AsMemory() });
+                    Assert.Equal(content, await File.ReadAllTextAsync(destFile));
+                }
+                else
+                {
+                    entry.ExtractToFile(destFile, new ZipExtractionOptions { OverwriteFiles = false, Password = password.AsMemory() });
+                    Assert.Equal(content, File.ReadAllText(destFile));
+                }
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(EncryptionMethodAndBoolTestData))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task ExtractToFile_EncryptedEntry_Overwrite_Success(ZipEncryptionMethod encryptionMethod, bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "TestPassword123";
+            string content = "Updated encrypted content";
+            var entries = new[] { ("encrypted.txt", content, (string?)password, (ZipEncryptionMethod?)encryptionMethod) };
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            string destFile = GetTestFilePath();
+            // Create an existing file to be overwritten
+            File.WriteAllText(destFile, "Original content to be overwritten");
+
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                ZipArchiveEntry entry = archive.GetEntry("encrypted.txt");
+                Assert.NotNull(entry);
+
+                if (async)
+                {
+                    await entry.ExtractToFileAsync(destFile, new ZipExtractionOptions { OverwriteFiles = true, Password = password.AsMemory() });
+                    Assert.Equal(content, await File.ReadAllTextAsync(destFile));
+                }
+                else
+                {
+                    entry.ExtractToFile(destFile, new ZipExtractionOptions { OverwriteFiles = true, Password = password.AsMemory() });
+                    Assert.Equal(content, File.ReadAllText(destFile));
+                }
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task ExtractToFile_EncryptedEntry_WrongPassword_Throws(bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "CorrectPassword";
+            var entries = new[] { ("encrypted.txt", "content", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.Aes256) };
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                ZipArchiveEntry entry = archive.GetEntry("encrypted.txt");
+                Assert.NotNull(entry);
+                string destFile = GetTestFilePath();
+
+                if (async)
+                {
+                    await Assert.ThrowsAsync<InvalidDataException>(() => entry.ExtractToFileAsync(destFile, new ZipExtractionOptions { OverwriteFiles = false, Password = "WrongPassword".AsMemory() }));
+                }
+                else
+                {
+                    Assert.Throws<InvalidDataException>(() => entry.ExtractToFile(destFile, new ZipExtractionOptions { OverwriteFiles = false, Password = "WrongPassword".AsMemory() }));
+                }
+            }
+        }
+
+        #endregion
+
+        #region ExtractToDirectory Tests for Encrypted Entries
+
+        [Theory]
+        [MemberData(nameof(EncryptionMethodAndBoolTestData))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task ExtractToDirectory_MultipleEncryptedEntries_SamePassword_Success(ZipEncryptionMethod encryptionMethod, bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "SharedPassword";
+            var entries = new[]
+            {
+                ("file1.txt", "Content 1", (string?)password, (ZipEncryptionMethod?)encryptionMethod),
+                ("file2.txt", "Content 2", (string?)password, (ZipEncryptionMethod?)encryptionMethod),
+                ("subfolder/file3.txt", "Content 3", (string?)password, (ZipEncryptionMethod?)encryptionMethod),
+                ("subfolder/nested/file4.txt", "Content 4", (string?)password, (ZipEncryptionMethod?)encryptionMethod)
+            };
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            using TempDirectory tempDir = new TempDirectory(GetTestFilePath());
+
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                if (async)
+                {
+                    await archive.ExtractToDirectoryAsync(tempDir.Path, new ZipExtractionOptions { OverwriteFiles = false, Password = password.AsMemory() });
+                }
+                else
+                {
+                    archive.ExtractToDirectory(tempDir.Path, new ZipExtractionOptions { OverwriteFiles = false, Password = password.AsMemory() });
+                }
+            }
+
+            // Verify all files were extracted correctly
+            foreach (var (name, content, _, _) in entries)
+            {
+                string extractedPath = Path.Combine(tempDir.Path, name.Replace('/', Path.DirectorySeparatorChar));
+                Assert.True(File.Exists(extractedPath), $"File {name} should exist");
+                Assert.Equal(content, File.ReadAllText(extractedPath));
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task ExtractToDirectory_MultipleEntries_DifferentPasswords_Throws(bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            // Create entries with different passwords
+            var entries = new[]
+            {
+                ("file1.txt", "Content 1", (string?)"Password1", (ZipEncryptionMethod?)ZipEncryptionMethod.Aes256),
+                ("file2.txt", "Content 2", (string?)"Password2", (ZipEncryptionMethod?)ZipEncryptionMethod.Aes256),
+                ("file3.txt", "Content 3", (string?)"Password3", (ZipEncryptionMethod?)ZipEncryptionMethod.Aes256)
+            };
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            using TempDirectory tempDir = new TempDirectory(GetTestFilePath());
+
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                // Using Password1 should fail for file2 and file3
+                if (async)
+                {
+                    await Assert.ThrowsAsync<InvalidDataException>(() =>
+                        archive.ExtractToDirectoryAsync(tempDir.Path, new ZipExtractionOptions { OverwriteFiles = false, Password = "Password1".AsMemory() }));
+                }
+                else
+                {
+                    Assert.Throws<InvalidDataException>(() =>
+                        archive.ExtractToDirectory(tempDir.Path, new ZipExtractionOptions { OverwriteFiles = false, Password = "Password1".AsMemory() }));
+                }
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(EncryptionMethodAndBoolTestData))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task ExtractToDirectory_EncryptedWithOverwrite_Success(ZipEncryptionMethod encryptionMethod, bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "TestPassword";
+            var entries = new[]
+            {
+                ("file1.txt", "New Content 1", (string?)password, (ZipEncryptionMethod?)encryptionMethod),
+                ("file2.txt", "New Content 2", (string?)password, (ZipEncryptionMethod?)encryptionMethod)
+            };
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            using TempDirectory tempDir = new TempDirectory(GetTestFilePath());
+
+            // Create existing files to be overwritten
+            File.WriteAllText(Path.Combine(tempDir.Path, "file1.txt"), "Old Content 1");
+            File.WriteAllText(Path.Combine(tempDir.Path, "file2.txt"), "Old Content 2");
+
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                if (async)
+                {
+                    await archive.ExtractToDirectoryAsync(tempDir.Path, new ZipExtractionOptions { OverwriteFiles = true, Password = password.AsMemory() });
+                }
+                else
+                {
+                    archive.ExtractToDirectory(tempDir.Path, new ZipExtractionOptions { OverwriteFiles = true, Password = password.AsMemory() });
+                }
+            }
+
+            // Verify files were overwritten with new content
+            Assert.Equal("New Content 1", File.ReadAllText(Path.Combine(tempDir.Path, "file1.txt")));
+            Assert.Equal("New Content 2", File.ReadAllText(Path.Combine(tempDir.Path, "file2.txt")));
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task ExtractToDirectory_EncryptedWithoutOverwrite_ExistingFile_Throws(bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "TestPassword";
+            var entries = new[]
+            {
+                ("file1.txt", "New Content", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.Aes256)
+            };
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            using TempDirectory tempDir = new TempDirectory(GetTestFilePath());
+
+            // Create existing file that should not be overwritten
+            File.WriteAllText(Path.Combine(tempDir.Path, "file1.txt"), "Existing Content");
+
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                if (async)
+                {
+                    await Assert.ThrowsAsync<IOException>(() =>
+                        archive.ExtractToDirectoryAsync(tempDir.Path, new ZipExtractionOptions { OverwriteFiles = false, Password = password.AsMemory() }));
+                }
+                else
+                {
+                    Assert.Throws<IOException>(() =>
+                        archive.ExtractToDirectory(tempDir.Path, new ZipExtractionOptions { OverwriteFiles = false, Password = password.AsMemory() }));
+                }
+            }
+
+            // Verify existing file was not modified
+            Assert.Equal("Existing Content", File.ReadAllText(Path.Combine(tempDir.Path, "file1.txt")));
+        }
+
+        #endregion
+
+        #region Open(FileAccess, ...) Tests
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        public async Task Open_FileAccess_ReadMode_WriteAccess_Throws(bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "secret";
+            var entries = new[] { ("test.txt", "content", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.ZipCrypto) };
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                ZipArchiveEntry entry = archive.GetEntry("test.txt");
+                Assert.NotNull(entry);
+
+
+                if (async)
+                {
+                    await Assert.ThrowsAsync<InvalidOperationException>(
+                        () => entry.OpenAsync(FileAccess.Write, password));
+
+                    await Assert.ThrowsAsync<InvalidOperationException>(
+                        () => entry.OpenAsync(FileAccess.ReadWrite, password));
+                }
+
+                else
+                {
+                    Assert.Throws<InvalidOperationException>(() => entry.Open(FileAccess.Write, password));
+                    Assert.Throws<InvalidOperationException>(() => entry.Open(FileAccess.ReadWrite, password));
+                }
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task Open_FileAccess_CreateMode_InvalidAccess_Throws(bool async)
+        {
+            string archivePath = GetTempArchivePath();
+
+            using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Create))
+            {
+                ZipArchiveEntry entry = archive.CreateEntry("test.txt");
+
+                if (async)
+                {
+                    // Read access in create mode throws
+                    await Assert.ThrowsAsync<InvalidOperationException>(() => entry.OpenAsync(FileAccess.Read, "password"));
+                    // Encryption without password throws
+                    Assert.Throws<ArgumentException>(() => archive.CreateEntry("test_null.txt", (string)null!, ZipEncryptionMethod.Aes256));
+                    Assert.Throws<ArgumentException>(() => archive.CreateEntry("test_empty.txt", "", ZipEncryptionMethod.Aes256));
+                }
+                else
+                {
+                    Assert.Throws<InvalidOperationException>(() => entry.Open(FileAccess.Read, "password"));
+                    Assert.Throws<ArgumentException>(() => archive.CreateEntry("test_null.txt", (string)null!, ZipEncryptionMethod.Aes256));
+                    Assert.Throws<ArgumentException>(() => archive.CreateEntry("test_empty.txt", "", ZipEncryptionMethod.Aes256));
+                }
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task Open_FileAccess_UpdateMode_EncryptedEntry_NoPassword_Throws(bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string password = "secret";
+            var entries = new[] { ("test.txt", "content", (string?)password, (ZipEncryptionMethod?)ZipEncryptionMethod.Aes256) };
+            await CreateArchiveWithEntries(archivePath, entries, async);
+
+            using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Update))
+            {
+                ZipArchiveEntry entry = archive.GetEntry("test.txt");
+                Assert.NotNull(entry);
+                Assert.True(entry.IsEncrypted);
+
+                if (async)
+                {
+                    await Assert.ThrowsAsync<ArgumentException>(() => entry.OpenAsync(FileAccess.Read, null!));
+                    await Assert.ThrowsAsync<ArgumentException>(() => entry.OpenAsync(FileAccess.Read, ""));
+                    await Assert.ThrowsAsync<ArgumentException>(() => entry.OpenAsync(FileAccess.ReadWrite, null!));
+                    await Assert.ThrowsAsync<ArgumentException>(() => entry.OpenAsync(FileAccess.ReadWrite, ""));
+                }
+                else
+                {
+                    Assert.ThrowsAny<ArgumentException>(() => entry.Open(FileAccess.Read, null!));
+                    Assert.ThrowsAny<ArgumentException>(() => entry.Open(FileAccess.Read, ""));
+                    Assert.ThrowsAny<ArgumentException>(() => entry.Open(FileAccess.ReadWrite, null!));
+                    Assert.ThrowsAny<ArgumentException>(() => entry.Open(FileAccess.ReadWrite, ""));
+                }
+            }
+        }
+
+        #endregion
+
+        #region CreateEntryFromFile Overload Tests
+
+        public static IEnumerable<object[]> CreateEntryFromFile_Encrypted_TestData()
+        {
+            foreach (var row in EncryptionMethodAndBoolTestData())
+            {
+                var method = (ZipEncryptionMethod)row[0];
+                var async = (bool)row[1];
+
+                yield return new object[] { method, false, async }; // no compression overload
+                yield return new object[] { method, true, async };  // compression overload
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(CreateEntryFromFile_Encrypted_TestData))]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public async Task CreateEntryFromFile_Encrypted_RoundTrip(ZipEncryptionMethod encryptionMethod, bool useCompression, bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string sourcePath = GetTestFilePath();
+            string entryName = useCompression ? "fromfile-compressed.txt" : "fromfile.txt";
+            string password = "password123";
+            string content = useCompression ? "Secret content with compression from file" : "Secret content from file";
+
+            if (async)
+                await File.WriteAllTextAsync(sourcePath, content, Encoding.UTF8);
+            else
+                File.WriteAllText(sourcePath, content, Encoding.UTF8);
+
+            using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Create))
+            {
+                if (async)
+                {
+                    if (useCompression)
+                        await archive.CreateEntryFromFileAsync(sourcePath, entryName, CompressionLevel.Optimal, password.AsMemory(), encryptionMethod);
+                    else
+                        await archive.CreateEntryFromFileAsync(sourcePath, entryName, password.AsMemory(), encryptionMethod);
+                }
+                else
+                {
+                    if (useCompression)
+                        archive.CreateEntryFromFile(sourcePath, entryName, CompressionLevel.Optimal, password, encryptionMethod);
+                    else
+                        archive.CreateEntryFromFile(sourcePath, entryName, password, encryptionMethod);
+                }
+            }
+
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                ZipArchiveEntry entry = archive.GetEntry(entryName);
+                Assert.NotNull(entry);
+                Assert.True(entry.IsEncrypted);
+
+                await AssertEntryTextEquals(entry, content, password, async);
+            }
+        }
+
+        #endregion
+
+        #region Browser Platform Tests
+
+        public static IEnumerable<object[]> AesEncryptionMethodAndBoolTestData()
+        {
+            foreach (var method in new[]
+            {
+                ZipEncryptionMethod.Aes128,
+                ZipEncryptionMethod.Aes192,
+                ZipEncryptionMethod.Aes256
+            })
+            {
+                yield return new object[] { method, false };
+                yield return new object[] { method, true };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        [PlatformSpecific(TestPlatforms.Browser)]
+        public async Task Browser_ZipCrypto_Encryption_Works(bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string entryName = "test.txt";
+            string content = "ZipCrypto Content on Browser";
+            string password = "password123";
+
+            using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Create))
+            {
+                ZipArchiveEntry entry = archive.CreateEntry(entryName, password, ZipEncryptionMethod.ZipCrypto);
+                using (Stream s = entry.Open())
+                using (StreamWriter w = new StreamWriter(s, Encoding.UTF8))
+                {
+                    if (async)
+                        await w.WriteAsync(content);
+                    else
+                        w.Write(content);
+                }
+            }
+
+            using (ZipArchive archive = await CallZipFileOpenRead(async, archivePath))
+            {
+                ZipArchiveEntry entry = archive.GetEntry(entryName);
+                Assert.NotNull(entry);
+                Assert.True(entry.IsEncrypted);
+                await AssertEntryTextEquals(entry, content, password, async);
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(AesEncryptionMethodAndBoolTestData))]
+        [PlatformSpecific(TestPlatforms.Browser)]
+        public async Task Browser_AesEncryption_Throws_PlatformNotSupportedException(ZipEncryptionMethod encryptionMethod, bool async)
+        {
+            string archivePath = GetTempArchivePath();
+            string entryName = "test.txt";
+            string password = "password123";
+
+            using (ZipArchive archive = await CallZipFileOpen(async, archivePath, ZipArchiveMode.Create))
+            {
+                Assert.Throws<PlatformNotSupportedException>(() => archive.CreateEntry(entryName, password, encryptionMethod));
+            }
+        }
+
+        #endregion
+
+        [Theory]
+        [InlineData(ZipEncryptionMethod.None)]
+        [InlineData(ZipEncryptionMethod.ZipCrypto)]
+        [InlineData(ZipEncryptionMethod.Aes128)]
+        [InlineData(ZipEncryptionMethod.Aes192)]
+        [InlineData(ZipEncryptionMethod.Aes256)]
+        [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on Browser")]
+        public void EncryptionMethod_Property_ReflectsEntryEncryption(ZipEncryptionMethod expectedMethod)
+        {
+            string archivePath = GetTempArchivePath();
+            string entryName = "test.txt";
+            string content = "Hello";
+            string password = "password123";
+
+            using (ZipArchive archive = ZipFile.Open(archivePath, ZipArchiveMode.Create))
+            {
+                ZipArchiveEntry entry = expectedMethod != ZipEncryptionMethod.None
+                    ? archive.CreateEntry(entryName, password, expectedMethod)
+                    : archive.CreateEntry(entryName);
+
+                using (StreamWriter writer = new StreamWriter(entry.Open()))
+                {
+                    writer.Write(content);
+                }
+            }
+
+            using (ZipArchive archive = ZipFile.Open(archivePath, ZipArchiveMode.Read))
+            {
+                ZipArchiveEntry entry = archive.GetEntry(entryName)!;
+                Assert.Equal(expectedMethod, entry.EncryptionMethod);
+                Assert.Equal(expectedMethod != ZipEncryptionMethod.None, entry.IsEncrypted);
+            }
+        }
+
+    }
+}
diff --git a/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs b/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs
index f9ce507af62804..97135de44d0265 100644
--- a/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs
+++ b/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs
@@ -145,6 +145,8 @@ public ZipArchive(System.IO.Stream stream, System.IO.Compression.ZipArchiveMode
         public static System.Threading.Tasks.Task<System.IO.Compression.ZipArchive> CreateAsync(System.IO.Stream stream, System.IO.Compression.ZipArchiveMode mode, bool leaveOpen, System.Text.Encoding? entryNameEncoding, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public System.IO.Compression.ZipArchiveEntry CreateEntry(string entryName) { throw null; }
         public System.IO.Compression.ZipArchiveEntry CreateEntry(string entryName, System.IO.Compression.CompressionLevel compressionLevel) { throw null; }
+        public System.IO.Compression.ZipArchiveEntry CreateEntry(string entryName, System.IO.Compression.CompressionLevel compressionLevel, System.ReadOnlySpan<char> password, System.IO.Compression.ZipEncryptionMethod encryptionMethod) { throw null; }
+        public System.IO.Compression.ZipArchiveEntry CreateEntry(string entryName, System.ReadOnlySpan<char> password, System.IO.Compression.ZipEncryptionMethod encryptionMethod) { throw null; }
         public void Dispose() { }
         protected virtual void Dispose(bool disposing) { }
         public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
@@ -164,14 +166,19 @@ internal ZipArchiveEntry() { }
         public int ExternalAttributes { get { throw null; } set { } }
         public string FullName { get { throw null; } }
         public bool IsEncrypted { get { throw null; } }
+        public System.IO.Compression.ZipEncryptionMethod EncryptionMethod { get { throw null; } }
         public System.DateTimeOffset LastWriteTime { get { throw null; } set { } }
         public long Length { get { throw null; } }
         public string Name { get { throw null; } }
         public void Delete() { }
         public System.IO.Stream Open() { throw null; }
-        public System.IO.Stream Open(FileAccess access) { throw null; }
-        public System.Threading.Tasks.Task<System.IO.Stream> OpenAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public System.IO.Stream Open(System.IO.FileAccess access) { throw null; }
+        public System.IO.Stream Open(System.IO.FileAccess access, System.ReadOnlySpan<char> password) { throw null; }
+        public System.IO.Stream Open(System.ReadOnlySpan<char> password) { throw null; }
+        public System.Threading.Tasks.Task<System.IO.Stream> OpenAsync(System.IO.FileAccess access, System.ReadOnlySpan<char> password, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public System.Threading.Tasks.Task<System.IO.Stream> OpenAsync(System.IO.FileAccess access, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public System.Threading.Tasks.Task<System.IO.Stream> OpenAsync(System.ReadOnlySpan<char> password, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public System.Threading.Tasks.Task<System.IO.Stream> OpenAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public override string ToString() { throw null; }
     }
     public enum ZipArchiveMode
@@ -186,6 +193,15 @@ public enum ZipCompressionMethod
         Deflate = 8,
         Deflate64 = 9,
     }
+    public enum ZipEncryptionMethod
+    {
+        Unknown = -1,
+        None = 0,
+        ZipCrypto = 1,
+        Aes128 = 2,
+        Aes192 = 3,
+        Aes256 = 4,
+    }
     public sealed partial class ZLibCompressionOptions
     {
         public static int DefaultWindowLog { get { throw null; } }
diff --git a/src/libraries/System.IO.Compression/src/Resources/Strings.resx b/src/libraries/System.IO.Compression/src/Resources/Strings.resx
index bbb10afbcf342a..c92d4a98f947aa 100644
--- a/src/libraries/System.IO.Compression/src/Resources/Strings.resx
+++ b/src/libraries/System.IO.Compression/src/Resources/Strings.resx
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <root>
   <!-- 
     Microsoft ResX Schema 
@@ -371,10 +372,49 @@
   <data name="IO_SeekBeforeBegin" xml:space="preserve">
     <value>An attempt was made to move the position before the beginning of the stream.</value>
   </data>
+  <data name="InvalidWinZipSize" xml:space="preserve">
+    <value>The encrypted data size in the archive is invalid.</value>
+  </data>
+  <data name="InvalidPassword" xml:space="preserve">
+    <value>The password did not match.</value>
+  </data>
+  <data name="WinZipAuthCodeMismatch" xml:space="preserve">
+    <value>The authentication code does not match. The archive may be corrupted.</value>
+  </data>
+  <data name="EntryNotEncrypted" xml:space="preserve">
+    <value>Entry is not encrypted.</value>
+  </data>
+  <data name="PasswordRequired" xml:space="preserve">
+    <value>A password is required for encrypted entries.</value>
+  </data>
+  <data name="EmptyPassword" xml:space="preserve">
+    <value>A non-empty password is required for creating an encrypted entry.</value>
+  </data>
+  <data name="InvalidAesStrength" xml:space="preserve">
+    <value>Invalid AES strength value.</value>
+  </data>
+  <data name="TruncatedZipCryptoHeader" xml:space="preserve">
+    <value>The encryption header is truncated.</value>
+  </data>
   <data name="CrcMismatch" xml:space="preserve">
     <value>The CRC32 checksum of the extracted data does not match the expected value from the archive.</value>
   </data>
   <data name="UnexpectedStreamLength" xml:space="preserve">
     <value>The decompressed data length does not match the expected value from the archive.</value>
   </data>
-</root>
+  <data name="EncryptionNotSpecified" xml:space="preserve">
+    <value>Encryption type is not specified.</value>
+  </data>
+  <data name="EncryptionReadMode" xml:space="preserve">
+    <value>Encryption method should not be specified in read mode.</value>
+  </data>
+  <data name="EncryptionUpdateMode" xml:space="preserve">
+    <value>Encryption method should not be specified in update mode.</value>
+  </data>
+  <data name="WinZipEncryptionNotSupportedOnBrowser" xml:space="preserve">
+    <value>Zip archive encryption is not supported on browser platform.</value>
+  </data>
+  <data name="UnsupportedEncryptionMethod" xml:space="preserve">
+    <value>The entry's encryption method is not supported.</value>
+  </data>
+</root>
\ No newline at end of file
diff --git a/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj b/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj
index 926452322b590d..86d809bf53f540 100644
--- a/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj
+++ b/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj
@@ -62,28 +62,28 @@
     <Compile Include="System\IO\Compression\ZLibEncoder.cs" />
     <Compile Include="System\IO\Compression\ZLibStream.cs" />
     <Compile Include="System\IO\Compression\ZipCompressionMethod.cs" />
-    <Compile Include="$(CommonPath)System\Obsoletions.cs"
-             Link="Common\System\Obsoletions.cs" />
+    <Compile Include="$(CommonPath)System\Obsoletions.cs" Link="Common\System\Obsoletions.cs" />
+    <Compile Include="System\IO\Compression\ZipEncryptionMethod.cs" />
+    <Compile Include="System\IO\Compression\ZipCryptoStream.cs" />
+    <Compile Include="System\IO\Compression\WinZipAesStream.cs" />
+    <Compile Include="System\IO\Compression\WinZipAesKeyMaterial.cs" />
+    <Compile Include="System\IO\Compression\ZipCryptoKeys.cs" />
   </ItemGroup>
 
   <!-- Windows specific files -->
   <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'windows'">
     <Compile Include="System\IO\Compression\ZipArchiveEntry.Windows.cs" />
     <Compile Include="$(CommonPath)System\IO\Compression\ZipArchiveEntryConstants.Windows.cs" />
-    <Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs"
-             Link="Common\Interop\Windows\Interop.Libraries.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs" Link="Common\Interop\Windows\Interop.Libraries.cs" />
   </ItemGroup>
 
   <!-- Unix specific files -->
   <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'unix' or '$(TargetPlatformIdentifier)' == 'browser' or '$(TargetPlatformIdentifier)' == 'wasi'">
     <Compile Include="System\IO\Compression\ZipArchiveEntry.Unix.cs" />
     <Compile Include="$(CommonPath)System\IO\Compression\ZipArchiveEntryConstants.Unix.cs" />
-    <Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs"
-             Link="Common\Interop\Unix\Interop.Libraries.cs" />
-    <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.PathConf.cs"
-             Link="Common\Interop\Unix\System.Native\Interop.PathConf.cs" />
-    <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs"
-             Link="Common\Interop\Unix\System.Native\Interop.Stat.cs" />
+    <Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs" Link="Common\Interop\Unix\Interop.Libraries.cs" />
+    <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.PathConf.cs" Link="Common\Interop\Unix\System.Native\Interop.PathConf.cs" />
+    <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs" Link="Common\Interop\Unix\System.Native\Interop.Stat.cs" />
   </ItemGroup>
 
   <!-- Zstandard files (not supported on browser/wasi) -->
@@ -109,9 +109,10 @@
   <ItemGroup>
     <ProjectReference Include="$(LibrariesProjectRoot)System.Collections\src\System.Collections.csproj" />
     <ProjectReference Include="$(LibrariesProjectRoot)System.Memory\src\System.Memory.csproj" />
+    <ProjectReference Include="$(LibrariesProjectRoot)System.Security.Cryptography\src\System.Security.Cryptography.csproj" />
     <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime\src\System.Runtime.csproj" />
     <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices\src\System.Runtime.InteropServices.csproj" />
     <ProjectReference Include="$(LibrariesProjectRoot)System.Threading\src\System.Threading.csproj" />
   </ItemGroup>
 
-</Project>
+</Project>
\ No newline at end of file
diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAesKeyMaterial.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAesKeyMaterial.cs
new file mode 100644
index 00000000000000..8f112454fe0fae
--- /dev/null
+++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAesKeyMaterial.cs
@@ -0,0 +1,126 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Runtime.Versioning;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace System.IO.Compression
+{
+    /// <summary>
+    /// Represents the parsed components of WinZip AES key material.
+    /// The key material layout is: [salt][encryption key][HMAC key][password verifier (2 bytes)].
+    /// </summary>
+    [UnsupportedOSPlatform("browser")]
+    internal readonly struct WinZipAesKeyMaterial
+    {
+        public byte[] Salt { get; }
+        public byte[] EncryptionKey { get; }
+        public byte[] HmacKey { get; }
+        public byte[] PasswordVerifier { get; }
+        public int KeySizeBits { get; }
+        public int SaltSize { get; }
+
+        private WinZipAesKeyMaterial(byte[] salt, byte[] encryptionKey, byte[] hmacKey, byte[] passwordVerifier, int keySizeBits)
+        {
+            Salt = salt;
+            EncryptionKey = encryptionKey;
+            HmacKey = hmacKey;
+            PasswordVerifier = passwordVerifier;
+            KeySizeBits = keySizeBits;
+            SaltSize = GetSaltSize(keySizeBits);
+        }
+
+        /// <summary>
+        /// Parses raw key material bytes into their individual components.
+        /// Validates that the input length matches the expected layout for the given key size.
+        /// </summary>
+        internal static WinZipAesKeyMaterial Parse(byte[] keyMaterial, int keySizeBits)
+        {
+            int saltSize = GetSaltSize(keySizeBits);
+            int keySizeBytes = keySizeBits / 8;
+            int expectedSize = checked(saltSize + keySizeBytes + keySizeBytes + 2);
+
+            if (keyMaterial.Length != expectedSize)
+            {
+                throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
+            }
+
+            int offset = 0;
+
+            byte[] salt = new byte[saltSize];
+            Array.Copy(keyMaterial, offset, salt, 0, saltSize);
+            offset += saltSize;
+
+            byte[] encryptionKey = new byte[keySizeBytes];
+            Array.Copy(keyMaterial, offset, encryptionKey, 0, keySizeBytes);
+            offset += keySizeBytes;
+
+            byte[] hmacKey = new byte[keySizeBytes];
+            Array.Copy(keyMaterial, offset, hmacKey, 0, keySizeBytes);
+            offset += keySizeBytes;
+
+            byte[] passwordVerifier = new byte[2];
+            Array.Copy(keyMaterial, offset, passwordVerifier, 0, 2);
+
+            return new WinZipAesKeyMaterial(salt, encryptionKey, hmacKey, passwordVerifier, keySizeBits);
+        }
+
+        /// <summary>
+        /// Derives key material from a password and optional salt using PBKDF2-SHA1.
+        /// </summary>
+        internal static unsafe WinZipAesKeyMaterial Create(ReadOnlySpan<char> password, byte[]? salt, int keySizeBits)
+        {
+            int saltSize = GetSaltSize(keySizeBits);
+            int keySizeBytes = keySizeBits / 8;
+            int totalKeySize = checked(keySizeBytes + keySizeBytes + 2);
+
+            byte[] saltBytes;
+            if (salt is null)
+            {
+                saltBytes = new byte[saltSize];
+                RandomNumberGenerator.Fill(saltBytes);
+            }
+            else
+            {
+                Debug.Assert(salt.Length == saltSize, $"Salt must be {saltSize} bytes for AES-{keySizeBits}.");
+                saltBytes = salt;
+            }
+
+            int maxPasswordByteCount = Encoding.UTF8.GetMaxByteCount(password.Length);
+            byte[] rentedPasswordBytes = System.Buffers.ArrayPool<byte>.Shared.Rent(maxPasswordByteCount);
+            Debug.Assert(totalKeySize <= 66, "totalKeySize should be at most 66 bytes (AES-256: 32 + 32 + 2)");
+            Span<byte> derivedKey = stackalloc byte[totalKeySize];
+
+            try
+            {
+                int actualByteCount = Encoding.UTF8.GetBytes(password, rentedPasswordBytes);
+                Span<byte> passwordSpan = rentedPasswordBytes.AsSpan(0, actualByteCount);
+
+                Rfc2898DeriveBytes.Pbkdf2(
+                    passwordSpan,
+                    saltBytes,
+                    derivedKey,
+                    1000, // iteration count specified by the WinZip AE-1/AE-2 specification
+                    HashAlgorithmName.SHA1);
+
+                // Slice the derived key directly into its components instead of
+                // round-tripping through a combined array and Parse.
+                byte[] encryptionKey = derivedKey.Slice(0, keySizeBytes).ToArray();
+                byte[] hmacKey = derivedKey.Slice(keySizeBytes, keySizeBytes).ToArray();
+                byte[] passwordVerifier = derivedKey.Slice(keySizeBytes + keySizeBytes, 2).ToArray();
+
+                return new WinZipAesKeyMaterial(saltBytes, encryptionKey, hmacKey, passwordVerifier, keySizeBits);
+            }
+            finally
+            {
+                CryptographicOperations.ZeroMemory(rentedPasswordBytes);
+                CryptographicOperations.ZeroMemory(derivedKey);
+                System.Buffers.ArrayPool<byte>.Shared.Return(rentedPasswordBytes);
+            }
+        }
+
+        internal static int GetSaltSize(int keySizeBits) => keySizeBits / 16;
+    }
+}
diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAesStream.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAesStream.cs
new file mode 100644
index 00000000000000..41be032b047f55
--- /dev/null
+++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAesStream.cs
@@ -0,0 +1,721 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers.Binary;
+using System.Diagnostics;
+using System.Runtime.Versioning;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.IO.Compression
+{
+    [UnsupportedOSPlatform("browser")]
+    internal sealed class WinZipAesStream : Stream
+    {
+        private const int BlockSize = 16; // AES block size in bytes
+        private const int KeystreamBufferSize = 4096; // Pre-generate 4KB of keystream (256 blocks)
+
+        private readonly Stream _baseStream;
+        private readonly bool _encrypting;
+        private readonly Aes _aes;
+        private IncrementalHash? _hmac;
+        private UInt128 _counter = 1;
+        private readonly byte[] _salt;
+        private readonly byte[] _passwordVerifier;
+        private bool _headerWritten;
+        private bool _disposed;
+        // During decryption: set to true after the stored auth code is read and verified.
+        // During encryption: set to true after the computed auth code has been written.
+        private bool _authCodeFinalized;
+        private readonly long _totalStreamSize;
+        private readonly bool _leaveOpen;
+        private readonly long _encryptedDataSize;
+        private long _encryptedDataRemaining;
+        private readonly byte[] _partialBlock = new byte[BlockSize];
+        private int _partialBlockBytes;
+
+        // Pre-generated keystream buffer for efficiency
+        private readonly byte[] _keystreamBuffer = new byte[KeystreamBufferSize];
+        private int _keystreamOffset = KeystreamBufferSize; // Start depleted to force initial generation
+
+        // Reusable work buffer for write operations, lazily allocated on first write
+        private byte[]? _writeWorkBuffer;
+
+        internal static int GetSaltSize(int keySizeBits) => WinZipAesKeyMaterial.GetSaltSize(keySizeBits);
+
+        /// <summary>
+        /// Derives key material from a password and optional salt.
+        /// </summary>
+        internal static WinZipAesKeyMaterial CreateKey(ReadOnlySpan<char> password, byte[]? salt, int keySizeBits)
+            => WinZipAesKeyMaterial.Create(password, salt, keySizeBits);
+
+        /// <summary>
+        /// Creates a WinZipAesStream synchronously. Reads and validates the header for decryption.
+        /// </summary>
+        internal static WinZipAesStream Create(Stream baseStream, WinZipAesKeyMaterial keyMaterial, long totalStreamSize, bool encrypting, bool leaveOpen = false)
+        {
+            ArgumentNullException.ThrowIfNull(baseStream);
+
+            if (!encrypting)
+            {
+                ReadAndValidateHeaderCore(isAsync: false, baseStream, keyMaterial, CancellationToken.None).GetAwaiter().GetResult();
+            }
+
+            return new WinZipAesStream(baseStream, keyMaterial, totalStreamSize, encrypting, leaveOpen);
+        }
+
+        /// <summary>
+        /// Creates a WinZipAesStream asynchronously. Reads and validates the header for decryption.
+        /// </summary>
+        internal static async Task<WinZipAesStream> CreateAsync(Stream baseStream, WinZipAesKeyMaterial keyMaterial, long totalStreamSize, bool encrypting, bool leaveOpen = false, CancellationToken cancellationToken = default)
+        {
+            ArgumentNullException.ThrowIfNull(baseStream);
+
+            if (!encrypting)
+            {
+                await ReadAndValidateHeaderCore(isAsync: true, baseStream, keyMaterial, cancellationToken).ConfigureAwait(false);
+            }
+
+            return new WinZipAesStream(baseStream, keyMaterial, totalStreamSize, encrypting, leaveOpen);
+        }
+
+        /// <summary>
+        /// Reads and validates the WinZip AES header (salt + password verifier) from the stream.
+        /// </summary>
+        private static async Task ReadAndValidateHeaderCore(bool isAsync, Stream baseStream, WinZipAesKeyMaterial keyMaterial, CancellationToken cancellationToken)
+        {
+            int saltSize = keyMaterial.SaltSize;
+
+            // Read salt from stream
+            byte[] fileSalt = new byte[saltSize];
+            if (isAsync)
+            {
+                await baseStream.ReadExactlyAsync(fileSalt, cancellationToken).ConfigureAwait(false);
+            }
+            else
+            {
+                baseStream.ReadExactly(fileSalt);
+            }
+
+            // Read the 2-byte password verifier from stream
+            byte[] verifier = new byte[2];
+            if (isAsync)
+            {
+                await baseStream.ReadExactlyAsync(verifier, cancellationToken).ConfigureAwait(false);
+            }
+            else
+            {
+                baseStream.ReadExactly(verifier);
+            }
+
+            // Verify the salt matches. In WinZip AES, the salt is stored in the archive
+            // header and is not secret; FixedTimeEquals is used here for consistency.
+            if (!CryptographicOperations.FixedTimeEquals(fileSalt, keyMaterial.Salt))
+            {
+                throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
+            }
+
+            // Verify the password verifier using constant-time comparison to prevent
+            // timing attacks that could distinguish a wrong password from a corrupt archive.
+            if (!CryptographicOperations.FixedTimeEquals(verifier, keyMaterial.PasswordVerifier))
+            {
+                throw new InvalidDataException(SR.InvalidPassword);
+            }
+        }
+
+        /// <summary>
+        /// Private constructor — used by Create/CreateAsync.
+        /// For decryption, the header must already be validated before calling this constructor.
+        /// </summary>
+        private WinZipAesStream(Stream baseStream, WinZipAesKeyMaterial keyMaterial, long totalStreamSize, bool encrypting, bool leaveOpen)
+        {
+            _baseStream = baseStream;
+
+            Debug.Assert((totalStreamSize >= 0) == !encrypting, "Total stream size must be known when decrypting");
+
+            _encrypting = encrypting;
+            _totalStreamSize = totalStreamSize;
+            _leaveOpen = leaveOpen;
+
+            _aes = Aes.Create();
+            _aes.Mode = CipherMode.ECB;
+            _aes.Padding = PaddingMode.None;
+
+            _salt = keyMaterial.Salt;
+            _passwordVerifier = keyMaterial.PasswordVerifier;
+
+            if (encrypting)
+            {
+                _encryptedDataSize = -1;
+                _encryptedDataRemaining = -1;
+            }
+            else
+            {
+                int headerSize = checked(keyMaterial.SaltSize + 2); // Salt + Password Verifier
+                const int hmacSize = 10; // 10-byte HMAC
+
+                _encryptedDataSize = _totalStreamSize - headerSize - hmacSize;
+                _encryptedDataRemaining = _encryptedDataSize;
+
+                if (_encryptedDataSize < 0)
+                {
+                    throw new InvalidDataException(SR.InvalidWinZipSize);
+                }
+            }
+
+            _hmac = IncrementalHash.CreateHMAC(HashAlgorithmName.SHA1, keyMaterial.HmacKey);
+            _aes.SetKey(keyMaterial.EncryptionKey);
+        }
+
+        private unsafe void FinalizeAndCompareHMAC(byte[] storedAuth)
+        {
+
+            Debug.Assert(_hmac is not null, "HMAC should have been initialized");
+
+            // Finalize HMAC computation after reading, so we can use stackalloc
+            Span<byte> expectedAuth = stackalloc byte[SHA1.HashSizeInBytes];
+            if (!_hmac.TryGetHashAndReset(expectedAuth, out int bytesWritten) || bytesWritten < 10)
+            {
+                throw new InvalidDataException(SR.WinZipAuthCodeMismatch);
+            }
+
+            // Compare the first 10 bytes of the expected hash
+            if (!CryptographicOperations.FixedTimeEquals(storedAuth, expectedAuth.Slice(0, 10)))
+            {
+                throw new InvalidDataException(SR.WinZipAuthCodeMismatch);
+            }
+        }
+
+        private void ValidateAuthCode()
+        {
+            Debug.Assert(!_encrypting, "ValidateAuthCode should only be called during decryption.");
+
+            if (_authCodeFinalized)
+            {
+                return;
+            }
+
+            // Read the 10-byte stored authentication code from the stream
+            byte[] storedAuth = new byte[10];
+            _baseStream.ReadExactly(storedAuth);
+            FinalizeAndCompareHMAC(storedAuth);
+            _authCodeFinalized = true;
+        }
+
+        private async Task ValidateAuthCodeAsync(CancellationToken cancellationToken)
+        {
+            Debug.Assert(!_encrypting, "ValidateAuthCode should only be called during decryption.");
+
+            if (_authCodeFinalized)
+            {
+                return;
+            }
+
+            // Read the 10-byte stored authentication code from the stream
+            byte[] storedAuth = new byte[10];
+            await _baseStream.ReadExactlyAsync(storedAuth, cancellationToken).ConfigureAwait(false);
+            FinalizeAndCompareHMAC(storedAuth);
+            _authCodeFinalized = true;
+        }
+
+        private async Task WriteHeaderAsync(CancellationToken cancellationToken)
+        {
+            if (_headerWritten)
+            {
+                return;
+            }
+
+            await _baseStream.WriteAsync(_salt, cancellationToken).ConfigureAwait(false);
+            await _baseStream.WriteAsync(_passwordVerifier, cancellationToken).ConfigureAwait(false);
+
+            _headerWritten = true;
+        }
+
+        private void WriteHeader()
+        {
+            if (_headerWritten)
+            {
+                return;
+            }
+
+            _baseStream.Write(_salt);
+            _baseStream.Write(_passwordVerifier);
+            _headerWritten = true;
+        }
+
+        private void ProcessBlock(Span<byte> buffer)
+        {
+            Debug.Assert(_hmac is not null, "HMAC should have been initialized");
+
+            int processed = 0;
+
+            while (processed < buffer.Length)
+            {
+                // Ensure we have enough keystream bytes available
+                int keystreamAvailable = KeystreamBufferSize - _keystreamOffset;
+                if (keystreamAvailable == 0)
+                {
+                    GenerateKeystreamBuffer();
+                    keystreamAvailable = KeystreamBufferSize;
+                }
+
+                // Process as many bytes as possible with the available keystream
+                int bytesToProcess = Math.Min(buffer.Length - processed, keystreamAvailable);
+
+                Span<byte> dataSpan = buffer.Slice(processed, bytesToProcess);
+                ReadOnlySpan<byte> keystreamSpan = _keystreamBuffer.AsSpan(_keystreamOffset, bytesToProcess);
+
+                if (_encrypting)
+                {
+                    // For encryption: XOR first, then HMAC the ciphertext
+                    XorBytes(dataSpan, keystreamSpan);
+                    _hmac.AppendData(dataSpan);
+                }
+                else
+                {
+                    // For decryption: HMAC first (on ciphertext), then XOR
+                    _hmac.AppendData(dataSpan);
+                    XorBytes(dataSpan, keystreamSpan);
+                }
+
+                _keystreamOffset += bytesToProcess;
+                processed += bytesToProcess;
+            }
+        }
+
+        private void GenerateKeystreamBuffer()
+        {
+            // Fill the buffer with all counter values first
+            for (int i = 0; i < KeystreamBufferSize; i += BlockSize)
+            {
+                BinaryPrimitives.WriteUInt128LittleEndian(_keystreamBuffer.AsSpan(i, BlockSize), _counter);
+                _counter++;
+            }
+
+            // Encrypt all 256 counter blocks in a single call
+            _aes.EncryptEcb(_keystreamBuffer, _keystreamBuffer, PaddingMode.None);
+
+            _keystreamOffset = 0;
+        }
+
+        private static void XorBytes(Span<byte> dest, ReadOnlySpan<byte> src)
+        {
+            Debug.Assert(dest.Length <= src.Length);
+
+            for (int i = 0; i < dest.Length; i++)
+            {
+                dest[i] ^= src[i];
+            }
+        }
+
+        private async Task WriteAuthCodeCoreAsync(bool isAsync, CancellationToken cancellationToken)
+        {
+            Debug.Assert(_encrypting, "WriteAuthCode should only be called during encryption.");
+            Debug.Assert(_hmac is not null, "HMAC should have been initialized");
+
+            if (_authCodeFinalized)
+            {
+                return;
+            }
+
+            byte[] authCode = new byte[20]; // SHA1 hash size
+            if (!_hmac.TryGetHashAndReset(authCode, out int bytesWritten) || bytesWritten < 10)
+            {
+                throw new CryptographicException();
+            }
+
+            // WinZip AES spec requires only the first 10 bytes of the HMAC
+            if (isAsync)
+            {
+                // WriteAsync requires Memory<byte>, so we must copy to a heap buffer for the async path
+                await _baseStream.WriteAsync(authCode.AsMemory(0, 10), cancellationToken).ConfigureAwait(false);
+            }
+            else
+            {
+                _baseStream.Write(authCode.AsSpan(0, 10));
+            }
+
+            _authCodeFinalized = true;
+        }
+
+        private void ThrowIfNotReadable()
+        {
+            ObjectDisposedException.ThrowIf(_disposed, this);
+
+            if (_encrypting)
+            {
+                throw new NotSupportedException(SR.ReadingNotSupported);
+            }
+        }
+
+        private int GetBytesToRead(int requestedCount)
+        {
+            if (_encryptedDataRemaining <= 0)
+            {
+                return 0;
+            }
+
+            return (int)Math.Min(requestedCount, _encryptedDataRemaining);
+        }
+
+        public override int Read(byte[] buffer, int offset, int count)
+        {
+            ValidateBufferArguments(buffer, offset, count);
+            return Read(buffer.AsSpan(offset, count));
+        }
+
+        public override int Read(Span<byte> buffer)
+        {
+            ThrowIfNotReadable();
+
+            int bytesToRead = GetBytesToRead(buffer.Length);
+            if (bytesToRead == 0)
+            {
+                // Only validate auth code when we've actually reached end of encrypted data,
+                // not when caller simply requested 0 bytes
+                if (_encryptedDataRemaining <= 0)
+                {
+                    ValidateAuthCode();
+                }
+                return 0;
+            }
+
+            Span<byte> readBuffer = buffer.Slice(0, bytesToRead);
+            int bytesRead = _baseStream.Read(readBuffer);
+
+            if (bytesRead > 0)
+            {
+                _encryptedDataRemaining -= bytesRead;
+                ProcessBlock(readBuffer.Slice(0, bytesRead));
+
+                // Validate auth code immediately when we've read all encrypted data
+                if (_encryptedDataRemaining <= 0)
+                {
+                    ValidateAuthCode();
+                }
+            }
+            else if (_encryptedDataRemaining > 0)
+            {
+                // Base stream returned 0 bytes but we expected more encrypted data - stream is truncated
+                throw new InvalidDataException(SR.UnexpectedEndOfStream);
+            }
+
+            return bytesRead;
+        }
+
+        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            ValidateBufferArguments(buffer, offset, count);
+            return ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
+        }
+
+        public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+            ThrowIfNotReadable();
+
+            int bytesToRead = GetBytesToRead(buffer.Length);
+            if (bytesToRead == 0)
+            {
+                // Only validate auth code when we've actually reached end of encrypted data,
+                // not when caller simply requested 0 bytes
+                if (_encryptedDataRemaining <= 0)
+                {
+                    await ValidateAuthCodeAsync(cancellationToken).ConfigureAwait(false);
+                }
+                return 0;
+            }
+
+            int bytesRead = await _baseStream.ReadAsync(buffer.Slice(0, bytesToRead), cancellationToken).ConfigureAwait(false);
+
+            if (bytesRead > 0)
+            {
+                _encryptedDataRemaining -= bytesRead;
+                ProcessBlock(buffer.Span.Slice(0, bytesRead));
+
+                // Validate auth code immediately when we've read all encrypted data
+                if (_encryptedDataRemaining <= 0)
+                {
+                    await ValidateAuthCodeAsync(cancellationToken).ConfigureAwait(false);
+                }
+            }
+            else if (_encryptedDataRemaining > 0)
+            {
+                // Base stream returned 0 bytes but we expected more encrypted data - stream is truncated
+                throw new InvalidDataException(SR.UnexpectedEndOfStream);
+            }
+
+            return bytesRead;
+        }
+
+        private byte[] GetWriteWorkBuffer() => _writeWorkBuffer ??= new byte[KeystreamBufferSize];
+
+        private void WriteCore(ReadOnlySpan<byte> buffer, byte[] workBuffer)
+        {
+            int inputOffset = 0;
+            int inputCount = buffer.Length;
+
+            // Fill the partial block buffer if it has data
+            if (_partialBlockBytes > 0)
+            {
+                int copyCount = Math.Min(BlockSize - _partialBlockBytes, inputCount);
+                buffer.Slice(inputOffset, copyCount).CopyTo(_partialBlock.AsSpan(_partialBlockBytes));
+
+                _partialBlockBytes += copyCount;
+                inputOffset += copyCount;
+                inputCount -= copyCount;
+
+                // If full, encrypt and write immediately
+                if (_partialBlockBytes == BlockSize)
+                {
+                    ProcessBlock(_partialBlock.AsSpan(0, BlockSize));
+                    _baseStream.Write(_partialBlock, 0, BlockSize);
+                    _partialBlockBytes = 0;
+                }
+            }
+
+            while (inputCount > 0)
+            {
+                int bytesToProcess = Math.Min(inputCount, workBuffer.Length);
+
+                buffer.Slice(inputOffset, bytesToProcess).CopyTo(workBuffer);
+                ProcessBlock(workBuffer.AsSpan(0, bytesToProcess));
+                _baseStream.Write(workBuffer, 0, bytesToProcess);
+
+                inputOffset += bytesToProcess;
+                inputCount -= bytesToProcess;
+            }
+        }
+
+        private void ThrowIfNotWritable()
+        {
+            ObjectDisposedException.ThrowIf(_disposed, this);
+
+            if (!_encrypting)
+            {
+                throw new NotSupportedException(SR.WritingNotSupported);
+            }
+        }
+
+        public override void Write(byte[] buffer, int offset, int count)
+        {
+            ValidateBufferArguments(buffer, offset, count);
+            Write(buffer.AsSpan(offset, count));
+        }
+
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            ThrowIfNotWritable();
+            if (!_headerWritten)
+            {
+                WriteHeader();
+            }
+
+            WriteCore(buffer, GetWriteWorkBuffer());
+        }
+
+        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            ValidateBufferArguments(buffer, offset, count);
+            return WriteAsyncCore(buffer.AsMemory(offset, count), cancellationToken).AsTask();
+        }
+
+        private async ValueTask WriteAsyncCore(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+            ThrowIfNotWritable();
+            if (!_headerWritten)
+            {
+                await WriteHeaderAsync(cancellationToken).ConfigureAwait(false);
+            }
+
+            int inputOffset = 0;
+            int inputCount = buffer.Length;
+            byte[] workBuffer = GetWriteWorkBuffer();
+
+            // Fill the partial block buffer if it has data
+            if (_partialBlockBytes > 0)
+            {
+                int copyCount = Math.Min(BlockSize - _partialBlockBytes, inputCount);
+                buffer.Slice(inputOffset, copyCount).CopyTo(_partialBlock.AsMemory(_partialBlockBytes));
+
+                _partialBlockBytes += copyCount;
+                inputOffset += copyCount;
+                inputCount -= copyCount;
+
+                // If full, encrypt and write immediately
+                if (_partialBlockBytes == BlockSize)
+                {
+                    ProcessBlock(_partialBlock.AsSpan(0, BlockSize));
+                    await _baseStream.WriteAsync(_partialBlock.AsMemory(0, BlockSize), cancellationToken).ConfigureAwait(false);
+                    _partialBlockBytes = 0;
+                }
+            }
+
+            while (inputCount > 0)
+            {
+                int bytesToProcess = Math.Min(inputCount, workBuffer.Length);
+
+                buffer.Slice(inputOffset, bytesToProcess).CopyTo(workBuffer);
+                ProcessBlock(workBuffer.AsSpan(0, bytesToProcess));
+                await _baseStream.WriteAsync(workBuffer.AsMemory(0, bytesToProcess), cancellationToken).ConfigureAwait(false);
+
+                inputOffset += bytesToProcess;
+                inputCount -= bytesToProcess;
+            }
+        }
+
+        public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+        {
+            return WriteAsyncCore(buffer, cancellationToken);
+        }
+
+        private async Task FinalizeEncryptionAsync(bool isAsync, CancellationToken cancellationToken)
+        {
+            // Process any bytes remaining in the partial buffer
+            if (_partialBlockBytes > 0)
+            {
+                // Encrypt the partial block (ProcessBlock handles partials by XORing only available bytes)
+                ProcessBlock(_partialBlock.AsSpan(0, _partialBlockBytes));
+
+                if (isAsync)
+                {
+                    await _baseStream.WriteAsync(_partialBlock.AsMemory(0, _partialBlockBytes), cancellationToken).ConfigureAwait(false);
+                }
+                else
+                {
+                    _baseStream.Write(_partialBlock, 0, _partialBlockBytes);
+                }
+
+                _partialBlockBytes = 0;
+            }
+        }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (_disposed)
+            {
+                return;
+            }
+
+            if (disposing)
+            {
+                try
+                {
+                    if (_encrypting && !_authCodeFinalized)
+                    {
+                        FinishEncryptingAsync(isAsync: false, CancellationToken.None).GetAwaiter().GetResult();
+                    }
+                }
+                finally
+                {
+                    _disposed = true;
+                    _aes.Dispose();
+                    _hmac?.Dispose();
+
+                    if (!_leaveOpen)
+                    {
+                        _baseStream.Dispose();
+                    }
+                }
+            }
+
+            base.Dispose(disposing);
+        }
+
+        public override async ValueTask DisposeAsync()
+        {
+            if (_disposed)
+            {
+                return;
+            }
+
+            try
+            {
+                if (_encrypting && !_authCodeFinalized)
+                {
+                    await FinishEncryptingAsync(isAsync: true, CancellationToken.None).ConfigureAwait(false);
+                }
+            }
+            finally
+            {
+                _aes.Dispose();
+                _hmac?.Dispose();
+
+                if (!_leaveOpen)
+                {
+                    await _baseStream.DisposeAsync().ConfigureAwait(false);
+                }
+            }
+
+            _disposed = true;
+            GC.SuppressFinalize(this);
+        }
+
+        /// <summary>
+        /// Completes the encryption sequence: ensures the header is written (even for empty entries),
+        /// flushes any remaining partial block, appends the HMAC authentication code, and flushes the base stream.
+        /// </summary>
+        private async Task FinishEncryptingAsync(bool isAsync, CancellationToken cancellationToken)
+        {
+            Debug.Assert(_encrypting && !_authCodeFinalized);
+
+            // Ensure header is written even for empty files
+            if (!_headerWritten)
+            {
+                if (isAsync)
+                    await WriteHeaderAsync(cancellationToken).ConfigureAwait(false);
+                else
+                    WriteHeader();
+            }
+
+            // Encrypt remaining partial data
+            await FinalizeEncryptionAsync(isAsync, cancellationToken).ConfigureAwait(false);
+
+            // Write Auth Code
+            await WriteAuthCodeCoreAsync(isAsync, cancellationToken).ConfigureAwait(false);
+
+            if (isAsync)
+                await _baseStream.FlushAsync(cancellationToken).ConfigureAwait(false);
+            else
+                _baseStream.Flush();
+        }
+
+        public override bool CanRead => !_encrypting && !_disposed;
+        public override bool CanSeek => false;
+        public override bool CanWrite => _encrypting && !_disposed;
+        public override long Length => throw new NotSupportedException();
+
+        public override long Position
+        {
+            get => throw new NotSupportedException();
+            set => throw new NotSupportedException();
+        }
+
+        public override void Flush()
+        {
+            ObjectDisposedException.ThrowIf(_disposed, this);
+
+            // Only flush if the base stream supports writing
+            if (_baseStream.CanWrite)
+            {
+                _baseStream.Flush();
+            }
+        }
+
+        public override async Task FlushAsync(CancellationToken cancellationToken)
+        {
+            ObjectDisposedException.ThrowIf(_disposed, this);
+
+            if (_baseStream.CanWrite)
+            {
+                await _baseStream.FlushAsync(cancellationToken).ConfigureAwait(false);
+            }
+        }
+
+        public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
+        public override void SetLength(long value) => throw new NotSupportedException();
+    }
+}
diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchive.Async.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchive.Async.cs
index cb72e9d99c8b28..880c9cb35c2329 100644
--- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchive.Async.cs
+++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchive.Async.cs
@@ -239,6 +239,12 @@ private async Task ReadCentralDirectoryAsync(CancellationToken cancellationToken
                     {
                         break;
                     }
+
+                    ZipArchiveEntry lastEntry = _entries[_entries.Count - 1];
+                    if (lastEntry.IsEncrypted)
+                    {
+                        await lastEntry.ReadEncryptionSaltIfNeededAsync(cancellationToken).ConfigureAwait(false);
+                    }
                 }
 
                 ReadCentralDirectoryEndOfOuterLoopWork(ref currPosition, sizedFileBuffer.Span);
diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchive.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchive.cs
index ca20d274e498f5..242eeac4639f50 100644
--- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchive.cs
+++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchive.cs
@@ -285,6 +285,51 @@ public ZipArchiveEntry CreateEntry(string entryName, CompressionLevel compressio
             return DoCreateEntry(entryName, compressionLevel);
         }
 
+        /// <summary>
+        /// Creates an empty encrypted entry in the Zip archive with the specified entry name.
+        /// The encryption key material is derived from the password and stored on the entry
+        /// so that a subsequent call to <see cref="ZipArchiveEntry.Open()"/> produces an encrypted stream.
+        /// </summary>
+        /// <param name="entryName">A path relative to the root of the archive, indicating the name of the entry to be created.</param>
+        /// <param name="password">The password to use for encrypting the entry.</param>
+        /// <param name="encryptionMethod">The encryption method to use.</param>
+        /// <returns>A wrapper for the newly created file entry in the archive.</returns>
+        /// <exception cref="ArgumentException"><paramref name="entryName"/> is a zero-length string.</exception>
+        /// <exception cref="ArgumentNullException"><paramref name="entryName"/> is <see langword="null"/>.</exception>
+        /// <exception cref="ArgumentException"><paramref name="password"/> is empty.</exception>
+        /// <exception cref="NotSupportedException">The ZipArchive does not support writing.</exception>
+        /// <exception cref="ObjectDisposedException">The ZipArchive has already been closed.</exception>
+        public ZipArchiveEntry CreateEntry(string entryName, ReadOnlySpan<char> password, ZipEncryptionMethod encryptionMethod)
+        {
+            ZipArchiveEntry entry = DoCreateEntry(entryName, null);
+            entry.PrepareEncryption(password, encryptionMethod);
+
+            return entry;
+        }
+
+        /// <summary>
+        /// Creates an empty encrypted entry in the Zip archive with the specified entry name and compression level.
+        /// The encryption key material is derived from the password and stored on the entry
+        /// so that a subsequent call to <see cref="ZipArchiveEntry.Open()"/> produces an encrypted stream.
+        /// </summary>
+        /// <param name="entryName">A path relative to the root of the archive, indicating the name of the entry to be created.</param>
+        /// <param name="compressionLevel">The level of the compression (speed/memory vs. compressed size trade-off).</param>
+        /// <param name="password">The password to use for encrypting the entry.</param>
+        /// <param name="encryptionMethod">The encryption method to use.</param>
+        /// <returns>A wrapper for the newly created file entry in the archive.</returns>
+        /// <exception cref="ArgumentException"><paramref name="entryName"/> is a zero-length string.</exception>
+        /// <exception cref="ArgumentNullException"><paramref name="entryName"/> is <see langword="null"/>.</exception>
+        /// <exception cref="ArgumentException"><paramref name="password"/> is empty.</exception>
+        /// <exception cref="NotSupportedException">The ZipArchive does not support writing.</exception>
+        /// <exception cref="ObjectDisposedException">The ZipArchive has already been closed.</exception>
+        public ZipArchiveEntry CreateEntry(string entryName, CompressionLevel compressionLevel, ReadOnlySpan<char> password, ZipEncryptionMethod encryptionMethod)
+        {
+            ZipArchiveEntry entry = DoCreateEntry(entryName, compressionLevel);
+            entry.PrepareEncryption(password, encryptionMethod);
+
+            return entry;
+        }
+
         /// <summary>
         /// Releases the unmanaged resources used by ZipArchive and optionally finishes writing the archive and releases the managed resources.
         /// </summary>
@@ -630,6 +675,12 @@ private void ReadCentralDirectory()
                         {
                             break;
                         }
+
+                        ZipArchiveEntry lastEntry = _entries[_entries.Count - 1];
+                        if (lastEntry.IsEncrypted)
+                        {
+                            lastEntry.ReadEncryptionSaltIfNeeded();
+                        }
                     }
 
                     ReadCentralDirectoryEndOfOuterLoopWork(ref currPosition, sizedFileBuffer);
diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs
index a393ea76491e0f..24b6035da548e5 100644
--- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs
+++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs
@@ -19,22 +19,11 @@ public partial class ZipArchiveEntry
     /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been deleted from the archive. -or- The archive that this entry belongs to was opened in ZipArchiveMode.Create, and this entry has already been written to once.</exception>
     /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be read. -or- The entry has been compressed using a compression method that is not supported.</exception>
     /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exception>
-    public async Task<Stream> OpenAsync(CancellationToken cancellationToken = default)
+    public Task<Stream> OpenAsync(CancellationToken cancellationToken = default)
     {
         cancellationToken.ThrowIfCancellationRequested();
         ThrowIfInvalidArchive();
-
-        switch (_archive.Mode)
-        {
-            case ZipArchiveMode.Read:
-                return await OpenInReadModeAsync(checkOpenable: true, cancellationToken).ConfigureAwait(false);
-            case ZipArchiveMode.Create:
-                return OpenInWriteMode();
-            case ZipArchiveMode.Update:
-            default:
-                Debug.Assert(_archive.Mode == ZipArchiveMode.Update);
-                return await OpenInUpdateModeAsync(loadExistingContent: true, cancellationToken).ConfigureAwait(false);
-        }
+        return OpenAsyncCore(InferAccessFromMode(), default, cancellationToken);
     }
 
     /// <summary>
@@ -56,40 +45,97 @@ public async Task<Stream> OpenAsync(CancellationToken cancellationToken = defaul
     /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been deleted from the archive. -or- The archive that this entry belongs to was opened in ZipArchiveMode.Create, and this entry has already been written to once.</exception>
     /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be read. -or- The entry has been compressed using a compression method that is not supported.</exception>
     /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exception>
-    public async Task<Stream> OpenAsync(FileAccess access, CancellationToken cancellationToken = default)
+    public Task<Stream> OpenAsync(FileAccess access, CancellationToken cancellationToken = default)
+    {
+        cancellationToken.ThrowIfCancellationRequested();
+        ThrowIfInvalidArchive();
+        ValidateAccessForMode(access);
+        return OpenAsyncCore(access, default, cancellationToken);
+    }
+
+    /// <summary>
+    /// Asynchronously opens the entry with the specified access mode and password for decrypting encrypted entries.
+    /// </summary>
+    /// <param name="access">The file access mode for the returned stream.</param>
+    /// <param name="password">The password used to decrypt the encrypted entry.</param>
+    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+    /// <returns>A <see cref="Task{Stream}"/> that represents the asynchronous open operation.</returns>
+    /// <remarks>
+    /// <para>The allowed <paramref name="access"/> values depend on the <see cref="ZipArchiveMode"/>:</para>
+    /// <list type="bullet">
+    /// <item><description><see cref="ZipArchiveMode.Read"/>: Only <see cref="FileAccess.Read"/> is allowed.</description></item>
+    /// <item><description><see cref="ZipArchiveMode.Create"/>: <see cref="FileAccess.Write"/> and <see cref="FileAccess.ReadWrite"/> are allowed; <see cref="FileAccess.Read"/> is not allowed. The <paramref name="password"/> is only used when decrypting existing encrypted entries and is not used when opening a newly created entry for writing.</description></item>
+    /// <item><description><see cref="ZipArchiveMode.Update"/>: All values are allowed for encrypted entries.</description></item>
+    /// </list>
+    /// </remarks>
+    /// <exception cref="ArgumentOutOfRangeException"><paramref name="access"/> is not a valid <see cref="FileAccess"/> value.</exception>
+    /// <exception cref="InvalidOperationException">The requested access is not compatible with the archive's open mode.</exception>
+    /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been deleted from the archive.</exception>
+    /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exception>
+    public Task<Stream> OpenAsync(FileAccess access, ReadOnlySpan<char> password, CancellationToken cancellationToken = default)
+    {
+        cancellationToken.ThrowIfCancellationRequested();
+        ThrowIfInvalidArchive();
+        ValidateAccessForMode(access);
+
+        if (IsEncrypted && password.IsEmpty)
+            throw new ArgumentException(SR.PasswordRequired, nameof(password));
+
+        return OpenAsyncCore(access, password, cancellationToken);
+    }
+
+    /// <summary>
+    /// Asynchronously opens the entry and uses the specified password to decrypt it if it is encrypted.
+    /// If the archive that the entry belongs to was opened in Read mode, the returned stream will be readable, and it may or may not be seekable. If Create mode, the returned stream will be writable and not seekable. If Update mode, the returned stream will be readable, writable, seekable, and support <see cref="Stream.SetLength(long)" />.
+    /// </summary>
+    /// <param name="password">The password used to decrypt the encrypted entry.</param>
+    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+    /// <returns>A task whose result is a stream that represents the contents of the entry.</returns>
+    /// <remarks>
+    /// <para>If the entry is not encrypted, <paramref name="password" /> is ignored.</para>
+    /// </remarks>
+    /// <exception cref="ArgumentException">The entry is encrypted and <paramref name="password" /> is empty.</exception>
+    /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been deleted from the archive. -or- The archive that this entry belongs to was opened in <see cref="ZipArchiveMode.Create" />, and this entry has already been written to once.</exception>
+    /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be read. -or- The entry has been compressed using a compression method that is not supported.</exception>
+    /// <exception cref="ObjectDisposedException">The <see cref="ZipArchive" /> that this entry belongs to has been disposed.</exception>
+    public Task<Stream> OpenAsync(ReadOnlySpan<char> password, CancellationToken cancellationToken = default)
     {
         cancellationToken.ThrowIfCancellationRequested();
         ThrowIfInvalidArchive();
 
-        if (access is not (FileAccess.Read or FileAccess.Write or FileAccess.ReadWrite))
-            throw new ArgumentOutOfRangeException(nameof(access), SR.InvalidFileAccess);
+        if (IsEncrypted && password.IsEmpty)
+            throw new ArgumentException(SR.PasswordRequired, nameof(password));
+
+        return OpenAsyncCore(InferAccessFromMode(), password, cancellationToken);
+    }
+
+    private Task<Stream> OpenAsyncCore(FileAccess access, ReadOnlySpan<char> password, CancellationToken cancellationToken)
+    {
+        bool usePassword = IsEncrypted && !password.IsEmpty;
 
-        // Validate that the requested access is compatible with the archive's mode
         switch (_archive.Mode)
         {
             case ZipArchiveMode.Read:
-                if (access != FileAccess.Read)
-                    throw new InvalidOperationException(SR.CannotBeWrittenInReadMode);
-                return await OpenInReadModeAsync(checkOpenable: true, cancellationToken).ConfigureAwait(false);
-
+                return OpenInReadModeAsync(checkOpenable: true, password, cancellationToken);
             case ZipArchiveMode.Create:
-                if (access == FileAccess.Read)
-                    throw new InvalidOperationException(SR.CannotBeReadInCreateMode);
-                return OpenInWriteMode();
-
+                return Task.FromResult<Stream>(OpenInWriteMode());
             case ZipArchiveMode.Update:
             default:
                 Debug.Assert(_archive.Mode == ZipArchiveMode.Update);
-                switch (access)
+                // Encrypted entries always require a password for re-encryption,
+                // even when discarding existing content (write-only access).
+                if (IsEncrypted && password.IsEmpty && access != FileAccess.Read)
+                    throw new ArgumentException(SR.PasswordRequired, nameof(password));
+                return access switch
                 {
-                    case FileAccess.Read:
-                        return await OpenInReadModeAsync(checkOpenable: true, cancellationToken).ConfigureAwait(false);
-                    case FileAccess.Write:
-                        return await OpenInUpdateModeAsync(loadExistingContent: false, cancellationToken).ConfigureAwait(false);
-                    case FileAccess.ReadWrite:
-                    default:
-                        return await OpenInUpdateModeAsync(loadExistingContent: true, cancellationToken).ConfigureAwait(false);
-                }
+                    FileAccess.Read => OpenInReadModeAsync(checkOpenable: true, password, cancellationToken),
+                    FileAccess.Write => usePassword
+                        ? OpenInUpdateModeWithPasswordAsync(loadExistingContent: false, password, cancellationToken)
+                        : CastToStreamAsync(OpenInUpdateModeAsync(loadExistingContent: false, cancellationToken)),
+                    _ => usePassword
+                        ? OpenInUpdateModeWithPasswordAsync(loadExistingContent: true, password, cancellationToken)
+                        : CastToStreamAsync(OpenInUpdateModeAsync(loadExistingContent: true, cancellationToken)),
+                };
         }
     }
 
@@ -98,30 +144,185 @@ internal async Task<long> GetOffsetOfCompressedDataAsync(CancellationToken cance
         cancellationToken.ThrowIfCancellationRequested();
         if (_storedOffsetOfCompressedData == null)
         {
+            // Seek to local header
             _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
-            // by calling this, we are using local header _storedEntryNameBytes.Length and extraFieldLength
-            // to find start of data, but still using central directory size information
+
+            // Skip the local file header to get to the compressed data
+            // TrySkipBlockAsync handles both AES and non-AES cases correctly
             if (!await ZipLocalFileHeader.TrySkipBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(false))
                 throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
+
             _storedOffsetOfCompressedData = _archive.ArchiveStream.Position;
         }
         return _storedOffsetOfCompressedData.Value;
     }
 
+    /// <summary>
+    /// Asynchronously reads and caches the AES encryption salt from the local file data area.
+    /// Called during central directory parsing for AES-encrypted entries so that
+    /// the salt is available for key derivation without additional I/O at open time.
+    /// </summary>
+    internal async Task ReadEncryptionSaltIfNeededAsync(CancellationToken cancellationToken)
+    {
+        if (!IsAesEncrypted || !_originallyInArchive || OperatingSystem.IsBrowser())
+        {
+            return;
+        }
+
+        long savedPosition = _archive.ArchiveStream.Position;
+        try
+        {
+            long offset = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
+            _archive.ArchiveStream.Seek(offset, SeekOrigin.Begin);
+
+            int keySizeBits = GetAesKeySizeBits(Encryption);
+            int saltSize = WinZipAesStream.GetSaltSize(keySizeBits);
+            _aesSalt = new byte[saltSize];
+            await _archive.ArchiveStream.ReadExactlyAsync(_aesSalt, cancellationToken).ConfigureAwait(false);
+        }
+        catch (Exception ex) when (ex is InvalidDataException or EndOfStreamException)
+        {
+            // These are the only exceptions GetOffsetOfCompressedDataAsync() and ReadExactlyAsync()
+            // can throw for corrupt or truncated data. Swallow them here and defer the error
+            // to when the entry is actually opened.
+            _aesSalt = null;
+        }
+        finally
+        {
+            _archive.ArchiveStream.Seek(savedPosition, SeekOrigin.Begin);
+        }
+    }
+
+    private Task<Stream> OpenInReadModeAsync(bool checkOpenable, ReadOnlySpan<char> password, CancellationToken cancellationToken)
+    {
+        cancellationToken.ThrowIfCancellationRequested();
+
+        // Derive key material from the password span before entering the async core.
+        WinZipAesKeyMaterial? aesKeys = null;
+        ZipCryptoKeys? zipCryptoKeys = null;
+        byte zipCryptoCheckByte = 0;
+
+        if (IsEncrypted)
+        {
+            if (Encryption == ZipEncryptionMethod.Unknown)
+            {
+                throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
+            }
+
+            if (password.IsEmpty)
+            {
+                throw new InvalidDataException(SR.PasswordRequired);
+            }
+
+            if (IsAesEncrypted)
+            {
+                if (OperatingSystem.IsBrowser())
+                {
+                    throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnBrowser);
+                }
+
+                if (_aesSalt is null)
+                {
+                    throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
+                }
+
+                int keySizeBits = GetAesKeySizeBits(Encryption);
+                aesKeys = WinZipAesStream.CreateKey(password, _aesSalt, keySizeBits);
+            }
+            else if (IsZipCryptoEncrypted)
+            {
+                zipCryptoCheckByte = CalculateZipCryptoCheckByte();
+                zipCryptoKeys = ZipCryptoStream.CreateKey(password);
+            }
+            else
+            {
+                throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
+            }
+        }
+
+        return OpenInReadModeAsyncCore(checkOpenable, aesKeys, zipCryptoKeys, zipCryptoCheckByte, cancellationToken);
+
+        async Task<Stream> OpenInReadModeAsyncCore(bool checkOpenable, WinZipAesKeyMaterial? aesKeys, ZipCryptoKeys? zipCryptoKeys, byte zipCryptoCheckByte, CancellationToken cancellationToken)
+        {
+            if (checkOpenable)
+                await ThrowIfNotOpenableAsync(needToUncompress: true, needToLoadIntoMemory: false, cancellationToken).ConfigureAwait(false);
+
+            long offset = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
+            Stream compressedStream = new SubReadStream(_archive.ArchiveStream, offset, _compressedSize);
+
+            Stream streamToDecompress;
+            if (aesKeys is not null)
+            {
+                streamToDecompress = await WinZipAesStream.CreateAsync(
+                    baseStream: compressedStream,
+                    keyMaterial: aesKeys.Value,
+                    totalStreamSize: _compressedSize,
+                    encrypting: false,
+                    cancellationToken: cancellationToken).ConfigureAwait(false);
+            }
+            else if (zipCryptoKeys is not null)
+            {
+                streamToDecompress = await ZipCryptoStream.CreateAsync(
+                    compressedStream, zipCryptoKeys.Value, zipCryptoCheckByte,
+                    encrypting: false, cancellationToken).ConfigureAwait(false);
+            }
+            else
+            {
+                streamToDecompress = compressedStream;
+            }
+
+            return BuildDecompressionPipeline(streamToDecompress);
+        }
+    }
+
+    private async Task<WrappedStream> OpenInUpdateModeAsync(bool loadExistingContent, CancellationToken cancellationToken)
+    {
+        cancellationToken.ThrowIfCancellationRequested();
+        if (_currentlyOpenForWrite)
+            throw new IOException(SR.UpdateModeOneStream);
+
+        if (Encryption == ZipEncryptionMethod.Unknown)
+            throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
+
+        if (loadExistingContent)
+        {
+            await ThrowIfNotOpenableAsync(needToUncompress: true, needToLoadIntoMemory: true, cancellationToken).ConfigureAwait(false);
+        }
+
+        _currentlyOpenForWrite = true;
+
+        if (loadExistingContent)
+        {
+            _storedUncompressedData = await GetUncompressedDataAsync(cancellationToken).ConfigureAwait(false);
+        }
+        else
+        {
+            _storedUncompressedData?.Dispose();
+            _storedUncompressedData = new MemoryStream();
+            MarkAsModified();
+        }
+
+        _storedUncompressedData.Seek(0, SeekOrigin.Begin);
+
+        return new WrappedStream(_storedUncompressedData, this,
+            onClosed: thisRef => thisRef!._currentlyOpenForWrite = false,
+            notifyEntryOnWrite: true);
+    }
+
     private async Task<MemoryStream> GetUncompressedDataAsync(CancellationToken cancellationToken)
     {
         cancellationToken.ThrowIfCancellationRequested();
-        if (_storedUncompressedData == null)
+        if (_storedUncompressedData is null)
         {
-            // this means we have never opened it before
+            if (_uncompressedSize > Array.MaxLength)
+                throw new InvalidDataException(SR.EntryTooLarge);
 
-            // if _uncompressedSize > int.MaxValue, it's still okay, because MemoryStream will just
-            // grow as data is copied into it
             _storedUncompressedData = new MemoryStream((int)_uncompressedSize);
 
             if (_originallyInArchive)
             {
-                Stream decompressor = await OpenInReadModeAsync(false, cancellationToken).ConfigureAwait(false);
+                Stream decompressor = await OpenInReadModeAsync(checkOpenable: false, default, cancellationToken).ConfigureAwait(false);
+
                 await using (decompressor)
                 {
                     try
@@ -130,15 +331,12 @@ private async Task<MemoryStream> GetUncompressedDataAsync(CancellationToken canc
                     }
                     catch (InvalidDataException)
                     {
-                        // this is the case where the archive say the entry is deflate, but deflateStream
-                        // throws an InvalidDataException. This property should only be getting accessed in
-                        // Update mode, so we want to make sure _storedUncompressedData stays null so
-                        // that later when we dispose the archive, this entry loads the compressedBytes, and
-                        // copies them straight over
                         await _storedUncompressedData.DisposeAsync().ConfigureAwait(false);
                         _storedUncompressedData = null;
                         _currentlyOpenForWrite = false;
                         _everOpenedForWrite = false;
+                        _derivedZipCryptoKeyMaterial = null;
+                        _derivedAesKeyMaterial = null;
                         throw;
                     }
                 }
@@ -178,14 +376,26 @@ internal async Task WriteCentralDirectoryFileHeaderAsync(bool forceWrite, Cancel
             await _archive.ArchiveStream.WriteAsync(cdStaticHeader, cancellationToken).ConfigureAwait(false);
             await _archive.ArchiveStream.WriteAsync(_storedEntryNameBytes, cancellationToken).ConfigureAwait(false);
 
-            // only write zip64ExtraField if we decided we need it (it's not null)
+            // Write zip64ExtraField first if we decided we need it
             if (zip64ExtraField != null)
             {
                 await zip64ExtraField.WriteBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(false);
             }
 
-            // write extra fields (and any malformed trailing data).
-            await ZipGenericExtraField.WriteAllBlocksAsync(_cdUnknownExtraFields, _cdTrailingExtraFieldData ?? Array.Empty<byte>(), _archive.ArchiveStream, cancellationToken).ConfigureAwait(false);
+            // Write WinZip AES extra field AFTER Zip64 (matching sync version order)
+            // Must match the exact check used in the sync version WriteCentralDirectoryFileHeader
+            if (UseAesEncryption())
+            {
+                await CreateAesExtraField().WriteBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(false);
+
+                // write extra fields excluding existing AES extra field (and any malformed trailing data).
+                await ZipGenericExtraField.WriteAllBlocksExcludingTagAsync(_cdUnknownExtraFields, _cdTrailingExtraFieldData ?? Array.Empty<byte>(), _archive.ArchiveStream, WinZipAesExtraField.HeaderId, cancellationToken).ConfigureAwait(false);
+            }
+            else
+            {
+                // write extra fields (and any malformed trailing data).
+                await ZipGenericExtraField.WriteAllBlocksAsync(_cdUnknownExtraFields, _cdTrailingExtraFieldData ?? Array.Empty<byte>(), _archive.ArchiveStream, cancellationToken).ConfigureAwait(false);
+            }
 
             if (_fileComment.Length > 0)
             {
@@ -193,7 +403,6 @@ internal async Task WriteCentralDirectoryFileHeaderAsync(bool forceWrite, Cancel
             }
         }
     }
-
     internal async Task LoadLocalHeaderExtraFieldIfNeededAsync(CancellationToken cancellationToken)
     {
         cancellationToken.ThrowIfCancellationRequested();
@@ -243,39 +452,133 @@ internal async Task ThrowIfNotOpenableAsync(bool needToUncompress, bool needToLo
             throw new InvalidDataException(message);
     }
 
-    private async Task<Stream> OpenInReadModeAsync(bool checkOpenable, CancellationToken cancellationToken)
+    /// <summary>
+    /// Accepts a <see cref="ReadOnlySpan{T}"/> password, derives decryption and re-encryption keys (CPU-only),
+    /// and delegates all I/O to fully async helper methods.
+    /// </summary>
+    private Task<Stream> OpenInUpdateModeWithPasswordAsync(bool loadExistingContent, ReadOnlySpan<char> password, CancellationToken cancellationToken)
     {
-        cancellationToken.ThrowIfCancellationRequested();
-        if (checkOpenable)
-            await ThrowIfNotOpenableAsync(needToUncompress: true, needToLoadIntoMemory: false, cancellationToken).ConfigureAwait(false);
+        if (_currentlyOpenForWrite)
+        {
+            throw new IOException(SR.UpdateModeOneStream);
+        }
+
+        if (Encryption == ZipEncryptionMethod.Unknown)
+        {
+            throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
+        }
+
+        // Encrypted entries always require a password for re-encryption,
+        // even when discarding existing content (write-only access).
+        if (IsEncrypted && password.IsEmpty)
+        {
+            throw new ArgumentException(SR.PasswordRequired, nameof(password));
+        }
+
+        // Derive re-encryption key material while the password span is still valid.
+        if (IsEncrypted)
+        {
+            SetupEncryptionKeyMaterial(password);
+        }
+
+        if (loadExistingContent && IsEncrypted)
+        {
+            if (IsAesEncrypted)
+            {
+                if (OperatingSystem.IsBrowser())
+                {
+                    throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnBrowser);
+                }
+
+                if (_aesSalt is null)
+                {
+                    throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
+                }
+
+                int keySizeBits = GetAesKeySizeBits(Encryption);
+                WinZipAesKeyMaterial aesKeys = WinZipAesStream.CreateKey(password, _aesSalt, keySizeBits);
+                return DecryptAndStoreForUpdateWithAesAsync(aesKeys, cancellationToken);
+            }
+
+            if (IsZipCryptoEncrypted)
+            {
+                byte checkByte = CalculateZipCryptoCheckByte();
+                ZipCryptoKeys keys = ZipCryptoStream.CreateKey(password);
+                return DecryptAndStoreForUpdateWithZipCryptoAsync(keys, checkByte, cancellationToken);
+            }
 
-        return OpenInReadModeGetDataCompressor(
-            await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false));
+            throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
+        }
+
+        return CastToStreamAsync(OpenInUpdateModeAsync(loadExistingContent, cancellationToken));
     }
 
-    private async Task<WrappedStream> OpenInUpdateModeAsync(bool loadExistingContent = true, CancellationToken cancellationToken = default)
+    private static async Task<Stream> CastToStreamAsync(Task<WrappedStream> task) => await task.ConfigureAwait(false);
+
+    private async Task<Stream> DecryptAndStoreForUpdateWithZipCryptoAsync(ZipCryptoKeys keys, byte checkByte, CancellationToken cancellationToken)
     {
-        cancellationToken.ThrowIfCancellationRequested();
-        if (_currentlyOpenForWrite)
-            throw new IOException(SR.UpdateModeOneStream);
+        await ThrowIfNotOpenableAsync(needToUncompress: true, needToLoadIntoMemory: true, cancellationToken).ConfigureAwait(false);
 
-        if (loadExistingContent)
+        long offset = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
+        Stream compressedStream = new SubReadStream(_archive.ArchiveStream, offset, _compressedSize);
+
+        Stream decrypted = await ZipCryptoStream.CreateAsync(compressedStream, keys, checkByte, encrypting: false, cancellationToken).ConfigureAwait(false);
+
+        return await StoreDecryptedDataForUpdateAsync(decrypted, cancellationToken).ConfigureAwait(false);
+    }
+
+    private async Task<Stream> DecryptAndStoreForUpdateWithAesAsync(WinZipAesKeyMaterial aesKeys, CancellationToken cancellationToken)
+    {
+        if (OperatingSystem.IsBrowser())
         {
-            await ThrowIfNotOpenableAsync(needToUncompress: true, needToLoadIntoMemory: true, cancellationToken).ConfigureAwait(false);
+            throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnBrowser);
         }
 
+        await ThrowIfNotOpenableAsync(needToUncompress: true, needToLoadIntoMemory: true, cancellationToken).ConfigureAwait(false);
+
+        long offset = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
+        Stream compressedStream = new SubReadStream(_archive.ArchiveStream, offset, _compressedSize);
+
+        Stream decrypted = await WinZipAesStream.CreateAsync(
+            baseStream: compressedStream,
+            keyMaterial: aesKeys,
+            totalStreamSize: _compressedSize,
+            encrypting: false,
+            cancellationToken: cancellationToken).ConfigureAwait(false);
+
+        return await StoreDecryptedDataForUpdateAsync(decrypted, cancellationToken).ConfigureAwait(false);
+    }
+
+    /// <summary>
+    /// Decompresses a decrypted stream and stores the result in memory for update mode.
+    /// </summary>
+    private async Task<Stream> StoreDecryptedDataForUpdateAsync(Stream decryptedStream, CancellationToken cancellationToken)
+    {
         _currentlyOpenForWrite = true;
 
-        if (loadExistingContent)
-        {
-            _storedUncompressedData = await GetUncompressedDataAsync(cancellationToken).ConfigureAwait(false);
-        }
-        else
+        if (_uncompressedSize > Array.MaxLength)
+            throw new InvalidDataException(SR.EntryTooLarge);
+
+        _storedUncompressedData = new MemoryStream((int)_uncompressedSize);
+
+        Stream decompressed = BuildDecompressionPipeline(decryptedStream);
+
+        await using (decompressed)
         {
-            _storedUncompressedData?.Dispose();
-            _storedUncompressedData = new MemoryStream();
-            // Opening with loadExistingContent: false discards existing content, which is a modification
-            MarkAsModified();
+            try
+            {
+                await decompressed.CopyToAsync(_storedUncompressedData, cancellationToken).ConfigureAwait(false);
+            }
+            catch (InvalidDataException)
+            {
+                await _storedUncompressedData.DisposeAsync().ConfigureAwait(false);
+                _storedUncompressedData = null;
+                _currentlyOpenForWrite = false;
+                _everOpenedForWrite = false;
+                _derivedZipCryptoKeyMaterial = null;
+                _derivedAesKeyMaterial = null;
+                throw;
+            }
         }
 
         _storedUncompressedData.Seek(0, SeekOrigin.Begin);
@@ -299,11 +602,24 @@ private async Task<WrappedStream> OpenInUpdateModeAsync(bool loadExistingContent
         {
             return (false, message);
         }
-        if (!await ZipLocalFileHeader.TrySkipBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(false))
+
+        if (!IsEncrypted && !await ZipLocalFileHeader.TrySkipBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(false))
         {
             message = SR.LocalFileHeaderCorrupt;
             return (false, message);
         }
+        else if (IsEncrypted && (ushort)_headerCompressionMethod == WinZipAesMethod)
+        {
+            _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
+            // AES case - skip the local file header and validate it exists.
+            // The AES metadata (encryption strength, actual compression method) was already
+            // parsed from the central directory in the constructor.
+            if (!await ZipLocalFileHeader.TrySkipBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(false))
+            {
+                message = SR.LocalFileHeaderCorrupt;
+                return (false, message);
+            }
+        }
 
         // when this property gets called, some duplicated work
         long offsetOfCompressedData = await GetOffsetOfCompressedDataAsync(cancellationToken).ConfigureAwait(false);
@@ -335,7 +651,19 @@ private async Task<bool> WriteLocalFileHeaderAsync(bool isEmptyFile, bool forceW
                 await zip64ExtraField.WriteBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(false);
             }
 
-            await ZipGenericExtraField.WriteAllBlocksAsync(_lhUnknownExtraFields, _lhTrailingExtraFieldData ?? Array.Empty<byte>(), _archive.ArchiveStream, cancellationToken).ConfigureAwait(false);
+            // Write WinZip AES extra field if using AES encryption
+            // Must match the exact check used in the sync version WriteLocalFileHeader
+            if (UseAesEncryption())
+            {
+                await CreateAesExtraField().WriteBlockAsync(_archive.ArchiveStream, cancellationToken).ConfigureAwait(false);
+
+                // Write other extra fields, excluding any existing AES extra field to avoid duplication
+                await ZipGenericExtraField.WriteAllBlocksExcludingTagAsync(_lhUnknownExtraFields, _lhTrailingExtraFieldData ?? Array.Empty<byte>(), _archive.ArchiveStream, WinZipAesExtraField.HeaderId, cancellationToken).ConfigureAwait(false);
+            }
+            else
+            {
+                await ZipGenericExtraField.WriteAllBlocksAsync(_lhUnknownExtraFields, _lhTrailingExtraFieldData ?? Array.Empty<byte>(), _archive.ArchiveStream, cancellationToken).ConfigureAwait(false);
+            }
         }
 
         return zip64ExtraField != null;
@@ -364,18 +692,134 @@ private async Task WriteLocalFileHeaderAndDataIfNeededAsync(bool forceWrite, Can
             {
                 _uncompressedSize = _storedUncompressedData.Length;
 
-                //The compressor fills in CRC and sizes
-                //The DirectToArchiveWriterStream writes headers and such
-                DirectToArchiveWriterStream entryWriter = new(GetDataCompressor(_archive.ArchiveStream, true, null), this);
-                await using (entryWriter)
+                // Check if we need to re-encrypt with ZipCrypto (only if we have cached key material)
+                if (Encryption == ZipEncryptionMethod.ZipCrypto && _derivedZipCryptoKeyMaterial != null)
+                {
+                    // Write local file header first (with encryption flag set)
+                    // Pass isEmptyFile: false because even empty encrypted files have the 12-byte header
+                    await WriteLocalFileHeaderAsync(isEmptyFile: false, forceWrite: true, preserveDataDescriptor: false, cancellationToken).ConfigureAwait(false);
+
+                    // Record position before encryption data
+                    long startPosition = _archive.ArchiveStream.Position;
+
+                    ushort verifierLow2Bytes = (ushort)ZipHelper.DateTimeToDosTime(_lastModified.DateTime);
+
+                    var encryptionStream = ZipCryptoStream.Create(
+                        baseStream: _archive.ArchiveStream,
+                        keys: _derivedZipCryptoKeyMaterial.Value,
+                        passwordVerifierLow2Bytes: verifierLow2Bytes,
+                        encrypting: true,
+                        crc32: null,
+                        leaveOpen: true);
+                    await using (encryptionStream.ConfigureAwait(false))
+                    {
+                        // Use GetDataCompressor which handles CRC calculation and compression
+                        var crcStream = GetDataCompressor(encryptionStream, leaveBackingStreamOpen: true, onClose: null, streamForPosition: _archive.ArchiveStream);
+                        await using (crcStream.ConfigureAwait(false))
+                        {
+                            _storedUncompressedData.Seek(0, SeekOrigin.Begin);
+                            await _storedUncompressedData.CopyToAsync(crcStream, cancellationToken).ConfigureAwait(false);
+                        }
+                        // CRC, uncompressed size are now set by GetDataCompressor callback
+                        // For empty files, ZipCryptoStream.Dispose() will write the 12-byte header
+                    }
+
+                    // Calculate compressed size AFTER ZipCryptoStream is disposed
+                    // (includes 12-byte encryption header + compressed data)
+                    _compressedSize = _archive.ArchiveStream.Position - startPosition;
+
+                    // Write data descriptor since we used streaming mode
+                    await WriteDataDescriptorAsync(cancellationToken).ConfigureAwait(false);
+
+                    await _storedUncompressedData.DisposeAsync().ConfigureAwait(false);
+                    _storedUncompressedData = null;
+                }
+                else if (UseAesEncryption() && _derivedAesKeyMaterial != null)
+                {
+
+                    if (OperatingSystem.IsBrowser())
+                    {
+                        throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnBrowser);
+                    }
+                    // For AES, we need to:
+                    // 1. Write header with CompressionMethod = Aes (99)
+                    // 2. Compress data with actual compression (Deflate/Stored)
+                    // 3. Keep CompressionMethod = Aes for central directory
+
+                    // WriteLocalFileHeaderAsync will set CompressionMethod = Aes
+                    bool usedZip64InLH = await WriteLocalFileHeaderAsync(isEmptyFile: false, forceWrite: true, preserveDataDescriptor: false, cancellationToken).ConfigureAwait(false);
+
+                    // Record position before encryption data
+                    long startPosition = _archive.ArchiveStream.Position;
+
+                    int keySizeBits = GetAesKeySizeBits(Encryption);
+
+                    // Determine the actual compression method to use
+                    // The AES extra field stores the real compression method
+                    bool useDeflate = _compressionLevel != CompressionLevel.NoCompression;
+
+                    var encryptionStream = WinZipAesStream.Create(
+                        baseStream: _archive.ArchiveStream,
+                        keyMaterial: _derivedAesKeyMaterial.Value,
+                        totalStreamSize: -1,
+                        encrypting: true,
+                        leaveOpen: true);
+
+
+                    await using (encryptionStream.ConfigureAwait(false))
+                    {
+                        // Only compress/write if there's data
+                        if (_storedUncompressedData.Length > 0)
+                        {
+                            // Temporarily set CompressionMethod for GetDataCompressor
+                            ZipCompressionMethod savedMethod = CompressionMethod;
+                            CompressionMethod = useDeflate ? ZipCompressionMethod.Deflate : ZipCompressionMethod.Stored;
+
+                            var crcStream = GetDataCompressor(encryptionStream, leaveBackingStreamOpen: true, onClose: null, streamForPosition: _archive.ArchiveStream);
+                            await using (crcStream.ConfigureAwait(false))
+                            {
+                                _storedUncompressedData.Seek(0, SeekOrigin.Begin);
+                                await _storedUncompressedData.CopyToAsync(crcStream, cancellationToken).ConfigureAwait(false);
+                            }
+
+                            // Restore CompressionMethod - AesCompressionMethodValue is used directly when writing headers
+                            CompressionMethod = savedMethod;
+                        }
+                        else
+                        {
+                            // Empty file: CRC is 0, uncompressed size is 0
+                            _crc32 = 0;
+                            _uncompressedSize = 0;
+                        }
+                        // WinZipAesStream.Dispose() writes salt + verifier + HMAC even for empty files
+                    }
+
+                    // Calculate compressed size AFTER WinZipAesStream is disposed
+                    // (includes salt + password verifier + encrypted data + HMAC)
+                    _compressedSize = _archive.ArchiveStream.Position - startPosition;
+
+                    // Patch CRC and sizes back into the local header
+                    await WriteCrcAndSizesInLocalHeaderAsync(usedZip64InLH, cancellationToken).ConfigureAwait(false);
+
+                    await _storedUncompressedData.DisposeAsync().ConfigureAwait(false);
+                    _storedUncompressedData = null;
+                }
+                else
                 {
-                    _storedUncompressedData.Seek(0, SeekOrigin.Begin);
-                    await _storedUncompressedData.CopyToAsync(entryWriter, cancellationToken).ConfigureAwait(false);
+                    // Non-encrypted: use standard path
+                    //The compressor fills in CRC and sizes
+                    //The DirectToArchiveWriterStream writes headers and such
+                    DirectToArchiveWriterStream entryWriter = new(GetDataCompressor(_archive.ArchiveStream, true, null, null), this);
+                    await using (entryWriter.ConfigureAwait(false))
+                    {
+                        _storedUncompressedData.Seek(0, SeekOrigin.Begin);
+                        await _storedUncompressedData.CopyToAsync(entryWriter, cancellationToken).ConfigureAwait(false);
+                    }
                     await _storedUncompressedData.DisposeAsync().ConfigureAwait(false);
                     _storedUncompressedData = null;
                 }
             }
-            else
+            else // _compressedBytes path - copying unchanged entry data
             {
                 if (_uncompressedSize == 0)
                 {
@@ -383,8 +827,48 @@ private async Task WriteLocalFileHeaderAndDataIfNeededAsync(bool forceWrite, Can
                     _compressedSize = 0;
                 }
 
+                // For unchanged entries, we need to write the header correctly but avoid
+                // WriteLocalFileHeaderAsync creating NEW encryption structures (which would have
+                // wrong compression method from _compressionLevel).
+                // The original AES extra field is preserved in _lhUnknownExtraFields.
+                BitFlagValues savedFlags = _generalPurposeBitFlag;
+                ZipEncryptionMethod savedEncryption = Encryption;
+                ZipCompressionMethod savedCompressionMethod = CompressionMethod;
+
+                // For AES entries: set CompressionMethod to Aes so header writes method 99,
+                // but clear _encryptionMethod so WriteLocalFileHeaderAsync doesn't create a new
+                // AES extra field (the original one in _lhUnknownExtraFields will be used).
+                if (savedEncryption is ZipEncryptionMethod.Aes128 or ZipEncryptionMethod.Aes192 or ZipEncryptionMethod.Aes256)
+                {
+                    CompressionMethod = (ZipCompressionMethod)WinZipAesMethod;
+                    Encryption = ZipEncryptionMethod.None;
+                }
+
                 await WriteLocalFileHeaderAsync(isEmptyFile: _uncompressedSize == 0, forceWrite: true, preserveDataDescriptor: false, cancellationToken).ConfigureAwait(false);
 
+                // WriteLocalFileHeaderInitialize may have cleared the DataDescriptor flag
+                // (because Encryption was temporarily set to None and the stream is seekable).
+                // If the original entry had a data descriptor, patch the general-purpose bit
+                // flags in the already-written local header to match, so the header on disk
+                // is consistent with the data descriptor we conditionally write below.
+                if ((savedFlags & BitFlagValues.DataDescriptor) != 0 &&
+                    (_generalPurposeBitFlag & BitFlagValues.DataDescriptor) == 0)
+                {
+                    long currentPos = _archive.ArchiveStream.Position;
+                    _archive.ArchiveStream.Seek(
+                        _offsetOfLocalHeader + ZipLocalFileHeader.FieldLocations.GeneralPurposeBitFlags,
+                        SeekOrigin.Begin);
+                    byte[] flagBytes = new byte[2];
+                    BinaryPrimitives.WriteUInt16LittleEndian(flagBytes, (ushort)savedFlags);
+                    await _archive.ArchiveStream.WriteAsync(flagBytes, cancellationToken).ConfigureAwait(false);
+                    _archive.ArchiveStream.Seek(currentPos, SeekOrigin.Begin);
+                }
+
+                // Restore original state
+                _generalPurposeBitFlag = savedFlags;
+                Encryption = savedEncryption;
+                CompressionMethod = savedCompressionMethod;
+
                 // according to ZIP specs, zero-byte files MUST NOT include file data
                 if (_uncompressedSize != 0)
                 {
@@ -394,6 +878,12 @@ private async Task WriteLocalFileHeaderAndDataIfNeededAsync(bool forceWrite, Can
                         await _archive.ArchiveStream.WriteAsync(compressedBytes, cancellationToken).ConfigureAwait(false);
                     }
                 }
+
+                // Write data descriptor if the original entry had one
+                if ((savedFlags & BitFlagValues.DataDescriptor) != 0)
+                {
+                    await WriteDataDescriptorAsync(cancellationToken).ConfigureAwait(false);
+                }
             }
         }
         else // there is no data in the file (or the data in the file has not been loaded), but if we are in update mode, we may still need to write a header
diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs
index e84666144c9379..4b51418c371334 100644
--- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs
+++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs
@@ -23,7 +23,6 @@ public partial class ZipArchiveEntry
         private ZipVersionNeededValues _versionMadeBySpecification;
         private ZipVersionNeededValues _versionToExtract;
         private BitFlagValues _generalPurposeBitFlag;
-        private readonly bool _isEncrypted;
         private ZipCompressionMethod _storedCompressionMethod;
         private DateTimeOffset _lastModified;
         private long _compressedSize;
@@ -47,8 +46,18 @@ public partial class ZipArchiveEntry
         private List<ZipGenericExtraField>? _lhUnknownExtraFields;
         private byte[]? _lhTrailingExtraFieldData;
         private byte[] _fileComment;
+        private ZipEncryptionMethod _encryptionMethod;
         private readonly CompressionLevel _compressionLevel;
-
+        private ZipCompressionMethod _headerCompressionMethod;
+        private ushort? _aeVersion;
+        // Cached derived key material for encrypted entries to allow updating in place.
+        // Only one of these is set at a time, depending on the encryption method.
+        private ZipCryptoKeys? _derivedZipCryptoKeyMaterial;
+        private WinZipAesKeyMaterial? _derivedAesKeyMaterial;
+        // Pre-read AES salt from the local file data area during central directory parsing.
+        // This allows async open methods to derive keys without synchronous I/O.
+        private byte[]? _aesSalt;
+        internal const ushort WinZipAesMethod = 99;
         // Initializes a ZipArchiveEntry instance for an existing archive entry.
         internal ZipArchiveEntry(ZipArchive archive, ZipCentralDirectoryFileHeader cd)
         {
@@ -66,8 +75,46 @@ internal ZipArchiveEntry(ZipArchive archive, ZipCentralDirectoryFileHeader cd)
             _versionMadeBySpecification = (ZipVersionNeededValues)cd.VersionMadeBySpecification;
             _versionToExtract = (ZipVersionNeededValues)cd.VersionNeededToExtract;
             _generalPurposeBitFlag = (BitFlagValues)cd.GeneralPurposeBitFlag;
-            _isEncrypted = (_generalPurposeBitFlag & BitFlagValues.IsEncrypted) != 0;
-            CompressionMethod = (ZipCompressionMethod)cd.CompressionMethod;
+            // Initialize _headerCompressionMethod from the central directory.
+            // For AES entries, this will be 99 (WinZip AES wrapper indicator) and never changes.
+            _headerCompressionMethod = (ZipCompressionMethod)cd.CompressionMethod;
+            // For AES-encrypted entries, the real compression method is stored in the AES extra field (0x9901)
+            // Parse it now so that people can see the actual value before opening the entry.
+            if (IsEncrypted && cd.AesExtraField.HasValue)
+            {
+                WinZipAesExtraField aesField = cd.AesExtraField.Value;
+                // Set the real compression method from the AES extra field
+                CompressionMethod = (ZipCompressionMethod)aesField.CompressionMethod;
+
+                // Also parse remaining needed metadata now
+                _aeVersion = aesField.VendorVersion;
+                Encryption = aesField.AesStrength switch
+                {
+                    1 => ZipEncryptionMethod.Aes128,
+                    2 => ZipEncryptionMethod.Aes192,
+                    3 => ZipEncryptionMethod.Aes256,
+                    _ => throw new InvalidDataException(SR.InvalidAesStrength)
+                };
+            }
+            else if (IsEncrypted)
+            {
+                if ((_generalPurposeBitFlag & BitFlagValues.StrongEncryption) != 0)
+                {
+                    Encryption = ZipEncryptionMethod.Unknown;
+                }
+                else
+                {
+                    // Encrypted but no AES extra field means ZipCrypto
+                    Encryption = ZipEncryptionMethod.ZipCrypto;
+                }
+                CompressionMethod = (ZipCompressionMethod)cd.CompressionMethod;
+            }
+            else
+            {
+                // Non-AES entry: compression method from CD is the real method
+                CompressionMethod = (ZipCompressionMethod)cd.CompressionMethod;
+            }
+
             _lastModified = new DateTimeOffset(ZipHelper.DosTimeToDateTime(cd.LastModified));
             _compressedSize = cd.CompressedSize;
             _uncompressedSize = cd.UncompressedSize;
@@ -97,7 +144,6 @@ internal ZipArchiveEntry(ZipArchive archive, ZipCentralDirectoryFileHeader cd)
 
             _compressionLevel = MapCompressionLevel(_generalPurposeBitFlag, CompressionMethod);
         }
-
         // Initializes a ZipArchiveEntry instance for a new archive entry with a specified compression level.
         internal ZipArchiveEntry(ZipArchive archive, string entryName, CompressionLevel compressionLevel)
             : this(archive, entryName)
@@ -172,7 +218,19 @@ internal ZipArchiveEntry(ZipArchive archive, string entryName)
         /// <summary>
         /// Gets a value that indicates whether the entry is encrypted.
         /// </summary>
-        public bool IsEncrypted => _isEncrypted;
+        public bool IsEncrypted => (_generalPurposeBitFlag & BitFlagValues.IsEncrypted) != 0;
+
+        /// <summary>
+        /// Gets the encryption method used to encrypt the entry.
+        /// </summary>
+        /// <value>
+        /// <see cref="ZipEncryptionMethod.None"/> if the entry is not encrypted;
+        /// <see cref="ZipEncryptionMethod.Unknown"/> if the entry uses an unsupported encryption method;
+        /// otherwise, the specific encryption method (e.g., <see cref="ZipEncryptionMethod.ZipCrypto"/>,
+        /// <see cref="ZipEncryptionMethod.Aes128"/>, <see cref="ZipEncryptionMethod.Aes192"/>,
+        /// or <see cref="ZipEncryptionMethod.Aes256"/>).
+        /// </value>
+        public ZipEncryptionMethod EncryptionMethod => _encryptionMethod;
 
         /// <summary>
         /// Gets the compression method used to compress the entry.
@@ -275,6 +333,11 @@ private set
             }
         }
 
+        /// <summary>
+        /// Returns the relative path of the entry in the Zip archive, equivalent to <see cref="FullName"/>.
+        /// </summary>
+        public override string ToString() => FullName;
+
         /// <summary>
         /// The last write time of the entry as stored in the Zip archive. When setting this property, the DateTime will be converted to the
         /// Zip timestamp format, which supports a resolution of two seconds. If the data in the last write time field is not a valid Zip timestamp,
@@ -365,18 +428,28 @@ public void Delete()
         public Stream Open()
         {
             ThrowIfInvalidArchive();
+            return OpenCore(InferAccessFromMode());
+        }
 
-            switch (_archive.Mode)
-            {
-                case ZipArchiveMode.Read:
-                    return OpenInReadMode(checkOpenable: true);
-                case ZipArchiveMode.Create:
-                    return OpenInWriteMode();
-                case ZipArchiveMode.Update:
-                default:
-                    Debug.Assert(_archive.Mode == ZipArchiveMode.Update);
-                    return OpenInUpdateMode();
-            }
+
+        /// <summary>
+        /// Opens the entry for reading or updating with the specified password.
+        /// If the entry is not encrypted, the password is ignored and the entry is opened normally.
+        /// </summary>
+        /// <returns>A Stream that represents the contents of the entry.</returns>
+        /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been deleted from the archive. -or- The archive that this entry belongs to was opened in ZipArchiveMode.Create, and this entry has already been written to once.</exception>
+        /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be read. -or- The entry has been compressed using a compression method that is not supported.</exception>
+        /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exception>
+        /// <exception cref="InvalidOperationException">The requested access is not compatible with the archive's open mode.</exception>
+        /// <param name="password">The password used to decrypt the entry. If the entry is not encrypted, this parameter is ignored.</param>
+        public Stream Open(ReadOnlySpan<char> password)
+        {
+            ThrowIfInvalidArchive();
+
+            if (IsEncrypted && password.IsEmpty)
+                throw new ArgumentException(SR.PasswordRequired, nameof(password));
+
+            return OpenCore(InferAccessFromMode(), password);
         }
 
         /// <summary>
@@ -400,48 +473,66 @@ public Stream Open()
         public Stream Open(FileAccess access)
         {
             ThrowIfInvalidArchive();
+            ValidateAccessForMode(access);
+            return OpenCore(access);
+        }
+
+        public Stream Open(FileAccess access, ReadOnlySpan<char> password)
+        {
+            ThrowIfInvalidArchive();
+            ValidateAccessForMode(access);
+
+            if (IsEncrypted && password.IsEmpty)
+                throw new ArgumentException(SR.PasswordRequired, nameof(password));
 
+            return OpenCore(access, password);
+        }
+
+        private FileAccess InferAccessFromMode()=> _archive.Mode switch
+        {
+            ZipArchiveMode.Read => FileAccess.Read,
+            ZipArchiveMode.Create => FileAccess.Write,
+            _ => FileAccess.ReadWrite
+        };
+
+        private void ValidateAccessForMode(FileAccess access)
+        {
             if (access is not (FileAccess.Read or FileAccess.Write or FileAccess.ReadWrite))
                 throw new ArgumentOutOfRangeException(nameof(access), SR.InvalidFileAccess);
 
-            // Validate that the requested access is compatible with the archive's mode
             switch (_archive.Mode)
             {
                 case ZipArchiveMode.Read:
                     if (access != FileAccess.Read)
                         throw new InvalidOperationException(SR.CannotBeWrittenInReadMode);
-                    return OpenInReadMode(checkOpenable: true);
-
+                    break;
                 case ZipArchiveMode.Create:
                     if (access == FileAccess.Read)
                         throw new InvalidOperationException(SR.CannotBeReadInCreateMode);
-                    return OpenInWriteMode();
+                    break;
+            }
+        }
 
+        private Stream OpenCore(FileAccess access, ReadOnlySpan<char> password = default)
+        {
+            switch (_archive.Mode)
+            {
+                case ZipArchiveMode.Read:
+                    return OpenInReadMode(checkOpenable: true, password);
+                case ZipArchiveMode.Create:
+                    return OpenInWriteMode();
                 case ZipArchiveMode.Update:
                 default:
                     Debug.Assert(_archive.Mode == ZipArchiveMode.Update);
-                    switch (access)
+                    return access switch
                     {
-                        case FileAccess.Read:
-                            return OpenInReadMode(checkOpenable: true);
-                        case FileAccess.Write:
-                            return OpenInUpdateMode(loadExistingContent: false);
-                        case FileAccess.ReadWrite:
-                        default:
-                            return OpenInUpdateMode(loadExistingContent: true);
-                    }
+                        FileAccess.Read => OpenInReadMode(checkOpenable: true, password),
+                        FileAccess.Write => OpenInUpdateMode(loadExistingContent: false, password),
+                        _ => OpenInUpdateMode(loadExistingContent: true, password),
+                    };
             }
         }
 
-        /// <summary>
-        /// Returns the FullName of the entry.
-        /// </summary>
-        /// <returns>FullName of the entry</returns>
-        public override string ToString()
-        {
-            return FullName;
-        }
-
         private string DecodeEntryString(byte[] entryStringBytes)
         {
             Debug.Assert(entryStringBytes != null);
@@ -465,29 +556,77 @@ internal long GetOffsetOfCompressedData()
         {
             if (_storedOffsetOfCompressedData == null)
             {
+                // Seek to local header
                 _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
-                // by calling this, we are using local header _storedEntryNameBytes.Length and extraFieldLength
-                // to find start of data, but still using central directory size information
+
+                // Skip the local file header to get to the compressed data
+                // TrySkipBlock handles both AES and non-AES cases correctly
                 if (!ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveStream))
                     throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
+
                 _storedOffsetOfCompressedData = _archive.ArchiveStream.Position;
             }
+
             return _storedOffsetOfCompressedData.Value;
         }
 
-        private MemoryStream GetUncompressedData()
+        /// <summary>
+        /// Reads and caches the AES encryption salt from the local file data area.
+        /// Called during central directory parsing for AES-encrypted entries so that
+        /// the salt is available for key derivation without additional I/O at open time.
+        /// </summary>
+        internal void ReadEncryptionSaltIfNeeded()
+        {
+            if (!IsAesEncrypted || !_originallyInArchive || OperatingSystem.IsBrowser())
+            {
+                return;
+            }
+
+            long savedPosition = _archive.ArchiveStream.Position;
+            try
+            {
+                long offset = GetOffsetOfCompressedData();
+                _archive.ArchiveStream.Seek(offset, SeekOrigin.Begin);
+
+                int keySizeBits = GetAesKeySizeBits(Encryption);
+                int saltSize = WinZipAesStream.GetSaltSize(keySizeBits);
+                _aesSalt = new byte[saltSize];
+                _archive.ArchiveStream.ReadExactly(_aesSalt);
+            }
+            catch (Exception ex) when (ex is InvalidDataException or EndOfStreamException)
+            {
+                // These are the only exceptions GetOffsetOfCompressedData() and ReadExactly()
+                // can throw for corrupt or truncated data. Swallow them here and defer the error
+                // to when the entry is actually opened.
+                _aesSalt = null;
+            }
+            finally
+            {
+                _archive.ArchiveStream.Seek(savedPosition, SeekOrigin.Begin);
+            }
+        }
+
+        private MemoryStream GetUncompressedData(ReadOnlySpan<char> password = default)
         {
             if (_storedUncompressedData == null)
             {
                 // this means we have never opened it before
 
-                // if _uncompressedSize > int.MaxValue, it's still okay, because MemoryStream will just
-                // grow as data is copied into it
+
+                if (_uncompressedSize > Array.MaxLength)
+                {
+                    throw new InvalidDataException(SR.EntryTooLarge);
+                }
+
                 _storedUncompressedData = new MemoryStream((int)_uncompressedSize);
 
                 if (_originallyInArchive)
                 {
-                    using (Stream decompressor = OpenInReadMode(false))
+                    Stream decompressor = !password.IsEmpty
+                        ? OpenInReadMode(checkOpenable: false, password)
+                        : OpenInReadMode(checkOpenable: false);
+
+                    using (decompressor)
                     {
                         try
                         {
@@ -504,6 +643,8 @@ private MemoryStream GetUncompressedData()
                             _storedUncompressedData = null;
                             _currentlyOpenForWrite = false;
                             _everOpenedForWrite = false;
+                            _derivedZipCryptoKeyMaterial = null;
+                            _derivedAesKeyMaterial = null;
                             throw;
                         }
                     }
@@ -595,14 +736,27 @@ private bool WriteCentralDirectoryFileHeaderInitialize(bool forceWrite, out Zip6
             }
 
 
+            WinZipAesExtraField? aesExtraField = null;
+            int aesExtraFieldSize = 0;
+
+            if (UseAesEncryption())
+            {
+                aesExtraField = CreateAesExtraField();
+                aesExtraFieldSize = WinZipAesExtraField.TotalSize;
+            }
+
             // determine if we can fit zip64 extra field and original extra fields all in
-            int currExtraFieldDataLength = ZipGenericExtraField.TotalSize(_cdUnknownExtraFields, _cdTrailingExtraFieldData?.Length ?? 0);
+            // When using AES encryption, exclude the AES tag from currExtraFieldDataLength since we're writing a new one
+            int currExtraFieldDataLength = UseAesEncryption()
+                ? ZipGenericExtraField.TotalSizeExcludingTag(_cdUnknownExtraFields, _cdTrailingExtraFieldData?.Length ?? 0, WinZipAesExtraField.HeaderId)
+                : ZipGenericExtraField.TotalSize(_cdUnknownExtraFields, _cdTrailingExtraFieldData?.Length ?? 0);
             int bigExtraFieldLength = (zip64ExtraField != null ? zip64ExtraField.TotalSize : 0)
+                                      + aesExtraFieldSize
                                       + currExtraFieldDataLength;
 
             if (bigExtraFieldLength > ushort.MaxValue)
             {
-                extraFieldLength = (ushort)(zip64ExtraField != null ? zip64ExtraField.TotalSize : 0);
+                extraFieldLength = (ushort)((zip64ExtraField != null ? zip64ExtraField.TotalSize : 0) + aesExtraFieldSize);
                 _cdUnknownExtraFields = null;
             }
             else
@@ -614,8 +768,7 @@ private bool WriteCentralDirectoryFileHeaderInitialize(bool forceWrite, out Zip6
             {
                 long centralDirectoryHeaderLength = ZipCentralDirectoryFileHeader.FieldLocations.DynamicData
                     + _storedEntryNameBytes.Length
-                    + (zip64ExtraField != null ? zip64ExtraField.TotalSize : 0)
-                    + currExtraFieldDataLength
+                    + extraFieldLength
                     + _fileComment.Length;
 
                 _archive.ArchiveStream.Seek(centralDirectoryHeaderLength, SeekOrigin.Current);
@@ -652,9 +805,16 @@ private void WriteCentralDirectoryFileHeaderPrepare(Span<byte> cdStaticHeader, u
             cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.VersionMadeByCompatibility] = (byte)CurrentZipPlatform;
             BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.VersionNeededToExtract..], (ushort)_versionToExtract);
             BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.GeneralPurposeBitFlags..], (ushort)_generalPurposeBitFlag);
-            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.CompressionMethod..], (ushort)CompressionMethod);
+
+            // For AES encryption, write compression method 99 (Aes) in the header
+            // _headerCompressionMethod preserves the original value from the central directory
+            ushort compressionMethodToWrite = UseAesEncryption() ? (ushort)WinZipAesMethod : (ushort)CompressionMethod;
+            BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.CompressionMethod..], compressionMethodToWrite);
+
             BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.LastModified..], ZipHelper.DateTimeToDosTime(_lastModified.DateTime));
-            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Crc32..], _crc32);
+            // when using aes encryption, ae-2 standard dictates crc to be 0
+            uint crcToWrite = UseAesEncryption() ? 0 : _crc32;
+            BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.Crc32..], crcToWrite);
             BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.CompressedSize..], compressedSizeTruncated);
             BinaryPrimitives.WriteUInt32LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.UncompressedSize..], uncompressedSizeTruncated);
             BinaryPrimitives.WriteUInt16LittleEndian(cdStaticHeader[ZipCentralDirectoryFileHeader.FieldLocations.FilenameLength..], (ushort)_storedEntryNameBytes.Length);
@@ -680,8 +840,19 @@ internal unsafe void WriteCentralDirectoryFileHeader(bool forceWrite)
                 // only write zip64ExtraField if we decided we need it (it's not null)
                 zip64ExtraField?.WriteBlock(_archive.ArchiveStream);
 
-                // write extra fields (and any malformed trailing data).
-                ZipGenericExtraField.WriteAllBlocks(_cdUnknownExtraFields, _cdTrailingExtraFieldData ?? Array.Empty<byte>(), _archive.ArchiveStream);
+                // Write AES extra field if using AES encryption
+                if (UseAesEncryption())
+                {
+                    CreateAesExtraField().WriteBlock(_archive.ArchiveStream);
+
+                    // write extra fields excluding existing AES extra field (and any malformed trailing data).
+                    ZipGenericExtraField.WriteAllBlocksExcludingTag(_cdUnknownExtraFields, _cdTrailingExtraFieldData ?? Array.Empty<byte>(), _archive.ArchiveStream, WinZipAesExtraField.HeaderId);
+                }
+                else
+                {
+                    // write extra fields (and any malformed trailing data).
+                    ZipGenericExtraField.WriteAllBlocks(_cdUnknownExtraFields, _cdTrailingExtraFieldData ?? Array.Empty<byte>(), _archive.ArchiveStream);
+                }
 
                 if (_fileComment.Length > 0)
                 {
@@ -755,7 +926,8 @@ private void DetectEntryNameVersion()
             }
         }
 
-        private CheckSumAndSizeWriteStream GetDataCompressor(Stream backingStream, bool leaveBackingStreamOpen, EventHandler? onClose)
+
+        private CheckSumAndSizeWriteStream GetDataCompressor(Stream backingStream, bool leaveBackingStreamOpen, EventHandler? onClose, Stream? streamForPosition = null)
         {
             // stream stack: backingStream -> DeflateStream -> CheckSumWriteStream
 
@@ -788,12 +960,11 @@ private CheckSumAndSizeWriteStream GetDataCompressor(Stream backingStream, bool
                 default:
                     compressorStreamFactory = () => new DeflateStream(backingStream, _compressionLevel, leaveBackingStreamOpen);
                     break;
-
             }
             bool leaveCompressorStreamOpenOnClose = leaveBackingStreamOpen && !isIntermediateStream;
             var checkSumStream = new CheckSumAndSizeWriteStream(
                 compressorStreamFactory,
-                backingStream,
+                streamForPosition ?? backingStream,
                 leaveCompressorStreamOpenOnClose,
                 this,
                 onClose,
@@ -808,6 +979,82 @@ private CheckSumAndSizeWriteStream GetDataCompressor(Stream backingStream, bool
             return checkSumStream;
         }
 
+        private byte CalculateZipCryptoCheckByte()
+        {
+            // If data descriptor NOT used, the check byte is the MSB of CRC32
+            if ((_generalPurposeBitFlag & BitFlagValues.DataDescriptor) == 0)
+                return (byte)((_crc32 >> 24) & 0xFF);
+
+            // If data descriptor IS used, the check byte is the MSB of the DOS time from the *local* header
+            return (byte)((ZipHelper.DateTimeToDosTime(_lastModified.DateTime) >> 8) & 0xFF);
+        }
+
+        private bool IsZipCryptoEncrypted => (_generalPurposeBitFlag & BitFlagValues.IsEncrypted) != 0 && (ushort)_headerCompressionMethod != WinZipAesMethod;
+
+        private bool UseAesEncryption()
+        {
+            return Encryption is ZipEncryptionMethod.Aes128 or ZipEncryptionMethod.Aes192 or ZipEncryptionMethod.Aes256;
+        }
+        private bool IsAesEncrypted => (ushort)_headerCompressionMethod == WinZipAesMethod;
+
+        private static int GetAesKeySizeBits(ZipEncryptionMethod encryption)
+        {
+            return encryption switch
+            {
+                ZipEncryptionMethod.Aes128 => 128,
+                ZipEncryptionMethod.Aes192 => 192,
+                ZipEncryptionMethod.Aes256 => 256,
+                _ => 256 // Default to AES-256
+            };
+        }
+
+        /// <summary>
+        /// Creates the appropriate decryption stream for an encrypted entry.
+        /// For AES entries, uses the salt that was pre-read during central directory parsing.
+        /// </summary>
+        private Stream WrapWithDecryptionIfNeeded(Stream compressedStream, ReadOnlySpan<char> password)
+        {
+            if (Encryption == ZipEncryptionMethod.Unknown)
+            {
+                throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
+            }
+
+            if (password.IsEmpty)
+            {
+                throw new InvalidDataException(SR.PasswordRequired);
+            }
+
+            if (IsAesEncrypted)
+            {
+                if (OperatingSystem.IsBrowser())
+                {
+                    throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnBrowser);
+                }
+
+                if (_aesSalt is null)
+                {
+                    throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
+                }
+
+                int keySizeBits = GetAesKeySizeBits(Encryption);
+                WinZipAesKeyMaterial keyMaterial = WinZipAesStream.CreateKey(password, _aesSalt, keySizeBits);
+                return WinZipAesStream.Create(
+                    baseStream: compressedStream,
+                    keyMaterial: keyMaterial,
+                    totalStreamSize: _compressedSize,
+                    encrypting: false);
+            }
+
+            if (IsZipCryptoEncrypted)
+            {
+                byte expectedCheckByte = CalculateZipCryptoCheckByte();
+                ZipCryptoKeys keyMaterial = ZipCryptoStream.CreateKey(password);
+                return ZipCryptoStream.Create(compressedStream, keyMaterial, expectedCheckByte, encrypting: false);
+            }
+
+            throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
+        }
+
         private Stream GetDataDecompressor(Stream compressedStreamToRead)
         {
             Stream? uncompressedStream;
@@ -820,11 +1067,15 @@ private Stream GetDataDecompressor(Stream compressedStreamToRead)
                     uncompressedStream = new DeflateManagedStream(compressedStreamToRead, ZipCompressionMethod.Deflate64, _uncompressedSize);
                     break;
                 case ZipCompressionMethod.Stored:
+                    uncompressedStream = compressedStreamToRead;
+                    break;
                 default:
-                    // we can assume that only deflate/deflate64/stored are allowed because we assume that
-                    // IsOpenable is checked before this function is called
-                    Debug.Assert(CompressionMethod == ZipCompressionMethod.Stored);
+                    // We should not get here with Aes as CompressionMethod anymore
+                    // as it should have been replaced with the actual compression method
+                    Debug.Assert((ushort)CompressionMethod != WinZipAesMethod,
+                        "AES compression method should have been replaced with actual compression method");
 
+                    // Fallback to stored if we somehow get here
                     uncompressedStream = compressedStreamToRead;
                     break;
             }
@@ -832,21 +1083,45 @@ private Stream GetDataDecompressor(Stream compressedStreamToRead)
             return uncompressedStream;
         }
 
-        private CrcValidatingReadStream OpenInReadMode(bool checkOpenable)
+        private Stream OpenInReadMode(bool checkOpenable, ReadOnlySpan<char> password = default)
         {
             if (checkOpenable)
                 ThrowIfNotOpenable(needToUncompress: true, needToLoadIntoMemory: false);
-            return OpenInReadModeGetDataCompressor(GetOffsetOfCompressedData());
+            return OpenInReadModeGetDataCompressor(GetOffsetOfCompressedData(), password);
         }
 
-        private CrcValidatingReadStream OpenInReadModeGetDataCompressor(long offsetOfCompressedData)
+        private Stream OpenInReadModeGetDataCompressor(long offsetOfCompressedData, ReadOnlySpan<char> password = default)
         {
             Stream compressedStream = new SubReadStream(_archive.ArchiveStream, offsetOfCompressedData, _compressedSize);
-            Stream decompressedStream = GetDataDecompressor(compressedStream);
+            Stream streamToDecompress;
 
-            return new CrcValidatingReadStream(decompressedStream, _crc32, _uncompressedSize);
+            if (IsEncrypted)
+            {
+                streamToDecompress = WrapWithDecryptionIfNeeded(compressedStream, password);
+            }
+            else
+            {
+                streamToDecompress = compressedStream;
+            }
+
+            return BuildDecompressionPipeline(streamToDecompress);
         }
 
+        /// <summary>
+        /// Wraps a (possibly decrypted) stream with decompression and CRC validation.
+        /// Shared by both sync and async read paths.
+        /// </summary>
+        private Stream BuildDecompressionPipeline(Stream streamToDecompress)
+        {
+            Stream decompressedStream = GetDataDecompressor(streamToDecompress);
+
+            // AE-2 encrypted entries store CRC as 0 so skip CRC validation for those.
+            // AE-1 version entries store a valid CRC.
+            if (IsAesEncrypted && _aeVersion == 2)
+                return decompressedStream;
+
+            return new CrcValidatingReadStream(decompressedStream, _crc32, _uncompressedSize);
+        }
         private WrappedStream OpenInWriteMode()
         {
             if (_everOpenedForWrite)
@@ -862,23 +1137,81 @@ private WrappedStream OpenInWriteModeCore()
         {
             _everOpenedForWrite = true;
             Changes |= ZipArchive.ChangeState.StoredData;
-            CheckSumAndSizeWriteStream crcSizeStream = GetDataCompressor(_archive.ArchiveStream, true, (object? o, EventArgs e) =>
+
+            // Use the encryption method pre-configured via PrepareEncryption (CreateEntry with password).
+            ZipEncryptionMethod encryptionMethod = Encryption;
+
+            // Build the stream stack with encryption if needed
+            Stream targetStream = _archive.ArchiveStream;
+            Stream? encryptionStream = null;
+
+            if (encryptionMethod == ZipEncryptionMethod.ZipCrypto)
             {
-                // release the archive stream
-                var entry = (ZipArchiveEntry)o!;
-                entry._archive.ReleaseArchiveStream(entry);
-                entry._outstandingWriteStream = null;
-            });
-            _outstandingWriteStream = new DirectToArchiveWriterStream(crcSizeStream, this);
+                ZipCryptoKeys keyMaterial = _derivedZipCryptoKeyMaterial
+                    ?? throw new InvalidOperationException(SR.EmptyPassword);
+
+                ushort verifierLow2Bytes = (ushort)ZipHelper.DateTimeToDosTime(_lastModified.DateTime);
+
+                targetStream = ZipCryptoStream.Create(
+                    baseStream: _archive.ArchiveStream,
+                    keys: keyMaterial,
+                    passwordVerifierLow2Bytes: verifierLow2Bytes,
+                    encrypting: true,
+                    crc32: null,
+                    leaveOpen: true);
+                encryptionStream = targetStream;
+            }
+            else if (encryptionMethod is ZipEncryptionMethod.Aes256 or ZipEncryptionMethod.Aes192 or ZipEncryptionMethod.Aes128)
+            {
+                if (OperatingSystem.IsBrowser())
+                {
+                    throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnBrowser);
+                }
+
+                WinZipAesKeyMaterial keyMaterial = _derivedAesKeyMaterial
+                    ?? throw new InvalidOperationException(SR.EmptyPassword);
+
+                targetStream = WinZipAesStream.Create(
+                    baseStream: _archive.ArchiveStream,
+                    keyMaterial: keyMaterial,
+                    totalStreamSize: -1,
+                    encrypting: true,
+                    leaveOpen: true);
+                encryptionStream = targetStream;
+            }
+
+            bool isAesEncryption = encryptionMethod is ZipEncryptionMethod.Aes256 or ZipEncryptionMethod.Aes192 or ZipEncryptionMethod.Aes128;
+
+            CheckSumAndSizeWriteStream crcSizeStream = GetDataCompressor(
+                targetStream,
+                leaveBackingStreamOpen: !isAesEncryption,
+                (object? o, EventArgs e) =>
+                {
+                    // release the archive stream
+                    var entry = (ZipArchiveEntry)o!;
+                    entry._archive.ReleaseArchiveStream(entry);
+                    entry._outstandingWriteStream = null;
+                },
+                streamForPosition: encryptionMethod != ZipEncryptionMethod.None ? _archive.ArchiveStream : null);
+
+            _outstandingWriteStream = new DirectToArchiveWriterStream(crcSizeStream, this, encryptionMethod, encryptionStream);
 
             return new WrappedStream(baseStream: _outstandingWriteStream, closeBaseStream: true);
         }
 
-        private WrappedStream OpenInUpdateMode(bool loadExistingContent = true)
+        private WrappedStream OpenInUpdateMode(bool loadExistingContent = true, ReadOnlySpan<char> password = default)
         {
             if (_currentlyOpenForWrite)
                 throw new IOException(SR.UpdateModeOneStream);
 
+            if (Encryption == ZipEncryptionMethod.Unknown)
+                throw new NotSupportedException(SR.UnsupportedEncryptionMethod);
+
+            // Encrypted entries always require a password for re-encryption,
+            // even when discarding existing content (write-only access).
+            if (IsEncrypted && password.IsEmpty)
+                throw new ArgumentException(SR.PasswordRequired, nameof(password));
+
             if (loadExistingContent)
             {
                 ThrowIfNotOpenable(needToUncompress: true, needToLoadIntoMemory: true);
@@ -886,9 +1219,15 @@ private WrappedStream OpenInUpdateMode(bool loadExistingContent = true)
 
             _currentlyOpenForWrite = true;
 
+            // Set up re-encryption key material so that the rewritten entry has valid encryption headers.
+            if (IsEncrypted)
+            {
+                SetupEncryptionKeyMaterial(password);
+            }
+
             if (loadExistingContent)
             {
-                _storedUncompressedData = GetUncompressedData();
+                _storedUncompressedData = GetUncompressedData(password);
             }
             else
             {
@@ -921,6 +1260,86 @@ internal void MarkAsModified()
             }
         }
 
+        /// <summary>
+        /// Sets up encryption key material for re-encryption when writing back to the archive.
+        /// </summary>
+        private void SetupEncryptionKeyMaterial(ReadOnlySpan<char> password)
+        {
+            // Derive and save key material for re-encryption
+            if (IsZipCryptoEncrypted)
+            {
+                _derivedZipCryptoKeyMaterial = ZipCryptoStream.CreateKey(password);
+                Encryption = ZipEncryptionMethod.ZipCrypto;
+            }
+            else if (UseAesEncryption())
+            {
+                if (OperatingSystem.IsBrowser())
+                {
+                    throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnBrowser);
+                }
+                // Generate new salt and derive key material for AES
+                // This ensures each write uses a fresh random salt for security
+                int keySizeBits = GetAesKeySizeBits(Encryption);
+                _derivedAesKeyMaterial = WinZipAesStream.CreateKey(password, salt: null, keySizeBits);
+                // Encryption is already set from constructor (parsed from central directory AES extra field)
+            }
+        }
+
+        /// <summary>
+        /// Pre-derives encryption key material from a password for use when the entry is later opened for writing.
+        /// Called by <see cref="ZipArchive.CreateEntry(string, ReadOnlySpan{char}, ZipEncryptionMethod)"/>.
+        /// </summary>
+        internal void PrepareEncryption(ReadOnlySpan<char> password, ZipEncryptionMethod encryptionMethod)
+        {
+            if (password.IsEmpty)
+            {
+                throw new ArgumentException(SR.EmptyPassword, nameof(password));
+            }
+
+            if (encryptionMethod is ZipEncryptionMethod.None or ZipEncryptionMethod.Unknown)
+            {
+                throw new ArgumentOutOfRangeException(nameof(encryptionMethod), SR.EncryptionNotSpecified);
+            }
+
+            Encryption = encryptionMethod;
+
+            if (encryptionMethod == ZipEncryptionMethod.ZipCrypto)
+            {
+                _derivedZipCryptoKeyMaterial = ZipCryptoStream.CreateKey(password);
+            }
+            else if (encryptionMethod is ZipEncryptionMethod.Aes128 or ZipEncryptionMethod.Aes192 or ZipEncryptionMethod.Aes256)
+            {
+                if (OperatingSystem.IsBrowser())
+                {
+                    throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnBrowser);
+                }
+
+                int keySizeBits = GetAesKeySizeBits(encryptionMethod);
+                _derivedAesKeyMaterial = WinZipAesStream.CreateKey(password, salt: null, keySizeBits);
+            }
+        }
+
+        /// <summary>
+        /// Creates a WinZip AES extra field for writing to local/central directory headers.
+        /// </summary>
+        private WinZipAesExtraField CreateAesExtraField()
+        {
+            return new WinZipAesExtraField
+            {
+                VendorVersion = 2,
+                AesStrength = Encryption switch
+                {
+                    ZipEncryptionMethod.Aes128 => (byte)1,
+                    ZipEncryptionMethod.Aes192 => (byte)2,
+                    ZipEncryptionMethod.Aes256 => (byte)3,
+                    _ => (byte)3 // Default to AES-256
+                },
+                CompressionMethod = _compressionLevel == CompressionLevel.NoCompression
+                    ? (ushort)ZipCompressionMethod.Stored
+                    : (ushort)ZipCompressionMethod.Deflate
+            };
+        }
+
         private bool IsOpenable(bool needToUncompress, bool needToLoadIntoMemory, out string? message)
         {
             message = null;
@@ -931,14 +1350,28 @@ private bool IsOpenable(bool needToUncompress, bool needToLoadIntoMemory, out st
                 {
                     return false;
                 }
-                if (!ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveStream))
+
+                if (!IsEncrypted && !ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveStream))
                 {
                     message = SR.LocalFileHeaderCorrupt;
                     return false;
                 }
+                else if (IsEncrypted && IsAesEncrypted)
+                {
+                    _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
+                    // AES case - skip the local file header and validate it exists.
+                    // The AES metadata (encryption strength, actual compression method) was already
+                    // parsed from the central directory in the constructor
+                    if (!ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveStream))
+                    {
+                        message = SR.LocalFileHeaderCorrupt;
+                        return false;
+                    }
+                }
 
-                // when this property gets called, some duplicated work
+                // Pass the detected encryption method to GetOffsetOfCompressedData
                 long offsetOfCompressedData = GetOffsetOfCompressedData();
+
                 if (!IsOpenableFinalVerifications(needToLoadIntoMemory, offsetOfCompressedData, out message))
                 {
                     return false;
@@ -955,6 +1388,9 @@ private bool IsOpenableInitialVerifications(bool needToUncompress, out string? m
             message = null;
             if (needToUncompress)
             {
+                // For AES-encrypted entries, CompressionMethod now contains the actual compression
+                // method (from the AES extra field), not the wrapper value 99. So we can use
+                // the same validation logic for both encrypted and non-encrypted entries.
                 if (CompressionMethod != ZipCompressionMethod.Stored &&
                     CompressionMethod != ZipCompressionMethod.Deflate &&
                     CompressionMethod != ZipCompressionMethod.Deflate64)
@@ -1061,6 +1497,7 @@ private static BitFlagValues MapDeflateCompressionOption(BitFlagValues generalPu
         private bool IsOffsetTooLarge => _offsetOfLocalHeader > uint.MaxValue;
 
         private bool ShouldUseZIP64 => AreSizesTooLarge || IsOffsetTooLarge;
+        internal ZipEncryptionMethod Encryption { get => _encryptionMethod; private set => _encryptionMethod = value; }
 
         private unsafe bool WriteLocalFileHeaderInitialize(bool isEmptyFile, bool forceWrite, bool preserveDataDescriptor, out Zip64ExtraField? zip64ExtraField, out uint compressedSizeTruncated, out uint uncompressedSizeTruncated, out ushort extraFieldLength, out uint crc32ToWrite)
         {
@@ -1074,6 +1511,10 @@ private unsafe bool WriteLocalFileHeaderInitialize(bool isEmptyFile, bool forceW
             // save offset
             _offsetOfLocalHeader = _archive.ArchiveStream.Position;
 
+            // for extra winzip aes header
+            WinZipAesExtraField? aesExtraField = null;
+            int aesExtraFieldSize = 0;
+
             // if we already know that we have an empty file don't worry about anything, just do a straight shot of the header
             if (isEmptyFile)
             {
@@ -1085,28 +1526,43 @@ private unsafe bool WriteLocalFileHeaderInitialize(bool isEmptyFile, bool forceW
             }
             else
             {
-                // if we have a non-seekable stream, don't worry about sizes at all, and just set the right bit
-                // if we are using the data descriptor, then sizes and crc should be set to 0 in the header
-                if (_archive.Mode == ZipArchiveMode.Create && !_archive.ArchiveStream.CanSeek)
+                bool isCreateMode = _archive.Mode == ZipArchiveMode.Create;
+
+                if (Encryption == ZipEncryptionMethod.ZipCrypto)
+                {
+                    _generalPurposeBitFlag |= BitFlagValues.IsEncrypted;
+                    _generalPurposeBitFlag |= BitFlagValues.DataDescriptor;
+                    compressedSizeTruncated = 0;
+                    uncompressedSizeTruncated = 0;
+                }
+                else if (UseAesEncryption())
+                {
+                    _generalPurposeBitFlag |= BitFlagValues.IsEncrypted;
+                    CompressionMethod = (ZipCompressionMethod)WinZipAesMethod;
+                    compressedSizeTruncated = 0;
+                    uncompressedSizeTruncated = 0;
+                    aesExtraField = CreateAesExtraField();
+                    aesExtraFieldSize = WinZipAesExtraField.TotalSize;
+                }
+                else if (isCreateMode && !_archive.ArchiveStream.CanSeek)
                 {
                     _generalPurposeBitFlag |= BitFlagValues.DataDescriptor;
                     compressedSizeTruncated = 0;
                     uncompressedSizeTruncated = 0;
-                    // the crc should not have been set if we are in create mode, but clear it just to be sure
                     Debug.Assert(_crc32 == 0);
                 }
-                else // if we are not in streaming mode, we have to decide if we want to write zip64 headers
+                else
                 {
+
                     if (ShouldUseZIP64
 #if DEBUG_FORCE_ZIP64
-                        || (_archive._forceZip64 && _archive.Mode == ZipArchiveMode.Update)
+                || (_archive._forceZip64 && _archive.Mode == ZipArchiveMode.Update)
 #endif
                         )
                     {
                         compressedSizeTruncated = ZipHelper.Mask32Bit;
                         uncompressedSizeTruncated = ZipHelper.Mask32Bit;
 
-                        // prepare Zip64 extra field object. If we have one of the sizes, the other must go in there
                         zip64ExtraField = new()
                         {
                             CompressedSize = _compressedSize,
@@ -1126,14 +1582,18 @@ private unsafe bool WriteLocalFileHeaderInitialize(bool isEmptyFile, bool forceW
             // save offset
             _offsetOfLocalHeader = _archive.ArchiveStream.Position;
 
-            // calculate extra field. if zip64 stuff + original extraField aren't going to fit, dump the original extraField, because this is more important
-            int currExtraFieldDataLength = ZipGenericExtraField.TotalSize(_lhUnknownExtraFields, _lhTrailingExtraFieldData?.Length ?? 0);
+            // Calculate extra field
+            // When using AES encryption, exclude the AES tag from currExtraFieldDataLength since we're writing a new one
+            int currExtraFieldDataLength = UseAesEncryption()
+                ? ZipGenericExtraField.TotalSizeExcludingTag(_lhUnknownExtraFields, _lhTrailingExtraFieldData?.Length ?? 0, WinZipAesExtraField.HeaderId)
+                : ZipGenericExtraField.TotalSize(_lhUnknownExtraFields, _lhTrailingExtraFieldData?.Length ?? 0);
             int bigExtraFieldLength = (zip64ExtraField != null ? zip64ExtraField.TotalSize : 0)
+                                      + aesExtraFieldSize
                                       + currExtraFieldDataLength;
 
             if (bigExtraFieldLength > ushort.MaxValue)
             {
-                extraFieldLength = (ushort)(zip64ExtraField != null ? zip64ExtraField.TotalSize : 0);
+                extraFieldLength = (ushort)((zip64ExtraField != null ? zip64ExtraField.TotalSize : 0) + aesExtraFieldSize);
                 _lhUnknownExtraFields = null;
             }
             else
@@ -1143,18 +1603,19 @@ private unsafe bool WriteLocalFileHeaderInitialize(bool isEmptyFile, bool forceW
 
             crc32ToWrite = _crc32;
 
+            // For AE-2, CRC is always 0 in the local file header
+            if (UseAesEncryption())
+            {
+                crc32ToWrite = 0;
+            }
+
             // If this is an existing, unchanged entry then silently skip forwards.
             // If it's new or changed, write the header.
             if (_originallyInArchive && Changes == ZipArchive.ChangeState.Unchanged && !forceWrite)
             {
-                _archive.ArchiveStream.Seek(ZipLocalFileHeader.SizeOfLocalHeader + _storedEntryNameBytes.Length, SeekOrigin.Current);
+                _archive.ArchiveStream.Seek(ZipLocalFileHeader.SizeOfLocalHeader + _storedEntryNameBytes.Length + extraFieldLength, SeekOrigin.Current);
 
-                if (zip64ExtraField != null)
-                {
-                    _archive.ArchiveStream.Seek(zip64ExtraField.TotalSize, SeekOrigin.Current);
-                }
 
-                _archive.ArchiveStream.Seek(currExtraFieldDataLength, SeekOrigin.Current);
 
                 return false;
             }
@@ -1176,7 +1637,7 @@ private unsafe bool WriteLocalFileHeaderInitialize(bool isEmptyFile, bool forceW
                         zip64ExtraField = new() { CompressedSize = 0, UncompressedSize = 0 };
                     }
                 }
-                else
+                else if (Encryption != ZipEncryptionMethod.ZipCrypto)
                 {
                     _generalPurposeBitFlag &= ~BitFlagValues.DataDescriptor;
                 }
@@ -1190,7 +1651,11 @@ private void WriteLocalFileHeaderPrepare(Span<byte> lfStaticHeader, uint crc32,
             ZipLocalFileHeader.SignatureConstantBytes.CopyTo(lfStaticHeader[ZipLocalFileHeader.FieldLocations.Signature..]);
             BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.VersionNeededToExtract..], (ushort)_versionToExtract);
             BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.GeneralPurposeBitFlags..], (ushort)_generalPurposeBitFlag);
-            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.CompressionMethod..], (ushort)CompressionMethod);
+
+            // For AES encryption, write compression method 99 (Aes) in the header
+            ushort compressionMethodToWrite = UseAesEncryption() ? (ushort)WinZipAesMethod : (ushort)CompressionMethod;
+            BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.CompressionMethod..], compressionMethodToWrite);
+
             BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.LastModified..], ZipHelper.DateTimeToDosTime(_lastModified.DateTime));
             BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.Crc32..], crc32);
             BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.CompressedSize..], compressedSizeTruncated);
@@ -1211,16 +1676,28 @@ private unsafe bool WriteLocalFileHeader(bool isEmptyFile, bool forceWrite, bool
                 _archive.ArchiveStream.Write(lfStaticHeader);
                 _archive.ArchiveStream.Write(_storedEntryNameBytes);
 
-                // Only when handling zip64
+                // Write Zip64 extra field if needed
                 zip64ExtraField?.WriteBlock(_archive.ArchiveStream);
 
-                ZipGenericExtraField.WriteAllBlocks(_lhUnknownExtraFields, _lhTrailingExtraFieldData ?? Array.Empty<byte>(), _archive.ArchiveStream);
+                // Write AES extra field if using AES encryption
+                if (UseAesEncryption())
+                {
+                    CreateAesExtraField().WriteBlock(_archive.ArchiveStream);
+
+                    // Write other extra fields, excluding any existing AES extra field to avoid duplication
+                    ZipGenericExtraField.WriteAllBlocksExcludingTag(_lhUnknownExtraFields, _lhTrailingExtraFieldData ?? Array.Empty<byte>(), _archive.ArchiveStream, WinZipAesExtraField.HeaderId);
+                }
+                else
+                {
+                    // Write other extra fields
+                    ZipGenericExtraField.WriteAllBlocks(_lhUnknownExtraFields, _lhTrailingExtraFieldData ?? Array.Empty<byte>(), _archive.ArchiveStream);
+                }
             }
 
             return zip64ExtraField != null;
         }
 
-        private void WriteLocalFileHeaderAndDataIfNeeded(bool forceWrite)
+        private unsafe void WriteLocalFileHeaderAndDataIfNeeded(bool forceWrite)
         {
             // Check if the entry's stored data was actually modified (StoredData flag is set).
             // If _storedUncompressedData is loaded but StoredData is not set, it means the entry
@@ -1241,19 +1718,99 @@ private void WriteLocalFileHeaderAndDataIfNeeded(bool forceWrite)
                 {
                     _uncompressedSize = _storedUncompressedData.Length;
 
-                    //The compressor fills in CRC and sizes
-                    //The DirectToArchiveWriterStream writes headers and such
-                    using (DirectToArchiveWriterStream entryWriter = new(
-                                                    GetDataCompressor(_archive.ArchiveStream, true, null),
-                                                    this))
+                    // Check if we need to re-encrypt with ZipCrypto (only if we have cached key material)
+                    if (Encryption == ZipEncryptionMethod.ZipCrypto && _derivedZipCryptoKeyMaterial is not null)
                     {
-                        _storedUncompressedData.Seek(0, SeekOrigin.Begin);
-                        _storedUncompressedData.CopyTo(entryWriter);
+                        WriteLocalFileHeader(isEmptyFile: false, forceWrite: true);
+
+                        long startPosition = _archive.ArchiveStream.Position;
+
+                        ushort verifierLow2Bytes = (ushort)ZipHelper.DateTimeToDosTime(_lastModified.DateTime);
+
+                        using (var encryptionStream = ZipCryptoStream.Create(
+                            baseStream: _archive.ArchiveStream,
+                            keys: _derivedZipCryptoKeyMaterial.Value,
+                            passwordVerifierLow2Bytes: verifierLow2Bytes,
+                            encrypting: true,
+                            crc32: null,
+                            leaveOpen: true))
+                        {
+                            using (var crcStream = GetDataCompressor(encryptionStream, leaveBackingStreamOpen: true, onClose: null, streamForPosition: _archive.ArchiveStream))
+                            {
+                                _storedUncompressedData.Seek(0, SeekOrigin.Begin);
+                                _storedUncompressedData.CopyTo(crcStream);
+                            }
+                        }
+
+                        _compressedSize = _archive.ArchiveStream.Position - startPosition;
+
+                        WriteDataDescriptor();
+
+                        _storedUncompressedData.Dispose();
+                        _storedUncompressedData = null;
+                    }
+                    else if (UseAesEncryption() && _derivedAesKeyMaterial is not null)
+                    {
+                        if (OperatingSystem.IsBrowser())
+                        {
+                            throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnBrowser);
+                        }
+
+                        bool usedZip64InLH = WriteLocalFileHeader(isEmptyFile: false, forceWrite: true);
+
+                        long startPosition = _archive.ArchiveStream.Position;
+
+                        bool useDeflate = _compressionLevel != CompressionLevel.NoCompression;
+
+                        using (var encryptionStream = WinZipAesStream.Create(
+                            baseStream: _archive.ArchiveStream,
+                            keyMaterial: _derivedAesKeyMaterial.Value,
+                            totalStreamSize: -1,
+                            encrypting: true,
+                            leaveOpen: true))
+                        {
+                            if (_storedUncompressedData.Length > 0)
+                            {
+                                ZipCompressionMethod savedMethod = CompressionMethod;
+                                CompressionMethod = useDeflate ? ZipCompressionMethod.Deflate : ZipCompressionMethod.Stored;
+
+                                using (var crcStream = GetDataCompressor(encryptionStream, leaveBackingStreamOpen: true, onClose: null, streamForPosition: _archive.ArchiveStream))
+                                {
+                                    _storedUncompressedData.Seek(0, SeekOrigin.Begin);
+                                    _storedUncompressedData.CopyTo(crcStream);
+                                }
+
+                                CompressionMethod = (ZipCompressionMethod)WinZipAesMethod;
+                            }
+                            else
+                            {
+                                _crc32 = 0;
+                                _uncompressedSize = 0;
+                            }
+                        }
+
+                        _compressedSize = _archive.ArchiveStream.Position - startPosition;
+
+                        WriteCrcAndSizesInLocalHeader(usedZip64InLH);
+
+                        _storedUncompressedData.Dispose();
+                        _storedUncompressedData = null;
+                    }
+                    else
+                    {
+                        // Non-encrypted: use standard path
+                        using (DirectToArchiveWriterStream entryWriter = new(
+                                                        GetDataCompressor(_archive.ArchiveStream, true, null, null),
+                                                        this))
+                        {
+                            _storedUncompressedData.Seek(0, SeekOrigin.Begin);
+                            _storedUncompressedData.CopyTo(entryWriter);
+                        }
                         _storedUncompressedData.Dispose();
                         _storedUncompressedData = null;
                     }
                 }
-                else
+                else // _compressedBytes path - copying unchanged entry data
                 {
                     if (_uncompressedSize == 0)
                     {
@@ -1261,8 +1818,48 @@ private void WriteLocalFileHeaderAndDataIfNeeded(bool forceWrite)
                         _compressedSize = 0;
                     }
 
+                    // For unchanged entries, we need to write the header correctly but avoid
+                    // WriteLocalFileHeader creating NEW encryption structures (which would have
+                    // wrong compression method from _compressionLevel).
+                    // The original AES extra field is preserved in _lhUnknownExtraFields.
+                    BitFlagValues savedFlags = _generalPurposeBitFlag;
+                    ZipEncryptionMethod savedEncryption = Encryption;
+                    ZipCompressionMethod savedCompressionMethod = CompressionMethod;
+
+                    // For AES entries: set CompressionMethod to Aes so header writes method 99,
+                    // but clear _encryptionMethod so WriteLocalFileHeader doesn't create a new
+                    // AES extra field (the original one in _lhUnknownExtraFields will be used).
+                    if (savedEncryption is ZipEncryptionMethod.Aes128 or ZipEncryptionMethod.Aes192 or ZipEncryptionMethod.Aes256)
+                    {
+                        CompressionMethod = (ZipCompressionMethod)WinZipAesMethod;
+                        Encryption = ZipEncryptionMethod.None;
+                    }
+
                     WriteLocalFileHeader(isEmptyFile: _uncompressedSize == 0, forceWrite: true);
 
+                    // WriteLocalFileHeaderInitialize may have cleared the DataDescriptor flag
+                    // (because Encryption was temporarily set to None and the stream is seekable).
+                    // If the original entry had a data descriptor, patch the general-purpose bit
+                    // flags in the already-written local header to match, so the header on disk
+                    // is consistent with the data descriptor we conditionally write below.
+                    if ((savedFlags & BitFlagValues.DataDescriptor) != 0 &&
+                        (_generalPurposeBitFlag & BitFlagValues.DataDescriptor) == 0)
+                    {
+                        long currentPos = _archive.ArchiveStream.Position;
+                        _archive.ArchiveStream.Seek(
+                            _offsetOfLocalHeader + ZipLocalFileHeader.FieldLocations.GeneralPurposeBitFlags,
+                            SeekOrigin.Begin);
+                        Span<byte> flagBytes = stackalloc byte[2];
+                        BinaryPrimitives.WriteUInt16LittleEndian(flagBytes, (ushort)savedFlags);
+                        _archive.ArchiveStream.Write(flagBytes);
+                        _archive.ArchiveStream.Seek(currentPos, SeekOrigin.Begin);
+                    }
+
+                    // Restore original state
+                    _generalPurposeBitFlag = savedFlags;
+                    Encryption = savedEncryption;
+                    CompressionMethod = savedCompressionMethod;
+
                     // according to ZIP specs, zero-byte files MUST NOT include file data
                     if (_uncompressedSize != 0)
                     {
@@ -1272,6 +1869,12 @@ private void WriteLocalFileHeaderAndDataIfNeeded(bool forceWrite)
                             _archive.ArchiveStream.Write(compressedBytes, 0, compressedBytes.Length);
                         }
                     }
+
+                    // Write data descriptor if the original entry had one
+                    if ((savedFlags & BitFlagValues.DataDescriptor) != 0)
+                    {
+                        WriteDataDescriptor();
+                    }
                 }
             }
             else // there is no data in the file (or the data in the file has not been loaded), but if we are in update mode, we may still need to write a header
@@ -1389,8 +1992,9 @@ private void WriteCrcAndSizesInLocalHeaderPrepareFor32bitValuesWriting(bool pret
                 int relativeCrc32Location = ZipLocalFileHeader.FieldLocations.Crc32 - ZipLocalFileHeader.FieldLocations.Crc32;
                 int relativeCompressedSizeLocation = ZipLocalFileHeader.FieldLocations.CompressedSize - ZipLocalFileHeader.FieldLocations.Crc32;
                 int relativeUncompressedSizeLocation = ZipLocalFileHeader.FieldLocations.UncompressedSize - ZipLocalFileHeader.FieldLocations.Crc32;
-
-                BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer[relativeCrc32Location..], _crc32);
+                // when using aes encryption, ae-2 standard dictates crc to be 0
+                uint crcToWrite = UseAesEncryption() ? 0 : _crc32;
+                BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer[relativeCrc32Location..], crcToWrite);
                 BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer[relativeCompressedSizeLocation..], compressedSizeTruncated);
                 BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer[relativeUncompressedSizeLocation..], uncompressedSizeTruncated);
             }
@@ -1417,8 +2021,9 @@ private void WriteCrcAndSizesInLocalHeaderPrepareForWritingDataDescriptor(Span<b
             int relativeCrc32Location = ZipLocalFileHeader.Zip64DataDescriptor.FieldLocations.Crc32 - ZipLocalFileHeader.Zip64DataDescriptor.FieldLocations.Crc32;
             int relativeCompressedSizeLocation = ZipLocalFileHeader.Zip64DataDescriptor.FieldLocations.CompressedSize - ZipLocalFileHeader.Zip64DataDescriptor.FieldLocations.Crc32;
             int relativeUncompressedSizeLocation = ZipLocalFileHeader.Zip64DataDescriptor.FieldLocations.UncompressedSize - ZipLocalFileHeader.Zip64DataDescriptor.FieldLocations.Crc32;
-
-            BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer.Slice(relativeCrc32Location), _crc32);
+            // when using aes encryption, ae-2 standard dictates crc to be 0
+            uint crcToWrite = UseAesEncryption() ? 0 : _crc32;
+            BinaryPrimitives.WriteUInt32LittleEndian(writeBuffer.Slice(relativeCrc32Location), crcToWrite);
             BinaryPrimitives.WriteInt64LittleEndian(writeBuffer.Slice(relativeCompressedSizeLocation), _compressedSize);
             BinaryPrimitives.WriteInt64LittleEndian(writeBuffer.Slice(relativeUncompressedSizeLocation), _uncompressedSize);
 
@@ -1443,7 +2048,9 @@ private int PrepareToWriteDataDescriptor(Span<byte> dataDescriptor)
             int bytesToWrite;
 
             ZipLocalFileHeader.DataDescriptorSignatureConstantBytes.CopyTo(dataDescriptor[ZipLocalFileHeader.ZipDataDescriptor.FieldLocations.Signature..]);
-            BinaryPrimitives.WriteUInt32LittleEndian(dataDescriptor[ZipLocalFileHeader.ZipDataDescriptor.FieldLocations.Crc32..], _crc32);
+            // when using aes encryption, ae-2 standard dictates crc to be 0
+            uint crcToWrite = UseAesEncryption() ? 0 : _crc32;
+            BinaryPrimitives.WriteUInt32LittleEndian(dataDescriptor[ZipLocalFileHeader.ZipDataDescriptor.FieldLocations.Crc32..], crcToWrite);
 
             if (AreSizesTooLarge)
             {
@@ -1528,10 +2135,12 @@ private sealed class DirectToArchiveWriterStream : Stream
             private readonly ZipArchiveEntry _entry;
             private bool _usedZip64inLH;
             private bool _canWrite;
+            private readonly ZipEncryptionMethod _encryption;
+            private readonly Stream? _encryptionStream;
 
             // makes the assumption that somewhere down the line, crcSizeStream is eventually writing directly to the archive
             // this class calls other functions on ZipArchiveEntry that write directly to the archive
-            public DirectToArchiveWriterStream(CheckSumAndSizeWriteStream crcSizeStream, ZipArchiveEntry entry)
+            public DirectToArchiveWriterStream(CheckSumAndSizeWriteStream crcSizeStream, ZipArchiveEntry entry, ZipEncryptionMethod encryptionMethod = ZipEncryptionMethod.None, Stream? encryptionStream = null)
             {
                 _position = 0;
                 _crcSizeStream = crcSizeStream;
@@ -1540,6 +2149,8 @@ public DirectToArchiveWriterStream(CheckSumAndSizeWriteStream crcSizeStream, Zip
                 _entry = entry;
                 _usedZip64inLH = false;
                 _canWrite = true;
+                _encryption = encryptionMethod;
+                _encryptionStream = encryptionStream;
             }
 
             public override long Length
@@ -1702,8 +2313,14 @@ protected override void Dispose(bool disposing)
                 {
                     _crcSizeStream.Dispose(); // now we have size/crc info
 
+                    // If no data was written through CheckSumAndSizeWriteStream, its lazy _baseStream
+                    // (DeflateStream wrapping the encryption stream) was never created, so the encryption
+                    // stream would be orphaned. Dispose it explicitly to finalize encryption
+                    // (e.g., write the ZipCrypto 12-byte header or AES salt/verifier/HMAC).
                     if (!_everWritten)
                     {
+                        _encryptionStream?.Dispose();
+
                         // write local header, no data, so we use stored
                         _entry.WriteLocalFileHeader(isEmptyFile: true, forceWrite: true);
                     }
@@ -1711,8 +2328,17 @@ protected override void Dispose(bool disposing)
                     {
                         // go back and finish writing
                         if (_entry._archive.ArchiveStream.CanSeek)
+                        {
                             // finish writing local header if we have seek capabilities
                             _entry.WriteCrcAndSizesInLocalHeader(_usedZip64inLH);
+
+                            // ZipCrypto entries retain DataDescriptor for check byte correctness;
+                            // write the trailing descriptor so the archive is consistent.
+                            if ((_entry._generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0)
+                            {
+                                _entry.WriteDataDescriptor();
+                            }
+                        }
                         else
                             // write out data descriptor if we don't have seek capabilities
                             _entry.WriteDataDescriptor();
@@ -1730,8 +2356,17 @@ public override async ValueTask DisposeAsync()
                 {
                     await _crcSizeStream.DisposeAsync().ConfigureAwait(false); // now we have size/crc info
 
+                    // If no data was written through CheckSumAndSizeWriteStream, its lazy _baseStream
+                    // (DeflateStream wrapping the encryption stream) was never created, so the encryption
+                    // stream would be orphaned. Dispose it explicitly to finalize encryption
+                    // (e.g., write the ZipCrypto 12-byte header or AES salt/verifier/HMAC).
                     if (!_everWritten)
                     {
+                        if (_encryptionStream is not null)
+                        {
+                            await _encryptionStream.DisposeAsync().ConfigureAwait(false);
+                        }
+
                         // write local header, no data, so we use stored
                         await _entry.WriteLocalFileHeaderAsync(isEmptyFile: true, forceWrite: true, preserveDataDescriptor: false, cancellationToken: default).ConfigureAwait(false);
                     }
@@ -1739,8 +2374,17 @@ public override async ValueTask DisposeAsync()
                     {
                         // go back and finish writing
                         if (_entry._archive.ArchiveStream.CanSeek)
+                        {
                             // finish writing local header if we have seek capabilities
                             await _entry.WriteCrcAndSizesInLocalHeaderAsync(_usedZip64inLH, cancellationToken: default).ConfigureAwait(false);
+
+                            // ZipCrypto entries retain DataDescriptor for check byte correctness;
+                            // write the trailing descriptor so the archive is consistent.
+                            if ((_entry._generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0)
+                            {
+                                await _entry.WriteDataDescriptorAsync(cancellationToken: default).ConfigureAwait(false);
+                            }
+                        }
                         else
                             // write out data descriptor if we don't have seek capabilities
                             await _entry.WriteDataDescriptorAsync(cancellationToken: default).ConfigureAwait(false);
@@ -1752,12 +2396,12 @@ public override async ValueTask DisposeAsync()
                 await base.DisposeAsync().ConfigureAwait(false);
             }
         }
-
         [Flags]
         internal enum BitFlagValues : ushort
         {
             IsEncrypted = 0x1,
             DataDescriptor = 0x8,
+            StrongEncryption = 0x40,
             UnicodeFileNameAndComment = 0x800
         }
 
diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipBlocks.Async.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipBlocks.Async.cs
index fea75ba16db328..30445fd5c8d0c6 100644
--- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipBlocks.Async.cs
+++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipBlocks.Async.cs
@@ -38,6 +38,27 @@ public static async Task WriteAllBlocksAsync(List<ZipGenericExtraField>? fields,
             await stream.WriteAsync(trailingExtraFieldData, cancellationToken).ConfigureAwait(false);
         }
     }
+
+    public static async Task WriteAllBlocksExcludingTagAsync(List<ZipGenericExtraField>? fields, ReadOnlyMemory<byte> trailingExtraFieldData, Stream stream, ushort excludeTag, CancellationToken cancellationToken)
+    {
+        cancellationToken.ThrowIfCancellationRequested();
+
+        if (fields != null)
+        {
+            foreach (ZipGenericExtraField field in fields)
+            {
+                if (field.Tag != excludeTag)
+                {
+                    await field.WriteBlockAsync(stream, cancellationToken).ConfigureAwait(false);
+                }
+            }
+        }
+
+        if (!trailingExtraFieldData.IsEmpty)
+        {
+            await stream.WriteAsync(trailingExtraFieldData, cancellationToken).ConfigureAwait(false);
+        }
+    }
 }
 
 internal sealed partial class Zip64ExtraField
@@ -147,9 +168,8 @@ public static async Task<bool> TrySkipBlockAsync(Stream stream, CancellationToke
         cancellationToken.ThrowIfCancellationRequested();
 
         byte[] blockBytes = new byte[FieldLengths.Signature];
-        long currPosition = stream.Position;
         int bytesRead = await stream.ReadAtLeastAsync(blockBytes, blockBytes.Length, throwOnEndOfStream: false, cancellationToken).ConfigureAwait(false);
-        if (!TrySkipBlockCore(stream, blockBytes, bytesRead, currPosition))
+        if (!TrySkipBlockCore(stream, blockBytes, bytesRead))
         {
             return false;
         }
diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipBlocks.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipBlocks.cs
index ee5bd67e21f65c..8ae37d18089113 100644
--- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipBlocks.cs
+++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipBlocks.cs
@@ -116,6 +116,43 @@ public static void WriteAllBlocks(List<ZipGenericExtraField>? fields, ReadOnlySp
                 stream.Write(trailingExtraFieldData);
             }
         }
+
+        public static void WriteAllBlocksExcludingTag(List<ZipGenericExtraField>? fields, ReadOnlySpan<byte> trailingExtraFieldData, Stream stream, ushort excludeTag)
+        {
+            if (fields != null)
+            {
+                foreach (ZipGenericExtraField field in fields)
+                {
+                    if (field.Tag != excludeTag)
+                    {
+                        field.WriteBlock(stream);
+                    }
+                }
+            }
+
+            if (!trailingExtraFieldData.IsEmpty)
+            {
+                stream.Write(trailingExtraFieldData);
+            }
+        }
+
+        public static int TotalSizeExcludingTag(List<ZipGenericExtraField>? fields, int trailingDataLength, ushort excludeTag)
+        {
+            int size = trailingDataLength;
+
+            if (fields != null)
+            {
+                foreach (ZipGenericExtraField field in fields)
+                {
+                    if (field.Tag != excludeTag)
+                    {
+                        size += field.Size + ZipGenericExtraField.FieldLengths.Tag + ZipGenericExtraField.FieldLengths.Size;
+                    }
+                }
+            }
+
+            return size;
+        }
     }
 
     internal sealed partial class Zip64ExtraField
@@ -613,18 +650,13 @@ public static unsafe List<ZipGenericExtraField> GetExtraFields(Stream stream, ou
             }
         }
 
-        private static bool TrySkipBlockCore(Stream stream, Span<byte> blockBytes, int bytesRead, long currPosition)
+        private static bool TrySkipBlockCore(Stream stream, Span<byte> blockBytes, int bytesRead)
         {
             if (bytesRead != FieldLengths.Signature || !blockBytes.SequenceEqual(SignatureConstantBytes))
             {
                 return false;
             }
 
-            if (stream.Length < currPosition + FieldLocations.FilenameLength)
-            {
-                return false;
-            }
-
             // Already read the signature, so make the filename length field location relative to that
             stream.Seek(FieldLocations.FilenameLength - FieldLengths.Signature, SeekOrigin.Current);
 
@@ -652,7 +684,11 @@ private static bool TrySkipBlockFinalize(Stream stream, Span<byte> blockBytes, i
                 return false;
             }
 
-            stream.Seek(filenameLength + extraFieldLength, SeekOrigin.Current);
+            // Calculate absolute position of compressed data and seek there
+            // Using SeekOrigin.Begin ensures we end up at the correct position
+            // regardless of any edge cases during header parsing
+            long dataStart = stream.Position + filenameLength + extraFieldLength;
+            stream.Seek(dataStart, SeekOrigin.Begin);
 
             return true;
         }
@@ -661,17 +697,154 @@ private static bool TrySkipBlockFinalize(Stream stream, Span<byte> blockBytes, i
         public static unsafe bool TrySkipBlock(Stream stream)
         {
             Span<byte> blockBytes = stackalloc byte[FieldLengths.Signature];
-            long currPosition = stream.Position;
             int bytesRead = stream.ReadAtLeast(blockBytes, blockBytes.Length, throwOnEndOfStream: false);
-            if (!TrySkipBlockCore(stream, blockBytes, bytesRead, currPosition))
+            if (!TrySkipBlockCore(stream, blockBytes, bytesRead))
             {
                 return false;
             }
             bytesRead = stream.ReadAtLeast(blockBytes, blockBytes.Length, throwOnEndOfStream: false);
             return TrySkipBlockFinalize(stream, blockBytes, bytesRead);
         }
+
+    }
+    internal struct WinZipAesExtraField
+    {
+        public const ushort HeaderId = 0x9901;
+        private const int DataSize = 7; // Vendor version (2) + Vendor ID (2) + AES strength (1) + Compression method (2)
+        private const byte VendorIdByte0 = (byte)'A';
+        private const byte VendorIdByte1 = (byte)'E';
+        private ushort _vendorVersion = 2;
+        private byte _aesStrength;
+        private ushort _compressionMethod;
+
+        public WinZipAesExtraField(ushort vendorVersion, byte aesStrength, ushort compressionMethod)
+        {
+            VendorVersion = vendorVersion;
+            AesStrength = aesStrength;
+            CompressionMethod = compressionMethod;
+        }
+
+        public ushort VendorVersion { get => _vendorVersion; set => _vendorVersion = value; }
+        public byte AesStrength { get => _aesStrength; set => _aesStrength = value; } // 1=128bit, 2=192bit, 3=256bit
+        public ushort CompressionMethod { get => _compressionMethod; set => _compressionMethod = value; } // Original compression method
+
+        public static int TotalSize => 11; // 2 (header) + 2 (size) + 7 (data)
+
+        /// <summary>
+        /// Tries to find and parse the WinZip AES extra field (0x9901) from a list of generic extra fields.
+        /// </summary>
+        /// <param name="extraFields">The list of extra fields to search.</param>
+        /// <param name="aesExtraField">When this method returns true, contains the parsed AES extra field.</param>
+        /// <returns>true if the AES extra field was found and parsed; otherwise, false.</returns>
+        public static bool TryGetFromExtraFields(List<ZipGenericExtraField>? extraFields, out WinZipAesExtraField aesExtraField)
+        {
+            aesExtraField = default;
+
+            if (extraFields == null)
+                return false;
+
+            foreach (ZipGenericExtraField field in extraFields)
+            {
+                if (field.Tag == HeaderId && field.Size >= DataSize &&
+                    TryParseData(field.Data.AsSpan(0, field.Size), out aesExtraField))
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Tries to find and parse the WinZip AES extra field (0x9901) from raw extra field data bytes.
+        /// This is used when ExtraFields are not saved (Read mode) but we still need to parse the AES field.
+        /// </summary>
+        /// <param name="extraFieldData">The raw extra field data bytes.</param>
+        /// <param name="aesExtraField">When this method returns true, contains the parsed AES extra field.</param>
+        /// <returns>true if the AES extra field was found and parsed; otherwise, false.</returns>
+        public static bool TryGetFromRawExtraFieldData(ReadOnlySpan<byte> extraFieldData, out WinZipAesExtraField aesExtraField)
+        {
+            aesExtraField = default;
+            int offset = 0;
+
+            while (offset + 4 <= extraFieldData.Length) // Need at least 4 bytes for header ID and size
+            {
+                ushort headerId = BinaryPrimitives.ReadUInt16LittleEndian(extraFieldData.Slice(offset, 2));
+                ushort fieldSize = BinaryPrimitives.ReadUInt16LittleEndian(extraFieldData.Slice(offset + 2, 2));
+
+                if (offset + 4 + fieldSize > extraFieldData.Length)
+                    break; // Not enough data for this field
+
+                if (headerId == HeaderId && fieldSize >= DataSize &&
+                    TryParseData(extraFieldData.Slice(offset + 4, fieldSize), out aesExtraField))
+                {
+                    return true;
+                }
+
+                offset += 4 + fieldSize;
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Parses and validates the data payload of a candidate WinZip AES extra field.
+        /// Validates the vendor ID ("AE"), vendor version (1 or 2), and AES strength (1–3).
+        /// </summary>
+        /// <param name="data">The raw field data bytes (must be at least <see cref="DataSize"/> bytes).</param>
+        /// <param name="aesExtraField">When this method returns true, contains the parsed AES extra field.</param>
+        /// <returns>true if the data represents a valid WinZip AES extra field; otherwise, false.</returns>
+        private static bool TryParseData(ReadOnlySpan<byte> data, out WinZipAesExtraField aesExtraField)
+        {
+            aesExtraField = default;
+
+            ushort vendorVersion = BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(0, 2));
+            byte aesStrength = data[4];
+
+            // Validate vendor ID must be "AE", vendor version must be 1 or 2,
+            // and AES strength must be 1 (128-bit), 2 (192-bit), or 3 (256-bit).
+            if (data[2] != VendorIdByte0 || data[3] != VendorIdByte1 ||
+                vendorVersion is < 1 or > 2 ||
+                aesStrength is < 1 or > 3)
+            {
+                return false;
+            }
+
+            aesExtraField = new WinZipAesExtraField(
+                vendorVersion: vendorVersion,
+                aesStrength: aesStrength,
+                compressionMethod: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(5, 2))
+            );
+            return true;
+        }
+
+        public unsafe void WriteBlock(Stream stream)
+        {
+            Span<byte> buffer = stackalloc byte[TotalSize];
+            WriteBlockCore(buffer);
+            stream.Write(buffer);
+        }
+
+        public async Task WriteBlockAsync(Stream stream, CancellationToken cancellationToken = default)
+        {
+            byte[] buffer = new byte[TotalSize];
+            WriteBlockCore(buffer);
+            await stream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
+        }
+
+        private void WriteBlockCore(Span<byte> buffer)
+        {
+            BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(0), HeaderId);
+            BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(2), DataSize);
+            BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(4), VendorVersion);
+            buffer[6] = (byte)'A';
+            buffer[7] = (byte)'E';
+            buffer[8] = AesStrength;
+            BinaryPrimitives.WriteUInt16LittleEndian(buffer[9..], CompressionMethod);
+        }
     }
 
+
     internal sealed partial class ZipCentralDirectoryFileHeader
     {
         // The Zip File Format Specification references 0x02014B50, this is a big endian representation.
@@ -705,6 +878,14 @@ internal sealed partial class ZipCentralDirectoryFileHeader
         public List<ZipGenericExtraField>? ExtraFields;
         public byte[]? TrailingExtraFieldData;
 
+        /// <summary>
+        /// The WinZip AES extra field (0x9901) if present in the central directory.
+        /// This is always parsed (regardless of saveExtraFieldsAndComments) so that
+        /// ZipArchiveEntry can determine the real compression method for AES-encrypted entries
+        /// without needing to read the local file header.
+        /// </summary>
+        public WinZipAesExtraField? AesExtraField;
+
         private static bool TryReadBlockInitialize(ReadOnlySpan<byte> buffer, [NotNullWhen(returnValue: true)] out ZipCentralDirectoryFileHeader? header, out int bytesRead, out uint compressedSizeSmall, out uint uncompressedSizeSmall, out ushort diskNumberStartSmall, out uint relativeOffsetOfLocalHeaderSmall)
         {
             // the buffer will always be large enough for at least the constant section to be verified
@@ -756,6 +937,14 @@ private static void TryReadBlockFinalize(ZipCentralDirectoryFileHeader header, R
 
             ReadOnlySpan<byte> zipExtraFields = dynamicHeader.Slice(header.FilenameLength, header.ExtraFieldLength);
 
+            // Always parse AES extra field (0x9901) from the central directory if present.
+            // This is needed so ZipArchiveEntry can determine the real compression method
+            // for AES-encrypted entries without requiring Open() to be called.
+            if (WinZipAesExtraField.TryGetFromRawExtraFieldData(zipExtraFields, out WinZipAesExtraField aesField))
+            {
+                header.AesExtraField = aesField;
+            }
+
             if (saveExtraFieldsAndComments)
             {
                 header.ExtraFields = ZipGenericExtraField.ParseExtraField(zipExtraFields, out ReadOnlySpan<byte> trailingDataSpan);
diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoKeys.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoKeys.cs
new file mode 100644
index 00000000000000..9de9abde90503c
--- /dev/null
+++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoKeys.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.IO.Compression
+{
+    internal readonly struct ZipCryptoKeys
+    {
+        internal readonly uint Key0;
+        internal readonly uint Key1;
+        internal readonly uint Key2;
+
+        internal ZipCryptoKeys(uint key0, uint key1, uint key2)
+        {
+            Key0 = key0;
+            Key1 = key1;
+            Key2 = key2;
+        }
+    }
+}
diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs
new file mode 100644
index 00000000000000..839a4a4ccb12c0
--- /dev/null
+++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs
@@ -0,0 +1,476 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers.Binary;
+using System.Security.Cryptography;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Diagnostics;
+using System.Text;
+
+namespace System.IO.Compression
+{
+    internal sealed class ZipCryptoStream : Stream
+    {
+        internal const int KeySize = 12; // 3 * sizeof(uint)
+
+        private const int EncryptionBufferSize = 4096;
+
+        private readonly bool _encrypting;
+        private readonly Stream _base;
+        private readonly bool _leaveOpen;
+        private bool _headerWritten;
+        private bool _disposed;
+        private readonly ushort _verifierLow2Bytes;       // (DOS time low word when streaming)
+        private readonly uint? _crc32ForHeader;           // (CRC-based header when not streaming)
+
+        private uint _key0;
+        private uint _key1;
+        private uint _key2;
+        private static readonly uint[] s_crc2Table = CreateCrc32Table();
+
+        // Reusable work buffer for write operations, lazily allocated on first write
+        private byte[]? _writeWorkBuffer;
+
+        private static uint[] CreateCrc32Table()
+        {
+            var table = new uint[256];
+            for (uint i = 0; i < 256; i++)
+            {
+                uint c = i;
+                for (int j = 0; j < 8; j++)
+                    c = (c & 1) != 0 ? (0xEDB88320u ^ (c >> 1)) : (c >> 1);
+                table[i] = c;
+            }
+            return table;
+        }
+
+        private static uint Crc32Update(uint crc, byte b) => s_crc2Table[(crc ^ b) & 0xFF] ^ (crc >> 8);
+
+        // Private decryption constructor - use Create/CreateAsync factory methods instead.
+        // Keys must already be validated before calling this constructor.
+        private ZipCryptoStream(Stream baseStream, uint key0, uint key1, uint key2, bool leaveOpen = false)
+        {
+            _base = baseStream;
+            _key0 = key0;
+            _key1 = key1;
+            _key2 = key2;
+            _encrypting = false;
+            _leaveOpen = leaveOpen;
+        }
+
+        /// <summary>
+        /// Creates a ZipCryptoStream for decryption. Reads and validates the 12-byte header synchronously.
+        /// </summary>
+        internal static ZipCryptoStream Create(Stream baseStream, ZipCryptoKeys keys, byte expectedCheckByte, bool encrypting, bool leaveOpen = false)
+        {
+            ArgumentNullException.ThrowIfNull(baseStream);
+            Debug.Assert(!encrypting, "Use the overload with passwordVerifierLow2Bytes for encryption.");
+
+            (uint key0, uint key1, uint key2) = ReadAndValidateHeaderCore(isAsync: false, baseStream, keys, expectedCheckByte, CancellationToken.None).GetAwaiter().GetResult();
+            return new ZipCryptoStream(baseStream, key0, key1, key2, leaveOpen);
+        }
+
+        /// <summary>
+        /// Creates a ZipCryptoStream for decryption. Reads and validates the 12-byte header asynchronously.
+        /// </summary>
+        internal static async Task<ZipCryptoStream> CreateAsync(Stream baseStream, ZipCryptoKeys keys, byte expectedCheckByte, bool encrypting, CancellationToken cancellationToken = default, bool leaveOpen = false)
+        {
+            ArgumentNullException.ThrowIfNull(baseStream);
+            Debug.Assert(!encrypting, "Use the overload with passwordVerifierLow2Bytes for encryption.");
+
+            (uint key0, uint key1, uint key2) = await ReadAndValidateHeaderCore(isAsync: true, baseStream, keys, expectedCheckByte, cancellationToken).ConfigureAwait(false);
+            return new ZipCryptoStream(baseStream, key0, key1, key2, leaveOpen);
+        }
+
+        /// <summary>
+        /// Creates a ZipCryptoStream for encryption. Only synchronous creation is needed since no I/O is performed here.
+        /// </summary>
+        internal static ZipCryptoStream Create(Stream baseStream,
+                                             ZipCryptoKeys keys,
+                                             ushort passwordVerifierLow2Bytes,
+                                             bool encrypting,
+                                             uint? crc32 = null,
+                                             bool leaveOpen = false)
+        {
+            ArgumentNullException.ThrowIfNull(baseStream);
+            Debug.Assert(encrypting, "Use the overload with expectedCheckByte for decryption.");
+
+            return new ZipCryptoStream(baseStream, keys, passwordVerifierLow2Bytes, crc32, leaveOpen);
+        }
+
+        // Encryption constructor
+        private ZipCryptoStream(Stream baseStream,
+                               ZipCryptoKeys keys,
+                               ushort passwordVerifierLow2Bytes,
+                               uint? crc32,
+                               bool leaveOpen)
+        {
+            _base = baseStream;
+            _encrypting = true;
+            _leaveOpen = leaveOpen;
+            _verifierLow2Bytes = passwordVerifierLow2Bytes;
+            _crc32ForHeader = crc32;
+            _key0 = keys.Key0;
+            _key1 = keys.Key1;
+            _key2 = keys.Key2;
+        }
+
+        // Creates the persisted key material from a password.
+        // Returns a struct of 3 integers to keep the key off the heap.
+        internal static unsafe ZipCryptoKeys CreateKey(ReadOnlySpan<char> password)
+        {
+            // Initialize keys with standard ZipCrypto initial values
+            uint key0 = 305419896;
+            uint key1 = 591751049;
+            uint key2 = 878082192;
+
+            // ASCII produces exactly 1 byte per char, so SegmentSize bytes is sufficient
+            // for SegmentSize chars.
+            const int SegmentSize = 32;
+            Span<byte> buf = stackalloc byte[SegmentSize];
+
+            ReadOnlySpan<char> pwSpan = password;
+
+            while (!pwSpan.IsEmpty)
+            {
+                ReadOnlySpan<char> segment = pwSpan;
+
+                if (segment.Length > SegmentSize)
+                {
+                    segment = segment.Slice(0, SegmentSize);
+                }
+
+                int byteCount = Encoding.ASCII.GetBytes(segment, buf);
+
+                foreach (byte b in buf.Slice(0, byteCount))
+                {
+                    UpdateKeys(ref key0, ref key1, ref key2, b);
+                }
+
+                pwSpan = pwSpan.Slice(segment.Length);
+            }
+
+            return new ZipCryptoKeys(key0, key1, key2);
+        }
+
+        private void CalculateHeader(Span<byte> header)
+        {
+            if (header.Length < 12)
+                throw new ArgumentException("Header must be at least 12 bytes.", nameof(header));
+
+            // bytes 0..9 random
+            RandomNumberGenerator.Fill(header.Slice(0, 10));
+
+            // bytes 10..11 verifier
+            if (_crc32ForHeader.HasValue)
+            {
+                uint crc = _crc32ForHeader.Value;
+                BinaryPrimitives.WriteUInt16LittleEndian(header.Slice(10), (ushort)(crc >> 16));
+            }
+            else
+            {
+                BinaryPrimitives.WriteUInt16LittleEndian(header.Slice(10), _verifierLow2Bytes);
+            }
+
+            // encrypt in place
+            for (int i = 0; i < 12; i++)
+            {
+                byte p = header[i];
+                byte ks = DecryptByte(_key2);
+                header[i] = (byte)(p ^ ks);
+
+                // keys updated with PLAINTEXT per ZIP spec
+                UpdateKeys(ref _key0, ref _key1, ref _key2, p);
+            }
+        }
+
+        private unsafe void WriteHeader()
+        {
+            if (!_encrypting || _headerWritten)
+                return;
+
+            Span<byte> header = stackalloc byte[12];
+            CalculateHeader(header);
+            _base.Write(header);
+            _headerWritten = true;
+        }
+
+        private async ValueTask WriteHeaderAsync(CancellationToken cancellationToken)
+        {
+            if (!_encrypting || _headerWritten)
+                return;
+
+            byte[] header = new byte[12];
+            CalculateHeader(header);
+            await _base.WriteAsync(header, cancellationToken).ConfigureAwait(false);
+            _headerWritten = true;
+        }
+
+        private void EnsureHeader()
+        {
+            WriteHeader();
+        }
+
+        private ValueTask EnsureHeaderAsync(CancellationToken cancellationToken)
+        {
+            return WriteHeaderAsync(cancellationToken);
+        }
+
+        private static async Task<(uint key0, uint key1, uint key2)> ReadAndValidateHeaderCore(bool isAsync, Stream baseStream, ZipCryptoKeys keys, byte expectedCheckByte, CancellationToken cancellationToken)
+        {
+            // Initialize keys from input
+            uint key0 = keys.Key0;
+            uint key1 = keys.Key1;
+            uint key2 = keys.Key2;
+
+            byte[] hdr = new byte[12];
+            int bytesRead;
+
+            if (isAsync)
+            {
+                bytesRead = await baseStream.ReadAtLeastAsync(hdr, hdr.Length, throwOnEndOfStream: false, cancellationToken).ConfigureAwait(false);
+            }
+            else
+            {
+                bytesRead = baseStream.ReadAtLeast(hdr, hdr.Length, throwOnEndOfStream: false);
+            }
+
+            if (bytesRead < hdr.Length)
+            {
+                throw new InvalidDataException(SR.TruncatedZipCryptoHeader);
+            }
+
+            // Decrypt header and update keys
+            for (int i = 0; i < hdr.Length; i++)
+            {
+                byte m = DecryptByte(key2);
+                byte plain = (byte)(hdr[i] ^ m);
+                UpdateKeys(ref key0, ref key1, ref key2, plain);
+                hdr[i] = plain;
+            }
+
+            if (hdr[11] != expectedCheckByte)
+            {
+                throw new InvalidDataException(SR.InvalidPassword);
+            }
+
+            return (key0, key1, key2);
+        }
+
+        private static void UpdateKeys(ref uint key0, ref uint key1, ref uint key2, byte b)
+        {
+            key0 = Crc32Update(key0, b);
+            key1 += (key0 & 0xFF);
+            key1 = key1 * 134775813 + 1;
+            key2 = Crc32Update(key2, (byte)(key1 >> 24));
+        }
+
+        private static byte DecryptByte(uint key2)
+        {
+            uint temp = key2 | 2;
+            return (byte)((temp * (temp ^ 1)) >> 8);
+        }
+
+        private byte DecryptAndUpdateKeys(byte ciph)
+        {
+            byte m = DecryptByte(_key2);
+            byte plain = (byte)(ciph ^ m);
+            UpdateKeys(ref _key0, ref _key1, ref _key2, plain);
+            return plain;
+        }
+
+        public override bool CanRead => !_disposed && !_encrypting;
+        public override bool CanSeek => false;
+        public override bool CanWrite => !_disposed && _encrypting;
+        public override long Length => throw new NotSupportedException();
+        public override long Position
+        {
+            get => throw new NotSupportedException();
+            set => throw new NotSupportedException();
+        }
+        public override void Flush()
+        {
+            ObjectDisposedException.ThrowIf(_disposed, this);
+            _base.Flush();
+        }
+
+        public override int Read(byte[] buffer, int offset, int count)
+        {
+            ValidateBufferArguments(buffer, offset, count);
+            return Read(buffer.AsSpan(offset, count));
+        }
+
+        public override int Read(Span<byte> destination)
+        {
+            ObjectDisposedException.ThrowIf(_disposed, this);
+            if (_encrypting)
+            {
+                throw new NotSupportedException(SR.ReadingNotSupported);
+            }
+            int n = _base.Read(destination);
+            for (int i = 0; i < n; i++)
+                destination[i] = DecryptAndUpdateKeys(destination[i]);
+            return n;
+
+        }
+
+        public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
+        public override void SetLength(long value) => throw new NotSupportedException();
+
+        public override void Write(byte[] buffer, int offset, int count)
+        {
+            ValidateBufferArguments(buffer, offset, count);
+            Write(buffer.AsSpan(offset, count));
+        }
+
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            ObjectDisposedException.ThrowIf(_disposed, this);
+            if (!_encrypting)
+            {
+                throw new NotSupportedException(SR.WritingNotSupported);
+            }
+
+            EnsureHeader();
+
+            byte[] workBuffer = GetWriteWorkBuffer();
+            ReadOnlySpan<byte> remaining = buffer;
+
+            while (!remaining.IsEmpty)
+            {
+                int chunkSize = Math.Min(remaining.Length, workBuffer.Length);
+
+                for (int i = 0; i < chunkSize; i++)
+                {
+                    byte ks = DecryptByte(_key2);
+                    byte p = remaining[i];
+                    workBuffer[i] = (byte)(p ^ ks);
+                    UpdateKeys(ref _key0, ref _key1, ref _key2, p);
+                }
+
+                _base.Write(workBuffer, 0, chunkSize);
+                remaining = remaining.Slice(chunkSize);
+            }
+        }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (_disposed)
+            {
+                return;
+            }
+            _disposed = true;
+
+            if (disposing)
+            {
+                // If encrypted empty entry (no payload written), still must emit 12-byte header:
+                if (_encrypting && !_headerWritten)
+                {
+                    EnsureHeader();
+                }
+
+                if (!_leaveOpen)
+                {
+                    _base.Dispose();
+                }
+            }
+            base.Dispose(disposing);
+        }
+
+        public override async ValueTask DisposeAsync()
+        {
+            if (_disposed)
+            {
+                return;
+            }
+            _disposed = true;
+
+            // If encrypted empty entry (no payload written), still must emit 12-byte header:
+            if (_encrypting && !_headerWritten)
+            {
+                await EnsureHeaderAsync(CancellationToken.None).ConfigureAwait(false);
+            }
+            if (!_leaveOpen)
+            {
+                await _base.DisposeAsync().ConfigureAwait(false);
+            }
+
+            GC.SuppressFinalize(this);
+
+            // Don't call base.DisposeAsync() as it would call Dispose() synchronously,
+            // which could fail on async-only streams. We've already handled all cleanup.
+        }
+
+        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            ValidateBufferArguments(buffer, offset, count);
+            return ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
+        }
+
+        public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+        {
+            ObjectDisposedException.ThrowIf(_disposed, this);
+            if (_encrypting)
+            {
+                throw new NotSupportedException(SR.ReadingNotSupported);
+            }
+
+            cancellationToken.ThrowIfCancellationRequested();
+            int n = await _base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
+            Span<byte> span = buffer.Span;
+
+            for (int i = 0; i < n; i++)
+                span[i] = DecryptAndUpdateKeys(span[i]);
+
+            return n;
+        }
+
+        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            ValidateBufferArguments(buffer, offset, count);
+            return WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
+        }
+
+        public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+        {
+            ObjectDisposedException.ThrowIf(_disposed, this);
+            if (!_encrypting)
+            {
+                throw new NotSupportedException(SR.WritingNotSupported);
+            }
+
+            cancellationToken.ThrowIfCancellationRequested();
+
+            await EnsureHeaderAsync(cancellationToken).ConfigureAwait(false);
+
+            byte[] workBuffer = GetWriteWorkBuffer();
+            int offset = 0;
+
+            while (offset < buffer.Length)
+            {
+                int chunkSize = Math.Min(buffer.Length - offset, workBuffer.Length);
+                ReadOnlySpan<byte> span = buffer.Span;
+
+                for (int i = 0; i < chunkSize; i++)
+                {
+                    byte ks = DecryptByte(_key2);
+                    byte p = span[offset + i];
+                    workBuffer[i] = (byte)(p ^ ks);
+                    UpdateKeys(ref _key0, ref _key1, ref _key2, p);
+                }
+
+                await _base.WriteAsync(workBuffer.AsMemory(0, chunkSize), cancellationToken).ConfigureAwait(false);
+                offset += chunkSize;
+            }
+        }
+
+        public override Task FlushAsync(CancellationToken cancellationToken)
+        {
+            ObjectDisposedException.ThrowIf(_disposed, this);
+            return _base.FlushAsync(cancellationToken);
+        }
+
+        private byte[] GetWriteWorkBuffer() => _writeWorkBuffer ??= new byte[EncryptionBufferSize];
+    }
+}
diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipEncryptionMethod.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipEncryptionMethod.cs
new file mode 100644
index 00000000000000..ca2f3d1eadd0c8
--- /dev/null
+++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipEncryptionMethod.cs
@@ -0,0 +1,42 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.IO.Compression
+{
+    /// <summary>
+    /// Specifies the encryption method used to encrypt an entry in a zip archive.
+    /// </summary>
+    public enum ZipEncryptionMethod
+    {
+        /// <summary>
+        /// An encryption method that is not supported by this implementation.
+        /// Attempting to open an entry with this encryption method will throw <see cref="NotSupportedException"/>.
+        /// </summary>
+        Unknown = -1,
+
+        /// <summary>
+        /// No Encryption is applied to the entry.
+        /// </summary>
+        None = 0,
+
+        /// <summary>
+        /// Legacy PKware encryption.
+        /// </summary>
+        ZipCrypto = 1,
+
+        /// <summary>
+        /// WinZip AES encryption with 128-bit key.
+        /// </summary>
+        Aes128 = 2,
+
+        /// <summary>
+        /// WinZip AES encryption with 192-bit key.
+        /// </summary>
+        Aes192 = 3,
+
+        /// <summary>
+        /// WinZip AES encryption with 256-bit key.
+        /// </summary>
+        Aes256 = 4
+    }
+}
diff --git a/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj b/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj
index b2d5aaf3524c76..e425efa75a8334 100644
--- a/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj
+++ b/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj
@@ -17,6 +17,9 @@
     <Compile Include="CompressionStreamUnitTests.ZLib.cs" />
     <Compile Include="CompressionStreamUnitTests.Deflate.cs" />
     <Compile Include="CompressionStreamUnitTests.Gzip.cs" />
+    <Compile Include="ZipCryptoStreamConformanceTests.cs" />
+    <Compile Include="WinZipAesStreamConformanceTests.cs" />
+    <Compile Include="ZipCryptoStreamWrappedConformanceTests.cs" />
     <Compile Include="Utilities\ClampedReadStream.cs" />
     <Compile Include="Utilities\StripHeaderAndFooter.cs" />
     <Compile Include="XunitAssemblyAttributes.cs" />
diff --git a/src/libraries/System.IO.Compression/tests/WinZipAesStreamConformanceTests.cs b/src/libraries/System.IO.Compression/tests/WinZipAesStreamConformanceTests.cs
new file mode 100644
index 00000000000000..9291abd1990cbc
--- /dev/null
+++ b/src/libraries/System.IO.Compression/tests/WinZipAesStreamConformanceTests.cs
@@ -0,0 +1,140 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.IO.Tests;
+using System.Reflection;
+using System.Runtime.Versioning;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.IO.Compression.Tests
+{
+    /// <summary>
+    /// Conformance tests for WinZipAesStream (AES-128).
+    /// </summary>
+    [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))]
+    [UnsupportedOSPlatform("browser")]
+    public sealed class WinZipAes128StreamConformanceTests : WinZipAesStreamConformanceTests
+    {
+        protected override int KeySizeBits => 128;
+    }
+
+    /// <summary>
+    /// Conformance tests for WinZipAesStream (AES-256).
+    /// </summary>
+    [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))]
+    [UnsupportedOSPlatform("browser")]
+    public sealed class WinZipAes256StreamConformanceTests : WinZipAesStreamConformanceTests
+    {
+        protected override int KeySizeBits => 256;
+    }
+
+    /// <summary>
+    /// Base class for WinZipAesStream conformance tests.
+    /// </summary>
+    public abstract class WinZipAesStreamConformanceTests : StandaloneStreamConformanceTests
+    {
+        private const string TestPassword = "test-password";
+
+        private delegate object CreateKeyDelegate(ReadOnlySpan<char> password, byte[]? salt, int keySizeBits);
+        private static readonly CreateKeyDelegate s_createKey;
+        private static readonly MethodInfo s_createMethod;
+
+        protected abstract int KeySizeBits { get; }
+        protected int SaltSize => KeySizeBits / 16;
+
+        static WinZipAesStreamConformanceTests()
+        {
+            Type winZipAesStreamType = Type.GetType("System.IO.Compression.WinZipAesStream, System.IO.Compression")!;
+            Type winZipAesKeyMaterialType = Type.GetType("System.IO.Compression.WinZipAesKeyMaterial, System.IO.Compression")!;
+
+            MethodInfo createKeyMethod = winZipAesKeyMaterialType.GetMethod("Create",
+                BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static,
+                null,
+                new[] { typeof(ReadOnlySpan<char>), typeof(byte[]), typeof(int) },
+                null)!;
+
+            // CreateDelegate can't handle value-type return covariance (struct → object boxing).
+            // Use DynamicMethod to emit a wrapper that calls the target and boxes the result.
+#pragma warning disable IL3050 // RequiresDynamicCode: DynamicMethod is not supported in AOT; these tests are skipped under NativeAOT.
+            var dm = new System.Reflection.Emit.DynamicMethod(
+                "CreateKeyWrapper",
+                typeof(object),
+                new[] { typeof(ReadOnlySpan<char>), typeof(byte[]), typeof(int) },
+                typeof(WinZipAesStreamConformanceTests).Module,
+                skipVisibility: true);
+            var il = dm.GetILGenerator();
+            il.Emit(System.Reflection.Emit.OpCodes.Ldarg_0);
+            il.Emit(System.Reflection.Emit.OpCodes.Ldarg_1);
+            il.Emit(System.Reflection.Emit.OpCodes.Ldarg_2);
+            il.Emit(System.Reflection.Emit.OpCodes.Call, createKeyMethod);
+            il.Emit(System.Reflection.Emit.OpCodes.Box, winZipAesKeyMaterialType);
+            il.Emit(System.Reflection.Emit.OpCodes.Ret);
+            s_createKey = dm.CreateDelegate<CreateKeyDelegate>();
+#pragma warning restore IL3050
+
+            s_createMethod = winZipAesStreamType.GetMethod("Create",
+                BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static,
+                null,
+                new[] { typeof(Stream), winZipAesKeyMaterialType, typeof(long), typeof(bool), typeof(bool) },
+                null)!;
+        }
+
+        protected override bool CanSeek => false;
+
+        protected override Task<Stream?> CreateReadWriteStreamCore(byte[]? initialData) =>
+            Task.FromResult<Stream?>(null); // WinZipAesStream is either read-only or write-only
+
+        protected override Task<Stream?> CreateWriteOnlyStreamCore(byte[]? initialData)
+        {
+            var ms = new MemoryStream();
+            object keyMaterial = s_createKey(TestPassword, null, KeySizeBits);
+
+            var encryptStream = (Stream)s_createMethod.Invoke(null, new object[]
+            {
+                ms, keyMaterial, -1L, true, false
+            })!;
+
+            if (initialData != null && initialData.Length > 0)
+            {
+                encryptStream.Write(initialData);
+            }
+
+            return Task.FromResult<Stream?>(encryptStream);
+        }
+
+        protected override Task<Stream?> CreateReadOnlyStreamCore(byte[]? initialData)
+        {
+            byte[] plaintext = initialData ?? Array.Empty<byte>();
+
+            // Create key material for encryption (generates random salt)
+            object encryptKeyMaterial = s_createKey(TestPassword, null, KeySizeBits);
+
+            // Encrypt data first
+            using var encryptedMs = new MemoryStream();
+            using (var encryptStream = (Stream)s_createMethod.Invoke(null, new object[]
+            {
+                encryptedMs, encryptKeyMaterial, -1L, true, true
+            })!)
+            {
+                encryptStream.Write(plaintext);
+            }
+
+            // Extract salt from encrypted data to create matching key material for decryption
+            byte[] encryptedData = encryptedMs.ToArray();
+            byte[] salt = new byte[SaltSize];
+            Array.Copy(encryptedData, 0, salt, 0, SaltSize);
+
+            object decryptKeyMaterial = s_createKey(TestPassword, salt, KeySizeBits);
+
+            // Create decryption stream over the encrypted data
+            var ms = new MemoryStream(encryptedData);
+            var decryptStream = (Stream)s_createMethod.Invoke(null, new object[]
+            {
+                ms, decryptKeyMaterial, (long)encryptedData.Length, false, false
+            })!;
+
+            return Task.FromResult<Stream?>(decryptStream);
+        }
+    }
+}
diff --git a/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs
index c2f856dd88bf96..e08515f3dea86a 100644
--- a/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs
+++ b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs
@@ -24,10 +24,10 @@ public NonSeekableStream(Stream baseStream)
         public override bool CanSeek => false; // Force non-seekable
         public override bool CanWrite => _baseStream.CanWrite;
         public override long Length => _baseStream.Length;
-        public override long Position 
-        { 
-            get => _baseStream.Position; 
-            set => throw new NotSupportedException("Seeking is not supported"); 
+        public override long Position
+        {
+            get => _baseStream.Position;
+            set => throw new NotSupportedException("Seeking is not supported");
         }
 
         public override void Flush() => _baseStream.Flush();
@@ -312,14 +312,14 @@ public static async Task ReadModeInvalidOpsTest(bool async)
             Stream s = await OpenEntryStream(async, e);
             Assert.Throws<NotSupportedException>(() => s.Flush()); //"Should not be able to flush on read stream"
             Assert.Throws<NotSupportedException>(() => s.WriteByte(25)); //"should not be able to write to read stream"
-            
+
             // Seeking behavior depends on whether the entry is compressed and the underlying stream is seekable
             if (!s.CanSeek)
             {
                 Assert.Throws<NotSupportedException>(() => s.Position = 4); //"should not be able to seek on non-seekable read stream"
                 Assert.Throws<NotSupportedException>(() => s.Seek(0, SeekOrigin.Begin)); //"should not be able to seek on non-seekable read stream"
             }
-            
+
             Assert.Throws<NotSupportedException>(() => s.SetLength(0)); //"should not be able to resize read stream"
 
             await DisposeZipArchive(async, archive);
@@ -591,12 +591,12 @@ public static async Task ReadStreamOps(bool async)
 
                 Assert.True(s.CanRead, "Can read to read archive");
                 Assert.False(s.CanWrite, "Can't write to read archive");
-                
+
                 // Check the entry's compression method to determine seekability
                 // SubReadStream should be seekable when the underlying stream is seekable and the entry is stored (uncompressed)
                 // If the entry is compressed (Deflate, Deflate64, etc.), it will be wrapped in a compression stream which is not seekable
                 ZipCompressionMethod compressionMethod = (ZipCompressionMethod)compressionMethodField.GetValue(e);
-                
+
                 if (compressionMethod == ZipCompressionMethod.Stored)
                 {
                     // Entry is stored (uncompressed), should be seekable
@@ -607,7 +607,7 @@ public static async Task ReadStreamOps(bool async)
                     // Entry is compressed (Deflate, Deflate64, etc.), wrapped in compression stream, should not be seekable
                     Assert.False(s.CanSeek, $"Entry '{e.FullName}' with compression method {compressionMethod} should not be seekable because compressed entries are wrapped in non-seekable compression streams");
                 }
-                
+
                 Assert.Equal(await LengthOfUnseekableStream(s), e.Length); //"Length is not correct on stream"
 
                 await DisposeStream(async, s);
@@ -672,7 +672,7 @@ public static async Task ReadStreamSeekOps(bool async)
                         // Test that seeking before beginning throws, but beyond end is allowed
                         Assert.Throws<ArgumentOutOfRangeException>(() => s.Position = -1);
                         Assert.Throws<IOException>(() => s.Seek(-1, SeekOrigin.Begin));
-                        
+
                         // Seeking beyond end should be allowed (no exception)
                         s.Position = e.Length + 1;
                         Assert.Equal(e.Length + 1, s.Position);
@@ -693,7 +693,7 @@ public static async Task ReadEntryContentTwice(bool async, bool useSeekMethod)
             using (var ms = new MemoryStream())
             {
                 var testData = "This is test data for reading content twice with seeking operations."u8.ToArray();
-                
+
                 // Create a ZIP with stored entries
                 using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, true))
                 {
@@ -919,5 +919,170 @@ public static async Task ReadAfterSeekingPastEnd_ReturnsZeroBytes(bool async)
 
             await DisposeStream(async, readStream);
         }
+
+        [Theory]
+        [InlineData(false)]
+        [InlineData(true)]
+        public static async Task StrongEncryptionDetectedAsUnknown(bool async)
+        {
+            var ms = new MemoryStream();
+            ZipArchive createArchive = await CreateZipArchive(async, ms, ZipArchiveMode.Create, leaveOpen: true);
+            ZipArchiveEntry newEntry = createArchive.CreateEntry("test.txt");
+            using (Stream entryStream = async ? await newEntry.OpenAsync() : newEntry.Open())
+            {
+                byte[] data = "hello"u8.ToArray();
+                if (async)
+                    await entryStream.WriteAsync(data);
+                else
+                    entryStream.Write(data, 0, data.Length);
+            }
+            await DisposeZipArchive(async, createArchive);
+
+            byte[] zipBytes = ms.ToArray();
+
+            // Set bit 0 (encrypted) and bit 6 (strong encryption) in both LH and CD general purpose bit flags
+            const ushort strongEncryptionFlags = 0x01 | 0x40;
+
+            // Local file header: signature at 0, version at 4, bit flags at offset 6
+            int lhBitFlagOffset = 6;
+            BinaryPrimitives.WriteUInt16LittleEndian(zipBytes.AsSpan(lhBitFlagOffset), strongEncryptionFlags);
+
+            // Find central directory (from EOCD at end of file)
+            // EOCD signature is 0x06054b50, CD offset is at EOCD + 16
+            int eocdOffset = zipBytes.Length - 22; // minimal EOCD is 22 bytes with no comment
+            int cdOffset = BinaryPrimitives.ReadInt32LittleEndian(zipBytes.AsSpan(eocdOffset + 16));
+            // CD header: signature at 0, version-made-by at 4 (2 bytes), version-needed at 6 (2 bytes), bit flags at offset 8
+            int cdBitFlagOffset = cdOffset + 8;
+            BinaryPrimitives.WriteUInt16LittleEndian(zipBytes.AsSpan(cdBitFlagOffset), strongEncryptionFlags);
+
+            using var archiveStream = new MemoryStream(zipBytes);
+            ZipArchive archive = await CreateZipArchive(async, archiveStream, ZipArchiveMode.Read);
+            ZipArchiveEntry entry = archive.Entries[0];
+
+            Assert.True(entry.IsEncrypted);
+            Assert.Equal(ZipEncryptionMethod.Unknown, entry.EncryptionMethod);
+
+            Assert.Throws<NotSupportedException>(() => entry.Open("password".AsSpan()));
+
+            await DisposeZipArchive(async, archive);
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        public async Task DecryptEntries_SamePassword_7Zip(bool async)
+        {
+            string password = "S3cur3P@ssw0rd";
+            using Stream archiveStream = await StreamHelpers.CreateTempCopyStream(passwordProtected("PasswordProtected_7ZIP_SamePassword.zip"));
+            ZipArchive archive = await CreateZipArchive(async, archiveStream, ZipArchiveMode.Read);
+
+            Assert.Equal(2, archive.Entries.Count);
+
+            foreach (ZipArchiveEntry entry in archive.Entries)
+            {
+                Assert.True(entry.IsEncrypted);
+
+                using Stream entryStream = await OpenEntryStream(async, entry, password);
+                using StreamReader reader = new(entryStream);
+                string content = reader.ReadToEnd().TrimEnd();
+
+                if (entry.Name == "hello.txt")
+                {
+                    Assert.Equal("Hello", content);
+                }
+                else if (entry.Name == "goodbye.txt")
+                {
+                    Assert.Equal("Goodbye", content);
+                }
+                else
+                {
+                    Assert.Fail($"Unexpected entry: {entry.Name}");
+                }
+            }
+            await DisposeZipArchive(async, archive);
+        }
+
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        public async Task DecryptEntries_MixedEncryptions(bool async)
+        {
+            string password = "S3cur3P@ssw0rd";
+            using Stream archiveStream = await StreamHelpers.CreateTempCopyStream(passwordProtected("PasswordProtected_MixedEncryptions.zip"));
+            ZipArchive archive = await CreateZipArchive(async, archiveStream, ZipArchiveMode.Read);
+
+            Assert.Equal(2, archive.Entries.Count);
+
+            ZipArchiveEntry helloEntry = archive.GetEntry("hello.txt");
+            Assert.NotNull(helloEntry);
+            Assert.True(helloEntry.IsEncrypted);
+            Assert.Equal(ZipEncryptionMethod.ZipCrypto, helloEntry.EncryptionMethod);
+
+            using (Stream helloStream = await OpenEntryStream(async, helloEntry, password))
+            using (StreamReader helloReader = new(helloStream))
+            {
+                Assert.Equal("Hello", helloReader.ReadToEnd().TrimEnd());
+            }
+
+            ZipArchiveEntry goodbyeEntry = archive.GetEntry("goodbye.txt");
+            Assert.NotNull(goodbyeEntry);
+            Assert.True(goodbyeEntry.IsEncrypted);
+            Assert.Equal(ZipEncryptionMethod.Aes256, goodbyeEntry.EncryptionMethod);
+
+            using (Stream goodbyeStream = await OpenEntryStream(async, goodbyeEntry, password))
+            using (StreamReader goodbyeReader = new(goodbyeStream))
+            {
+                Assert.Equal("Goodbye", goodbyeReader.ReadToEnd().TrimEnd());
+            }
+
+            await DisposeZipArchive(async, archive);
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        public async Task DecryptEntries_DifferentPasswords(bool async)
+        {
+            using Stream archiveStream = await StreamHelpers.CreateTempCopyStream(passwordProtected("PasswordProtected_DifferentPasswords.zip"));
+            ZipArchive archive = await CreateZipArchive(async, archiveStream, ZipArchiveMode.Read);
+
+            Assert.Equal(2, archive.Entries.Count);
+
+            ZipArchiveEntry helloEntry = archive.GetEntry("hello.txt");
+            Assert.NotNull(helloEntry);
+            Assert.True(helloEntry.IsEncrypted);
+            Assert.Equal(ZipEncryptionMethod.Aes256, helloEntry.EncryptionMethod);
+
+            using (Stream helloStream = await OpenEntryStream(async, helloEntry, "S3cur3P@ssw0rd2"))
+            using (StreamReader helloReader = new(helloStream))
+            {
+                Assert.Equal("Hello", helloReader.ReadToEnd().TrimEnd());
+            }
+
+            ZipArchiveEntry goodbyeEntry = archive.GetEntry("goodbye.txt");
+            Assert.NotNull(goodbyeEntry);
+            Assert.True(goodbyeEntry.IsEncrypted);
+            Assert.Equal(ZipEncryptionMethod.Aes256, goodbyeEntry.EncryptionMethod);
+
+            using (Stream goodbyeStream = await OpenEntryStream(async, goodbyeEntry, "S3cur3P@ssw0rd1"))
+            using (StreamReader goodbyeReader = new(goodbyeStream))
+            {
+                Assert.Equal("Goodbye", goodbyeReader.ReadToEnd().TrimEnd());
+            }
+
+            await DisposeZipArchive(async, archive);
+        }
+
+        [Theory]
+        [MemberData(nameof(Get_Booleans_Data))]
+        public async Task PasswordProtectedZip64_UpdateMode_Throws(bool async)
+        {
+            using Stream archiveStream = await StreamHelpers.CreateTempCopyStream(passwordProtected("PasswordProtectedZIP64.zip"));
+
+            await Assert.ThrowsAsync<InvalidDataException>(async () =>
+            {
+                ZipArchive archive = await CreateZipArchive(async, archiveStream, ZipArchiveMode.Update);
+                await DisposeZipArchive(async, archive);
+            });
+        }
     }
 }
+
diff --git a/src/libraries/System.IO.Compression/tests/ZipCryptoStreamConformanceTests.cs b/src/libraries/System.IO.Compression/tests/ZipCryptoStreamConformanceTests.cs
new file mode 100644
index 00000000000000..b3405643f6c5a1
--- /dev/null
+++ b/src/libraries/System.IO.Compression/tests/ZipCryptoStreamConformanceTests.cs
@@ -0,0 +1,119 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.IO.Tests;
+using System.Reflection;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.IO.Compression.Tests
+{
+    /// <summary>
+    /// Conformance tests for ZipCryptoStream.
+    /// </summary>
+    [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))]
+    public class ZipCryptoStreamConformanceTests : StandaloneStreamConformanceTests
+    {
+        private const string TestPassword = "test-password";
+        private const ushort PasswordVerifier = 0x1234;
+
+        private delegate object CreateKeyDelegate(ReadOnlySpan<char> password);
+        private static readonly CreateKeyDelegate s_createKey;
+        private static readonly MethodInfo s_createEncryptionMethod;
+        private static readonly MethodInfo s_createDecryptionMethod;
+
+        static ZipCryptoStreamConformanceTests()
+        {
+            Type zipCryptoStreamType = Type.GetType("System.IO.Compression.ZipCryptoStream, System.IO.Compression")!;
+            Type zipCryptoKeysType = Type.GetType("System.IO.Compression.ZipCryptoKeys, System.IO.Compression")!;
+
+            MethodInfo createKeyMethod = zipCryptoStreamType.GetMethod("CreateKey",
+                BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static,
+                null,
+                new[] { typeof(ReadOnlySpan<char>) },
+                null)!;
+
+            // Use DynamicMethod to box the struct return value.
+#pragma warning disable IL3050 // RequiresDynamicCode: DynamicMethod is not supported in AOT; these tests are skipped under NativeAOT.
+            var dm = new System.Reflection.Emit.DynamicMethod(
+                "CreateKeyWrapper",
+                typeof(object),
+                new[] { typeof(ReadOnlySpan<char>) },
+                typeof(ZipCryptoStreamConformanceTests).Module,
+                skipVisibility: true);
+            var il = dm.GetILGenerator();
+            il.Emit(System.Reflection.Emit.OpCodes.Ldarg_0);
+            il.Emit(System.Reflection.Emit.OpCodes.Call, createKeyMethod);
+            il.Emit(System.Reflection.Emit.OpCodes.Box, zipCryptoKeysType);
+            il.Emit(System.Reflection.Emit.OpCodes.Ret);
+            s_createKey = dm.CreateDelegate<CreateKeyDelegate>();
+#pragma warning restore IL3050
+
+            s_createEncryptionMethod = zipCryptoStreamType.GetMethod("Create",
+                BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static,
+                null,
+                new[] { typeof(Stream), zipCryptoKeysType, typeof(ushort), typeof(bool), typeof(uint?), typeof(bool) },
+                null)!;
+
+            s_createDecryptionMethod = zipCryptoStreamType.GetMethod("Create",
+                BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static,
+                null,
+                new[] { typeof(Stream), zipCryptoKeysType, typeof(byte), typeof(bool), typeof(bool) },
+                null)!;
+
+        }
+
+        protected override bool CanSeek => false;
+
+        protected override Task<Stream?> CreateReadWriteStreamCore(byte[]? initialData) =>
+            Task.FromResult<Stream?>(null); // ZipCryptoStream is either read-only or write-only
+
+        protected override Task<Stream?> CreateWriteOnlyStreamCore(byte[]? initialData)
+        {
+            var ms = new MemoryStream();
+            object keys = s_createKey(TestPassword);
+
+            var encryptStream = (Stream)s_createEncryptionMethod.Invoke(null, new object?[]
+            {
+                ms, keys, PasswordVerifier, true /* encrypting */, null /* crc32 */, false /* leaveOpen */
+            })!;
+
+            if (initialData != null && initialData.Length > 0)
+            {
+                encryptStream.Write(initialData);
+            }
+
+            return Task.FromResult<Stream?>(encryptStream);
+        }
+
+        protected override Task<Stream?> CreateReadOnlyStreamCore(byte[]? initialData)
+        {
+            byte[] plaintext = initialData ?? Array.Empty<byte>();
+            object keys = s_createKey(TestPassword);
+
+            // The check byte is the HIGH byte of the password verifier (little-endian format)
+            byte expectedCheckByte = (byte)(PasswordVerifier >> 8);
+
+            // Encrypt data first
+            using var encryptedMs = new MemoryStream();
+            using (var encryptStream = (Stream)s_createEncryptionMethod.Invoke(null, new object?[]
+            {
+                encryptedMs, keys, PasswordVerifier, true /* encrypting */, null /* crc32 */, true /* leaveOpen */
+            })!)
+            {
+                encryptStream.Write(plaintext);
+            }
+
+            byte[] encryptedData = encryptedMs.ToArray();
+
+            // Create decryption stream over the encrypted data
+            var ms = new MemoryStream(encryptedData);
+            var decryptStream = (Stream)s_createDecryptionMethod.Invoke(null, new object[]
+            {
+                ms, keys, expectedCheckByte, false /* encrypting */, false /* leaveOpen */
+            })!;
+
+            return Task.FromResult<Stream?>(decryptStream);
+        }
+    }
+}
diff --git a/src/libraries/System.IO.Compression/tests/ZipCryptoStreamWrappedConformanceTests.cs b/src/libraries/System.IO.Compression/tests/ZipCryptoStreamWrappedConformanceTests.cs
new file mode 100644
index 00000000000000..95beee145ec014
--- /dev/null
+++ b/src/libraries/System.IO.Compression/tests/ZipCryptoStreamWrappedConformanceTests.cs
@@ -0,0 +1,140 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.IO.Tests;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.IO.Compression.Tests
+{
+    /// <summary>
+    /// Wrapped connected stream conformance tests for ZipCryptoStream.
+    /// Tests encryption → decryption data flow through connected streams.
+    /// </summary>
+    [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))]
+    public class ZipCryptoStreamWrappedConformanceTests : WrappingConnectedStreamConformanceTests
+    {
+        private const string TestPassword = "test-password";
+        private const ushort PasswordVerifier = 0x1234;
+
+        private delegate object CreateKeyDelegate(ReadOnlySpan<char> password);
+        private static readonly CreateKeyDelegate s_createKey;
+        private static readonly MethodInfo s_createEncryptionMethod;
+        private static readonly MethodInfo s_createDecryptionMethod;
+        private static readonly MethodInfo s_createAsyncMethod;
+
+        static ZipCryptoStreamWrappedConformanceTests()
+        {
+            Type zipCryptoStreamType = Type.GetType("System.IO.Compression.ZipCryptoStream, System.IO.Compression")!;
+            Type zipCryptoKeysType = Type.GetType("System.IO.Compression.ZipCryptoKeys, System.IO.Compression")!;
+
+            MethodInfo createKeyMethod = zipCryptoStreamType.GetMethod("CreateKey",
+                BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static,
+                null,
+                new[] { typeof(ReadOnlySpan<char>) },
+                null)!;
+
+            // Use DynamicMethod to box the struct return value.
+#pragma warning disable IL3050 // RequiresDynamicCode: DynamicMethod is not supported in AOT; these tests are skipped under NativeAOT.
+            var dm = new System.Reflection.Emit.DynamicMethod(
+                "CreateKeyWrapper",
+                typeof(object),
+                new[] { typeof(ReadOnlySpan<char>) },
+                typeof(ZipCryptoStreamWrappedConformanceTests).Module,
+                skipVisibility: true);
+            var il = dm.GetILGenerator();
+            il.Emit(System.Reflection.Emit.OpCodes.Ldarg_0);
+            il.Emit(System.Reflection.Emit.OpCodes.Call, createKeyMethod);
+            il.Emit(System.Reflection.Emit.OpCodes.Box, zipCryptoKeysType);
+            il.Emit(System.Reflection.Emit.OpCodes.Ret);
+            s_createKey = dm.CreateDelegate<CreateKeyDelegate>();
+#pragma warning restore IL3050
+
+            s_createEncryptionMethod = zipCryptoStreamType.GetMethod("Create",
+                BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static,
+                null,
+                new[] { typeof(Stream), zipCryptoKeysType, typeof(ushort), typeof(bool), typeof(uint?), typeof(bool) },
+                null)!;
+
+            s_createDecryptionMethod = zipCryptoStreamType.GetMethod("Create",
+                BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static,
+                null,
+                new[] { typeof(Stream), zipCryptoKeysType, typeof(byte), typeof(bool), typeof(bool) },
+                null)!;
+
+            s_createAsyncMethod = zipCryptoStreamType.GetMethod("CreateAsync",
+               BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static,
+               null,
+               new[] { typeof(Stream), zipCryptoKeysType, typeof(byte), typeof(bool), typeof(CancellationToken), typeof(bool) },
+               null);
+        }
+
+        // ZipCryptoStream doesn't support seeking
+        protected override bool CanSeek => false;
+
+        protected override bool FlushGuaranteesAllDataWritten => true;
+
+        // ZipCrypto uses streaming cipher - blocks on zero byte reads
+        protected override bool BlocksOnZeroByteReads => true;
+
+        // No concurrent exception type - single-threaded stream
+        protected override Type UnsupportedConcurrentExceptionType => null!;
+
+        protected override Task<StreamPair> CreateConnectedStreamsAsync()
+        {
+            // Create bidirectional connected streams with sufficient buffer for header
+            (Stream stream1, Stream stream2) = ConnectedStreams.CreateBidirectional(4 * 1024, 16 * 1024);
+            return CreateWrappedConnectedStreamsAsync((stream1, stream2), leaveOpen: false);
+        }
+
+        protected override async Task<StreamPair> CreateWrappedConnectedStreamsAsync(StreamPair wrapped, bool leaveOpen = false)
+        {
+            object keys = s_createKey(TestPassword);
+
+            // The check byte is the HIGH byte of the password verifier (little-endian format)
+            byte expectedCheckByte = (byte)(PasswordVerifier >> 8); // 0x12
+
+            // Create the encryption stream (write-only) - wraps stream1
+            var encryptStream = (Stream)s_createEncryptionMethod.Invoke(null, new object?[]
+            {
+                wrapped.Stream1, keys, PasswordVerifier, true /* encrypting */, null /* crc32 */, leaveOpen
+            })!;
+
+            // Write and flush the header so the decryption stream can read it
+            // ZipCrypto header is written lazily on first write, so we need to trigger it
+            // Use async operations to support AsyncOnlyStream wrappers used by conformance tests
+            await encryptStream.WriteAsync(new byte[] { 0 }, 0, 1).ConfigureAwait(false); // Trigger header write
+            await encryptStream.FlushAsync().ConfigureAwait(false);
+
+            // Now create the decryption stream (read-only) - wraps stream2
+            // This will read and validate the 12-byte header
+            // Use async factory method to support AsyncOnlyStream wrappers
+            var decryptStream = await CreateDecryptStreamAsync(wrapped.Stream2, keys, expectedCheckByte, leaveOpen).ConfigureAwait(false);
+
+            // Read the byte we wrote to trigger the header
+            byte[] readBuffer = new byte[1];
+            await decryptStream.ReadAsync(readBuffer, 0, 1).ConfigureAwait(false);
+
+            return (encryptStream, decryptStream);
+        }
+
+        private static async Task<Stream> CreateDecryptStreamAsync(Stream baseStream, object keys, byte expectedCheckByte, bool leaveOpen)
+        {
+            // CreateAsync returns Task<ZipCryptoStream>, await it and get the result
+            var task = (Task)s_createAsyncMethod.Invoke(null, new object[]
+            {
+                    baseStream, keys, expectedCheckByte, false /* encrypting */, CancellationToken.None, leaveOpen
+            })!;
+            await task.ConfigureAwait(false);
+
+            // Get the Result property from the completed task
+#pragma warning disable IL2075 // CreateAsync returns Task<ZipCryptoStream>; this test is skipped under NativeAOT.
+            var resultProperty = task.GetType().GetProperty(nameof(Task<object>.Result))!;
+#pragma warning restore IL2075
+            return (Stream)resultProperty.GetValue(task)!;
+
+        }
+    }
+}
diff --git a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj
index 344b9f60f008ed..da1a5c444712c6 100644
--- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj
+++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj
@@ -2069,7 +2069,6 @@
     <ProjectReference Include="$(LibrariesProjectRoot)System.Collections.Concurrent\src\System.Collections.Concurrent.csproj" />
     <ProjectReference Include="$(LibrariesProjectRoot)System.Collections.NonGeneric\src\System.Collections.NonGeneric.csproj" />
     <ProjectReference Include="$(LibrariesProjectRoot)System.Diagnostics.Tracing\src\System.Diagnostics.Tracing.csproj" />
-    <ProjectReference Include="$(LibrariesProjectRoot)System.Diagnostics.StackTrace\src\System.Diagnostics.StackTrace.csproj" />
     <ProjectReference Include="$(LibrariesProjectRoot)System.Formats.Asn1\src\System.Formats.Asn1.csproj" />
     <ProjectReference Include="$(LibrariesProjectRoot)System.IO.MemoryMappedFiles\src\System.IO.MemoryMappedFiles.csproj" />
     <ProjectReference Include="$(LibrariesProjectRoot)System.Memory\src\System.Memory.csproj" />
