< Summary

Information
Class: System.IO.Compression.ZipCryptoStream
Assembly: System.IO.Compression
File(s): D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\ZipCryptoStream.cs
Line coverage
82%
Covered lines: 238
Uncovered lines: 50
Coverable lines: 288
Total lines: 461
Line coverage: 82.6%
Branch coverage
71%
Covered branches: 47
Total branches: 66
Branch coverage: 71.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%11100%
CreateCrc32Table()100%66100%
Crc32Update(...)100%11100%
.ctor(...)100%11100%
Create(...)100%11100%
CreateAsync(...)100%11100%
Create(...)100%11100%
.ctor(...)100%11100%
CreateKey(...)100%22100%
CalculateHeader(...)75%4478.94%
EnsureHeader()50%4477.77%
EnsureHeaderAsync(...)50%4477.77%
ReadAndValidateHeaderCore(...)100%6689.65%
UpdateKeys(...)100%11100%
DecryptByte(...)100%11100%
DecryptAndUpdateKeys(...)100%11100%
Flush()100%110%
Read(...)100%11100%
Read(...)75%4480%
Seek(...)100%110%
SetLength(...)100%110%
Write(...)100%11100%
Write(...)83.33%6690.47%
Dispose(...)50%8872.22%
DisposeAsync()50%6666.66%
ReadAsync(...)100%110%
ReadAsync(...)75%4485.71%
WriteAsync(...)100%110%
WriteAsync(...)83.33%6691.3%
FlushAsync(...)100%110%
GetWriteWorkBuffer()50%22100%

File(s)

