diff --git a/src/libraries/System.IO.Compression/src/Resources/Strings.resx b/src/libraries/System.IO.Compression/src/Resources/Strings.resx
index 8f70a3e2dea3d2..d2fb9df005cfba 100644
--- a/src/libraries/System.IO.Compression/src/Resources/Strings.resx
+++ b/src/libraries/System.IO.Compression/src/Resources/Strings.resx
@@ -426,6 +426,10 @@
WinZip AES encryption is not supported on the browser platform.
+
+ Unable to generate cryptographically secure random bytes.
+
+
The entry's encryption method is not supported.
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 c71e8f1e4b181f..9af63969bfaef5 100644
--- a/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj
+++ b/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj
@@ -66,8 +66,12 @@
-
-
+
+
+
+
+
+
@@ -111,7 +115,7 @@
-
+
diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/Interop.GetCryptographicallySecureRandomBytes.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/Interop.GetCryptographicallySecureRandomBytes.cs
new file mode 100644
index 00000000000000..bc93ee82bac327
--- /dev/null
+++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/Interop.GetCryptographicallySecureRandomBytes.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetCryptographicallySecureRandomBytes")]
+ internal static unsafe partial int GetCryptographicallySecureRandomBytes(byte* buffer, int length);
+ }
+}
diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAes.PlatformNotSupported.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAes.PlatformNotSupported.cs
new file mode 100644
index 00000000000000..75c9de825295e3
--- /dev/null
+++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAes.PlatformNotSupported.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.IO.Compression
+{
+ internal readonly struct WinZipAesKeyMaterial;
+
+ internal static class WinZipAesStream
+ {
+ internal static int GetSaltSize(int keySizeBits) => throw CreateException();
+
+ internal static WinZipAesKeyMaterial CreateKey(ReadOnlySpan password, byte[]? salt, int keySizeBits) => throw CreateException();
+
+ internal static Stream Create(Stream baseStream, WinZipAesKeyMaterial keyMaterial, long totalStreamSize, bool encrypting, bool leaveOpen = false) => throw CreateException();
+
+ internal static Task CreateAsync(Stream baseStream, WinZipAesKeyMaterial keyMaterial, long totalStreamSize, bool encrypting, bool leaveOpen = false, CancellationToken cancellationToken = default) => throw CreateException();
+
+ private static PlatformNotSupportedException CreateException() => new(SR.WinZipEncryptionNotSupportedOnBrowser);
+ }
+}
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 6a74dc6f308525..274c09cd07e9c2 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
@@ -728,7 +728,7 @@ private async Task WriteLocalFileHeaderAndDataIfNeededAsync(bool forceWrite, Can
ushort verifierLow2Bytes = (ushort)ZipHelper.DateTimeToDosTime(_lastModified.DateTime);
- ZipCryptoStream encryptionStream = ZipCryptoStream.Create(
+ Stream encryptionStream = ZipCryptoStream.Create(
baseStream: _archive.ArchiveStream,
keys: _derivedZipCryptoKeyMaterial.Value,
passwordVerifierLow2Bytes: verifierLow2Bytes,
@@ -782,7 +782,7 @@ private async Task WriteLocalFileHeaderAndDataIfNeededAsync(bool forceWrite, Can
// The AES extra field stores the real compression method
bool useDeflate = _compressionLevel != CompressionLevel.NoCompression;
- WinZipAesStream encryptionStream = WinZipAesStream.Create(
+ Stream encryptionStream = WinZipAesStream.Create(
baseStream: _archive.ArchiveStream,
keyMaterial: _derivedAesKeyMaterial.Value,
totalStreamSize: -1,
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 f6f0dfc2af3106..76c1f2c4692df3 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
@@ -1776,7 +1776,7 @@ private unsafe void WriteLocalFileHeaderAndDataIfNeeded(bool forceWrite)
ushort verifierLow2Bytes = (ushort)ZipHelper.DateTimeToDosTime(_lastModified.DateTime);
- using (ZipCryptoStream encryptionStream = ZipCryptoStream.Create(
+ using (Stream encryptionStream = ZipCryptoStream.Create(
baseStream: _archive.ArchiveStream,
keys: _derivedZipCryptoKeyMaterial.Value,
passwordVerifierLow2Bytes: verifierLow2Bytes,
@@ -1807,7 +1807,7 @@ private unsafe void WriteLocalFileHeaderAndDataIfNeeded(bool forceWrite)
bool useDeflate = _compressionLevel != CompressionLevel.NoCompression;
- using (WinZipAesStream encryptionStream = WinZipAesStream.Create(
+ using (Stream encryptionStream = WinZipAesStream.Create(
baseStream: _archive.ArchiveStream,
keyMaterial: _derivedAesKeyMaterial.Value,
totalStreamSize: -1,
diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.BrowserOrWasi.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.BrowserOrWasi.cs
new file mode 100644
index 00000000000000..68df99653c4d70
--- /dev/null
+++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.BrowserOrWasi.cs
@@ -0,0 +1,23 @@
+// 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 sealed partial class ZipCryptoStream
+ {
+ private static unsafe void FillHeaderRandomBytes(Span header)
+ {
+ // System.Security.Cryptography's RandomNumberGenerator is not referenced on browser and is
+ // not supported on wasi, so call the shared native CSPRNG directly. It is backed by
+ // crypto.getRandomValues() on browser and by wasi:random (getentropy/__wasi_random_get) on wasi.
+ Span randomBytes = header.Slice(0, 10);
+ fixed (byte* pRandomBytes = randomBytes)
+ {
+ if (Interop.Sys.GetCryptographicallySecureRandomBytes(pRandomBytes, randomBytes.Length) != 0)
+ {
+ throw new IOException(SR.UnableToGenerateRandomBytes);
+ }
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.Default.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.Default.cs
new file mode 100644
index 00000000000000..56c63770d6fa69
--- /dev/null
+++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.Default.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Security.Cryptography;
+
+namespace System.IO.Compression
+{
+ internal sealed partial class ZipCryptoStream
+ {
+ private static void FillHeaderRandomBytes(Span header)
+ {
+ RandomNumberGenerator.Fill(header.Slice(0, 10));
+ }
+ }
+}
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
index 3243c56566ebb0..64450569184a34 100644
--- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs
+++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs
@@ -4,14 +4,13 @@
using System.Buffers;
using System.Buffers.Binary;
using System.Diagnostics;
-using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace System.IO.Compression
{
- internal sealed class ZipCryptoStream : Stream
+ internal sealed partial class ZipCryptoStream : Stream
{
private const int EncryptionBufferSize = 4096;
@@ -138,7 +137,7 @@ internal static ZipCryptoKeys CreateKey(ReadOnlySpan password)
}
finally
{
- CryptographicOperations.ZeroMemory(passwordBytes);
+ Array.Clear(passwordBytes);
ArrayPool.Shared.Return(passwordBytes);
}
@@ -150,7 +149,7 @@ private void CalculateHeader(Span header)
Debug.Assert(header.Length == 12);
// bytes 0..9 random
- RandomNumberGenerator.Fill(header.Slice(0, 10));
+ FillHeaderRandomBytes(header);
// bytes 10..11 verifier
if (_crc32ForHeader.HasValue)
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 8ab7802ff61ac5..e79f9eeead138e 100644
--- a/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs
+++ b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs
@@ -1052,6 +1052,26 @@ public async Task DecryptEntries_MixedEncryptions(bool async)
await DisposeZipArchive(async, archive);
}
+ [Theory]
+ [MemberData(nameof(Get_Booleans_Data))]
+ [PlatformSpecific(TestPlatforms.Browser)]
+ public async Task DecryptZipCryptoEntry_Browser(bool async)
+ {
+ const string Password = "S3cur3P@ssw0rd";
+ using Stream archiveStream = await StreamHelpers.CreateTempCopyStream(passwordProtected("PasswordProtected_MixedEncryptions.zip"));
+ ZipArchive archive = await CreateZipArchive(async, archiveStream, ZipArchiveMode.Read);
+
+ ZipArchiveEntry entry = archive.GetEntry("hello.txt");
+ Assert.NotNull(entry);
+ Assert.Equal(ZipEncryptionMethod.ZipCrypto, entry.EncryptionMethod);
+
+ using Stream entryStream = await OpenEntryStream(async, entry, Password);
+ using StreamReader reader = new(entryStream);
+ Assert.Equal("Hello", reader.ReadToEnd().TrimEnd());
+
+ await DisposeZipArchive(async, archive);
+ }
+
[Theory]
[MemberData(nameof(Get_Booleans_Data))]
[SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on browser.")]
diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs b/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs
index 3e107ad21bb180..e0d1fad124f520 100644
--- a/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs
@@ -115,6 +115,9 @@ public async Task ZipArchiveInteropTest()
Configuration config = Configuration.Debug;
ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "ZipArchiveInteropTest", extraProperties: "true");
BuildProject(info, config, new BuildOptions(AssertAppBundle: false));
+ BuildPaths paths = GetBuildPaths(config, forPublish: false);
+ string pinvokeTableFileName = IsCoreClrRuntime ? "callhelpers-pinvoke.cpp" : "pinvoke-table.h";
+ Assert.DoesNotContain("System_Security_Cryptography", File.ReadAllText(Path.Combine(paths.ObjWasmDir, pinvokeTableFileName)));
RunResult result = await RunForBuildWithDotnetRun(new BrowserRunOptions(config, TestScenario: "ZipArchiveInteropTest"));
Assert.Collection(
result.TestOutput,