D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\ZipCryptoStream.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Buffers.Binary;
 6using System.Diagnostics;
 7using System.Security.Cryptography;
 8using System.Text;
 9using System.Threading;
 10using System.Threading.Tasks;
 11
 12namespace System.IO.Compression
 13{
 14    internal sealed class ZipCryptoStream : Stream
 15    {
 16        private const int EncryptionBufferSize = 4096;
 17
 18        private readonly bool _encrypting;
 19        private readonly Stream _base;
 20        private readonly bool _leaveOpen;
 21        private bool _headerWritten;
 22        private bool _disposed;
 23        private readonly ushort _verifierLow2Bytes;       // (DOS time low word when streaming)
 24        private readonly uint? _crc32ForHeader;           // (CRC-based header when not streaming)
 25
 26        private uint _key0;
 27        private uint _key1;
 28        private uint _key2;
 129        private static readonly uint[] s_crc2Table = CreateCrc32Table();
 30
 31        // Reusable work buffer for write operations, lazily allocated on first write
 32        private byte[]? _writeWorkBuffer;
 33
 34        private static uint[] CreateCrc32Table()
 135        {
 136            var table = new uint[256];
 51437            for (uint i = 0; i < 256; i++)
 25638            {
 25639                uint c = i;
 460840                for (int j = 0; j < 8; j++)
 204841                    c = (c & 1) != 0 ? (0xEDB88320u ^ (c >> 1)) : (c >> 1);
 25642                table[i] = c;
 25643            }
 144            return table;
 145        }
 46
 50901447        private static uint Crc32Update(uint crc, byte b) => s_crc2Table[(crc ^ b) & 0xFF] ^ (crc >> 8);
 48
 49        // Private decryption constructor - use Create/CreateAsync factory methods instead.
 50        // Keys must already be validated before calling this constructor.
 80551        private ZipCryptoStream(Stream baseStream, uint key0, uint key1, uint key2, bool leaveOpen = false)
 80552        {
 80553            _base = baseStream;
 80554            _key0 = key0;
 80555            _key1 = key1;
 80556            _key2 = key2;
 80557            _encrypting = false;
 80558            _leaveOpen = leaveOpen;
 80559        }
 60
 61        /// <summary>
 62        /// Creates a ZipCryptoStream for decryption. Reads and validates the 12-byte header synchronously.
 63        /// </summary>
 64        internal static ZipCryptoStream Create(Stream baseStream, ZipCryptoKeys keys, byte expectedCheckByte, bool encry
 80465        {
 80466            ArgumentNullException.ThrowIfNull(baseStream);
 80467            Debug.Assert(!encrypting, "Use the overload with passwordVerifierLow2Bytes for encryption.");
 68
 80469            (uint key0, uint key1, uint key2) = ReadAndValidateHeaderCore(isAsync: false, baseStream, keys, expectedChec
 40370            return new ZipCryptoStream(baseStream, key0, key1, key2, leaveOpen);
 40371        }
 72
 73        /// <summary>
 74        /// Creates a ZipCryptoStream for decryption. Reads and validates the 12-byte header asynchronously.
 75        /// </summary>
 76        internal static async Task<ZipCryptoStream> CreateAsync(Stream baseStream, ZipCryptoKeys keys, byte expectedChec
 80477        {
 80478            ArgumentNullException.ThrowIfNull(baseStream);
 80479            Debug.Assert(!encrypting, "Use the overload with passwordVerifierLow2Bytes for encryption.");
 80
 80481            (uint key0, uint key1, uint key2) = await ReadAndValidateHeaderCore(isAsync: true, baseStream, keys, expecte
 40282            return new ZipCryptoStream(baseStream, key0, key1, key2, leaveOpen);
 40283        }
 84
 85        /// <summary>
 86        /// Creates a ZipCryptoStream for encryption. Only synchronous creation is needed since no I/O is performed here
 87        /// </summary>
 88        internal static ZipCryptoStream Create(Stream baseStream,
 89                                             ZipCryptoKeys keys,
 90                                             ushort passwordVerifierLow2Bytes,
 91                                             bool encrypting,
 92                                             uint? crc32 = null,
 93                                             bool leaveOpen = false)
 80494        {
 80495            ArgumentNullException.ThrowIfNull(baseStream);
 80496            Debug.Assert(encrypting, "Use the overload with expectedCheckByte for decryption.");
 97
 80498            return new ZipCryptoStream(baseStream, keys, passwordVerifierLow2Bytes, crc32, leaveOpen);
 80499        }
 100
 101        // Encryption constructor
 804102        private ZipCryptoStream(Stream baseStream,
 804103                               ZipCryptoKeys keys,
 804104                               ushort passwordVerifierLow2Bytes,
 804105                               uint? crc32,
 804106                               bool leaveOpen)
 804107        {
 804108            _base = baseStream;
 804109            _encrypting = true;
 804110            _leaveOpen = leaveOpen;
 804111            _verifierLow2Bytes = passwordVerifierLow2Bytes;
 804112            _crc32ForHeader = crc32;
 804113            _key0 = keys.Key0;
 804114            _key1 = keys.Key1;
 804115            _key2 = keys.Key2;
 804116        }
 117
 118        // Creates the persisted key material from a password.
 119        // Returns a struct of 3 integers to keep the key off the heap.
 120        internal static ZipCryptoKeys CreateKey(ReadOnlySpan<char> password)
 2412121        {
 122            // Initialize keys with standard ZipCrypto initial values
 2412123            uint key0 = 305419896;
 2412124            uint key1 = 591751049;
 2412125            uint key2 = 878082192;
 126
 127            // Feed the password's UTF-8 bytes into the key schedule. UTF-8 (rather than ASCII)
 128            // preserves non-ASCII passwords; interop with tools that assume a different code page
 129            // is documented as a caveat.
 2412130            byte[] passwordBytes = ArrayPool<byte>.Shared.Rent(Encoding.UTF8.GetMaxByteCount(password.Length));
 131            try
 2412132            {
 2412133                int byteCount = Encoding.UTF8.GetBytes(password, passwordBytes);
 69144134                for (int i = 0; i < byteCount; i++)
 32160135                {
 32160136                    UpdateKeys(ref key0, ref key1, ref key2, passwordBytes[i]);
 32160137                }
 2412138            }
 139            finally
 2412140            {
 2412141                CryptographicOperations.ZeroMemory(passwordBytes);
 2412142                ArrayPool<byte>.Shared.Return(passwordBytes);
 2412143            }
 144
 2412145            return new ZipCryptoKeys(key0, key1, key2);
 2412146        }
 147
 148        private void CalculateHeader(Span<byte> header)
 804149        {
 804150            Debug.Assert(header.Length == 12);
 151
 152            // bytes 0..9 random
 804153            RandomNumberGenerator.Fill(header.Slice(0, 10));
 154
 155            // bytes 10..11 verifier
 804156            if (_crc32ForHeader.HasValue)
 0157            {
 0158                uint crc = _crc32ForHeader.Value;
 0159                BinaryPrimitives.WriteUInt16LittleEndian(header.Slice(10), (ushort)(crc >> 16));
 0160            }
 161            else
 804162            {
 804163                BinaryPrimitives.WriteUInt16LittleEndian(header.Slice(10), _verifierLow2Bytes);
 804164            }
 165
 166            // encrypt in place
 20904167            for (int i = 0; i < header.Length; i++)
 9648168            {
 9648169                byte p = header[i];
 9648170                byte ks = DecryptByte(_key2);
 9648171                header[i] = (byte)(p ^ ks);
 172
 173                // keys updated with PLAINTEXT per ZIP spec
 9648174                UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 9648175            }
 804176        }
 177
 178        private unsafe void EnsureHeader()
 402179        {
 402180            if (!_encrypting || _headerWritten)
 0181            {
 0182                return;
 183            }
 184
 402185            Span<byte> header = stackalloc byte[12];
 402186            CalculateHeader(header);
 402187            _base.Write(header);
 402188            _headerWritten = true;
 402189        }
 190
 191        private async ValueTask EnsureHeaderAsync(CancellationToken cancellationToken)
 402192        {
 402193            if (!_encrypting || _headerWritten)
 0194            {
 0195                return;
 196            }
 197
 402198            byte[] header = new byte[12];
 402199            CalculateHeader(header);
 402200            await _base.WriteAsync(header, cancellationToken).ConfigureAwait(false);
 402201            _headerWritten = true;
 402202        }
 203
 204        private static async Task<(uint key0, uint key1, uint key2)> ReadAndValidateHeaderCore(bool isAsync, Stream base
 1608205        {
 206            // Initialize keys from input
 1608207            uint key0 = keys.Key0;
 1608208            uint key1 = keys.Key1;
 1608209            uint key2 = keys.Key2;
 210
 1608211            byte[] hdr = new byte[12];
 212
 213            try
 1608214            {
 1608215                if (isAsync)
 804216                {
 804217                    await baseStream.ReadExactlyAsync(hdr, cancellationToken).ConfigureAwait(false);
 804218                }
 219                else
 804220                {
 804221                    baseStream.ReadExactly(hdr);
 804222                }
 1608223            }
 0224            catch (EndOfStreamException)
 0225            {
 0226                throw new InvalidDataException(SR.TruncatedZipCryptoHeader);
 227            }
 228
 229            // Decrypt header and update keys
 41808230            for (int i = 0; i < hdr.Length; i++)
 19296231            {
 19296232                byte m = DecryptByte(key2);
 19296233                byte plain = (byte)(hdr[i] ^ m);
 19296234                UpdateKeys(ref key0, ref key1, ref key2, plain);
 19296235                hdr[i] = plain;
 19296236            }
 237
 1608238            if (hdr[11] != expectedCheckByte)
 803239            {
 803240                throw new InvalidDataException(SR.InvalidPassword);
 241            }
 242
 805243            return (key0, key1, key2);
 805244        }
 245
 246        private static void UpdateKeys(ref uint key0, ref uint key1, ref uint key2, byte b)
 254507247        {
 254507248            key0 = Crc32Update(key0, b);
 254507249            key1 += (key0 & 0xFF);
 254507250            key1 = key1 * 134775813 + 1;
 254507251            key2 = Crc32Update(key2, (byte)(key1 >> 24));
 254507252        }
 253
 254        private static byte DecryptByte(uint key2)
 222347255        {
 222347256            uint temp = key2 | 2;
 222347257            return (byte)((temp * (temp ^ 1)) >> 8);
 222347258        }
 259
 260        private byte DecryptAndUpdateKeys(byte ciph)
 96703261        {
 96703262            byte m = DecryptByte(_key2);
 96703263            byte plain = (byte)(ciph ^ m);
 96703264            UpdateKeys(ref _key0, ref _key1, ref _key2, plain);
 96703265            return plain;
 96703266        }
 267
 1610268        public override bool CanRead => !_disposed && !_encrypting;
 804269        public override bool CanSeek => false;
 804270        public override bool CanWrite => !_disposed && _encrypting;
 0271        public override long Length => throw new NotSupportedException();
 272        public override long Position
 273        {
 0274            get => throw new NotSupportedException();
 0275            set => throw new NotSupportedException();
 276        }
 277        public override void Flush()
 0278        {
 0279            ObjectDisposedException.ThrowIf(_disposed, this);
 0280            _base.Flush();
 0281        }
 282
 283        public override int Read(byte[] buffer, int offset, int count)
 403284        {
 403285            ValidateBufferArguments(buffer, offset, count);
 403286            return Read(buffer.AsSpan(offset, count));
 403287        }
 288
 289        public override int Read(Span<byte> destination)
 403290        {
 403291            ObjectDisposedException.ThrowIf(_disposed, this);
 403292            if (_encrypting)
 0293            {
 0294                throw new NotSupportedException(SR.ReadingNotSupported);
 295            }
 403296            int n = _base.Read(destination);
 97512297            for (int i = 0; i < n; i++)
 48353298                destination[i] = DecryptAndUpdateKeys(destination[i]);
 403299            return n;
 300
 403301        }
 302
 0303        public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
 0304        public override void SetLength(long value) => throw new NotSupportedException();
 305
 306        public override void Write(byte[] buffer, int offset, int count)
 402307        {
 402308            ValidateBufferArguments(buffer, offset, count);
 402309            Write(buffer.AsSpan(offset, count));
 402310        }
 311
 312        public override void Write(ReadOnlySpan<byte> buffer)
 402313        {
 402314            ObjectDisposedException.ThrowIf(_disposed, this);
 402315            if (!_encrypting)
 0316            {
 0317                throw new NotSupportedException(SR.WritingNotSupported);
 318            }
 319
 402320            EnsureHeader();
 321
 402322            byte[] workBuffer = GetWriteWorkBuffer();
 323
 804324            while (!buffer.IsEmpty)
 402325            {
 402326                int chunkSize = Math.Min(buffer.Length, workBuffer.Length);
 327
 97504328                for (int i = 0; i < chunkSize; i++)
 48350329                {
 48350330                    byte ks = DecryptByte(_key2);
 48350331                    byte p = buffer[i];
 48350332                    workBuffer[i] = (byte)(p ^ ks);
 48350333                    UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 48350334                }
 335
 402336                _base.Write(workBuffer, 0, chunkSize);
 402337                buffer = buffer[chunkSize..];
 402338            }
 402339        }
 340
 341        protected override void Dispose(bool disposing)
 403342        {
 403343            if (_disposed)
 0344            {
 0345                return;
 346            }
 403347            _disposed = true;
 348
 403349            if (disposing)
 403350            {
 351                // If encrypted empty entry (no payload written), still must emit 12-byte header:
 403352                if (_encrypting)
 0353                {
 0354                    EnsureHeader();
 0355                }
 356
 403357                if (!_leaveOpen)
 403358                {
 403359                    _base.Dispose();
 403360                }
 403361            }
 403362            base.Dispose(disposing);
 403363        }
 364
 365        public override async ValueTask DisposeAsync()
 402366        {
 402367            if (_disposed)
 0368            {
 0369                return;
 370            }
 402371            _disposed = true;
 372
 373            // If encrypted empty entry (no payload written), still must emit 12-byte header:
 402374            if (_encrypting)
 0375            {
 0376                await EnsureHeaderAsync(CancellationToken.None).ConfigureAwait(false);
 0377            }
 402378            if (!_leaveOpen)
 402379            {
 402380                await _base.DisposeAsync().ConfigureAwait(false);
 402381            }
 382
 402383            GC.SuppressFinalize(this);
 384
 385            // Don't call base.DisposeAsync() as it would call Dispose() synchronously,
 386            // which could fail on async-only streams. We've already handled all cleanup.
 402387        }
 388
 389        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
 0390        {
 0391            ValidateBufferArguments(buffer, offset, count);
 0392            return ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
 0393        }
 394
 395        public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = defaul
 402396        {
 402397            ObjectDisposedException.ThrowIf(_disposed, this);
 402398            if (_encrypting)
 0399            {
 0400                throw new NotSupportedException(SR.ReadingNotSupported);
 401            }
 402
 402403            cancellationToken.ThrowIfCancellationRequested();
 402404            int n = await _base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
 402405            Span<byte> span = buffer.Span;
 406
 97504407            for (int i = 0; i < n; i++)
 48350408            {
 48350409                span[i] = DecryptAndUpdateKeys(span[i]);
 48350410            }
 411
 402412            return n;
 402413        }
 414
 415        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
 0416        {
 0417            ValidateBufferArguments(buffer, offset, count);
 0418            return WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
 0419        }
 420
 421        public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = de
 402422        {
 402423            ObjectDisposedException.ThrowIf(_disposed, this);
 402424            if (!_encrypting)
 0425            {
 0426                throw new NotSupportedException(SR.WritingNotSupported);
 427            }
 428
 402429            cancellationToken.ThrowIfCancellationRequested();
 430
 402431            await EnsureHeaderAsync(cancellationToken).ConfigureAwait(false);
 432
 402433            byte[] workBuffer = GetWriteWorkBuffer();
 434
 804435            while (!buffer.IsEmpty)
 402436            {
 402437                int chunkSize = Math.Min(buffer.Length, workBuffer.Length);
 402438                ReadOnlySpan<byte> chunk = buffer.Span[..chunkSize];
 439
 97504440                for (int i = 0; i < chunkSize; i++)
 48350441                {
 48350442                    byte ks = DecryptByte(_key2);
 48350443                    byte p = chunk[i];
 48350444                    workBuffer[i] = (byte)(p ^ ks);
 48350445                    UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 48350446                }
 447
 402448                await _base.WriteAsync(workBuffer.AsMemory(0, chunkSize), cancellationToken).ConfigureAwait(false);
 402449                buffer = buffer[chunkSize..];
 402450            }
 402451        }
 452
 453        public override Task FlushAsync(CancellationToken cancellationToken)
 0454        {
 0455            ObjectDisposedException.ThrowIf(_disposed, this);
 0456            return _base.FlushAsync(cancellationToken);
 0457        }
 458
 804459        private byte[] GetWriteWorkBuffer() => _writeWorkBuffer ??= new byte[EncryptionBufferSize];
 460    }
 461}

Methods/Properties

.cctor()
CreateCrc32Table()
Crc32Update(System.UInt32,System.Byte)
.ctor(System.IO.Stream,System.UInt32,System.UInt32,System.UInt32,System.Boolean)
Create(System.IO.Stream,System.IO.Compression.ZipCryptoKeys,System.Byte,System.Boolean,System.Boolean)
CreateAsync(System.IO.Stream,System.IO.Compression.ZipCryptoKeys,System.Byte,System.Boolean,System.Threading.CancellationToken,System.Boolean)
Create(System.IO.Stream,System.IO.Compression.ZipCryptoKeys,System.UInt16,System.Boolean,System.Nullable`1<System.UInt32>,System.Boolean)
.ctor(System.IO.Stream,System.IO.Compression.ZipCryptoKeys,System.UInt16,System.Nullable`1<System.UInt32>,System.Boolean)
CreateKey(System.ReadOnlySpan`1<System.Char>)
CalculateHeader(System.Span`1<System.Byte>)
EnsureHeader()
EnsureHeaderAsync(System.Threading.CancellationToken)
ReadAndValidateHeaderCore(System.Boolean,System.IO.Stream,System.IO.Compression.ZipCryptoKeys,System.Byte,System.Threading.CancellationToken)
UpdateKeys(System.UInt32&,System.UInt32&,System.UInt32&,System.Byte)
DecryptByte(System.UInt32)
DecryptAndUpdateKeys(System.Byte)
CanRead()
CanSeek()
CanWrite()
Length()
Position()
Position(System.Int64)
Flush()
Read(System.Byte[],System.Int32,System.Int32)
Read(System.Span`1<System.Byte>)
Seek(System.Int64,System.IO.SeekOrigin)
SetLength(System.Int64)
Write(System.Byte[],System.Int32,System.Int32)
Write(System.ReadOnlySpan`1<System.Byte>)
Dispose(System.Boolean)
DisposeAsync()
ReadAsync(System.Byte[],System.Int32,System.Int32,System.Threading.CancellationToken)
ReadAsync(System.Memory`1<System.Byte>,System.Threading.CancellationToken)
WriteAsync(System.Byte[],System.Int32,System.Int32,System.Threading.CancellationToken)
WriteAsync(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
FlushAsync(System.Threading.CancellationToken)
GetWriteWorkBuffer()