4using System.IO.Compression;
7using System.Threading.Tasks;
21 public static class Zip
23 private const int PK_LOCAL_FILE_HEADER = 0x04034b50;
24 private const int PK_DATA_DESCRIPTOR = 0x08074b50;
25 private const int PK_CENTRAL_DIRECTORY_FILE_HEADER = 0x02014b50;
26 private const int PK_END_OF_CENTRAL_DIRECTORY = 0x06054b50;
33 public static Task
CreateZipFile(
string SourceFileName,
string OutputFileName)
46 string OutputFileName,
string Password,
ZipEncryption EncryptionMethod)
48 return CreateZipFile(SourceFileName, OutputFileName,
false, Password,
60 string OutputFileName,
bool CreateFolder)
62 return CreateZipFile(SourceFileName, OutputFileName, CreateFolder,
null,
76 string OutputFileName,
bool CreateFolder,
string Password,
81 string Folder = Path.GetDirectoryName(OutputFileName);
82 if (!Directory.Exists(Folder))
83 Directory.CreateDirectory(Folder);
86 DateTime LastWriteTime = File.GetLastWriteTime(SourceFileName);
87 using FileStream fs = File.OpenRead(SourceFileName);
88 using FileStream Output = File.Create(OutputFileName);
91 Output, Password, EncryptionMethod);
101 byte[] SourceFileContents)
103 return CreateZipFile(SourceFileName, SourceFileContents,
null,
116 byte[] SourceFileContents,
string Password,
ZipEncryption EncryptionMethod)
119 DateTime.Now, Password, EncryptionMethod);
130 byte[] SourceFileContents, DateTime SourceLastWriteTime)
146 byte[] SourceFileContents, DateTime SourceLastWriteTime,
string Password,
149 using MemoryStream SourceFile =
new MemoryStream(SourceFileContents);
150 using MemoryStream
ZipFile =
new MemoryStream();
152 await
CreateZipFile(SourceFileName, SourceFile, SourceLastWriteTime,
153 ZipFile, Password, EncryptionMethod);
166 Stream SourceFileContents, DateTime SourceLastWriteTime, Stream Output)
182 Stream SourceFileContents, DateTime SourceLastWriteTime, Stream Output,
186 new Stream[] { SourceFileContents },
187 new DateTime[] { SourceLastWriteTime }, Output, Password, EncryptionMethod);
195 public static Task
CreateZipFile(
string[] SourceFileNames,
string OutputFileName)
208 string OutputFileName,
string Password,
ZipEncryption EncryptionMethod)
210 return CreateZipFile(SourceFileNames, OutputFileName,
false, Password,
222 string OutputFileName,
bool CreateFolder)
224 return CreateZipFile(SourceFileNames, OutputFileName, CreateFolder,
null,
238 string OutputFileName,
bool CreateFolder,
string Password,
243 string Folder = Path.GetDirectoryName(OutputFileName);
244 if (!Directory.Exists(Folder))
245 Directory.CreateDirectory(Folder);
248 int c = SourceFileNames.Length;
249 FileStream[] SourceFiles =
new FileStream[c];
250 DateTime[] LastWriteTimes =
new DateTime[c];
253 for (
int i = 0; i < c; i++)
255 SourceFiles[i] = File.OpenRead(SourceFileNames[i]);
256 LastWriteTimes[i] = File.GetLastWriteTime(SourceFileNames[i]);
259 using FileStream Output = File.Create(OutputFileName);
261 await
CreateZipFile(SourceFileNames, SourceFiles, LastWriteTimes,
262 Output, Password, EncryptionMethod);
266 foreach (FileStream fs
in SourceFiles)
278 byte[][] SourceFileContents)
280 return CreateZipFile(SourceFileNames, SourceFileContents,
null,
293 byte[][] SourceFileContents,
string Password,
ZipEncryption EncryptionMethod)
295 int c = SourceFileNames.Length;
296 DateTime[] LastWriteTimes =
new DateTime[c];
297 DateTime Now = DateTime.Now;
299 for (
int i = 0; i < c; i++)
300 LastWriteTimes[i] = Now;
303 LastWriteTimes, Password, EncryptionMethod);
314 byte[][] SourceFileContents, DateTime[] SourceLastWriteTimes)
330 byte[][] SourceFileContents, DateTime[] SourceLastWriteTimes,
string Password,
333 MemoryStream[] SourceFiles =
new MemoryStream[SourceFileContents.Length];
337 for (
int i = 0; i < SourceFileContents.Length; i++)
338 SourceFiles[i] =
new MemoryStream(SourceFileContents[i]);
340 using MemoryStream
ZipFile =
new MemoryStream();
342 await
CreateZipFile(SourceFileNames, SourceFiles, SourceLastWriteTimes,
343 ZipFile, Password, EncryptionMethod);
349 foreach (MemoryStream ms
in SourceFiles)
362 IEnumerable<Stream> SourceFileContents, IEnumerable<DateTime> SourceLastWriteTimes,
379 IEnumerable<Stream> SourceFileContents, IEnumerable<DateTime> SourceLastWriteTimes,
380 Stream Output,
string Password,
ZipEncryption EncryptionMethod)
382 if (SourceFileNames is
null)
383 throw new ArgumentNullException(nameof(SourceFileNames));
385 if (SourceFileContents is
null)
386 throw new ArgumentNullException(nameof(SourceFileContents));
388 if (SourceLastWriteTimes is
null)
389 throw new ArgumentNullException(nameof(SourceLastWriteTimes));
394 foreach (
string _ in SourceFileNames)
397 if (Count > ushort.MaxValue)
398 throw new IOException(
"Too many entries for non-ZIP64 archive.");
400 IEnumerator<string> SourceFileNameEnumerator = SourceFileNames.GetEnumerator();
401 IEnumerator<Stream> SourceFileContentEnumerator = SourceFileContents.GetEnumerator();
402 IEnumerator<DateTime> SourceLastWriteTimeEnumerator = SourceLastWriteTimes.GetEnumerator();
406 if (
string.IsNullOrEmpty(Password))
409 throw new ArgumentException(
"Password not specified.", nameof(Password));
416 throw new ArgumentException(
"Encryption method not specified.", nameof(EncryptionMethod));
421 EntryMeta[] Entries =
new EntryMeta[Count];
425 ushort VersionToExtract = EncryptionMethod
switch
427 ZipEncryption.Aes128Ae1 => 51,
428 ZipEncryption.Aes192Ae1 => 51,
429 ZipEncryption.Aes256Ae1 => 51,
430 ZipEncryption.Aes128Ae2 => 51,
431 ZipEncryption.Aes192Ae2 => 51,
432 ZipEncryption.Aes256Ae2 => 51,
436 while (SourceFileNameEnumerator.MoveNext() &&
437 SourceFileContentEnumerator.MoveNext() &&
438 SourceLastWriteTimeEnumerator.MoveNext())
440 string SourceFileName = SourceFileNameEnumerator.Current;
441 Stream SourceFileContent = SourceFileContentEnumerator.Current;
442 DateTime SourceLastWriteTime = SourceLastWriteTimeEnumerator.Current;
444 SourceFileContent.Position = 0;
446 byte[] Bin = await SourceFileContent.ReadAllAsync();
447 int UncompressedSize = Bin.Length;
451 using (MemoryStream ms =
new MemoryStream())
453 using (DeflateStream ds =
new DeflateStream(ms, CompressionLevel.Optimal,
true))
455 ds.Write(Bin, 0, UncompressedSize);
458 Compressed = ms.ToArray();
461 int CompressedSize0 = Compressed.Length;
462 int CompressedSizeTot = CompressedSize0;
464 string FileName = Path.GetFileName(SourceFileName);
465 byte[] FileNameBytes = Encoding.UTF8.GetBytes(FileName);
466 ushort FileNameLength = (ushort)FileNameBytes.Length;
468 if (FileNameLength > ushort.MaxValue)
469 throw new ArgumentException(
"Zip entry name too long: " + FileName, nameof(SourceFileNames));
472 ushort Flags = 0x0800;
476 ushort CompressionMethod = 8;
477 bool DataDescriptor =
false;
479 await Output.FlushAsync();
480 long HeaderOffset = Output.Position;
481 if (HeaderOffset >
int.MaxValue)
482 throw new IOException(
"ZIP output too large (>2GB). ZIP64 is not implemented.");
485 bool Ae2 = EncryptionMethod
switch
487 ZipEncryption.Aes128Ae1 =>
false,
488 ZipEncryption.Aes192Ae1 =>
false,
489 ZipEncryption.Aes256Ae1 =>
false,
490 ZipEncryption.Aes128Ae2 =>
true,
491 ZipEncryption.Aes192Ae2 =>
true,
492 ZipEncryption.Aes256Ae2 =>
true,
501 CompressedSizeTot += 12;
505 CompressionMethod = 99;
506 DataDescriptor =
true;
508 ushort AesVersion = EncryptionMethod
switch
510 ZipEncryption.Aes128Ae1 => 0x0001,
511 ZipEncryption.Aes192Ae1 => 0x0001,
512 ZipEncryption.Aes256Ae1 => 0x0001,
513 ZipEncryption.Aes128Ae2 => 0x0002,
514 ZipEncryption.Aes192Ae2 => 0x0002,
515 ZipEncryption.Aes256Ae2 => 0x0002,
516 _ =>
throw new ArgumentException(
"Invalid ZIP encryption method.", nameof(EncryptionMethod))
519 byte Strength = EncryptionMethod
switch
521 ZipEncryption.Aes128Ae1 => 1,
522 ZipEncryption.Aes128Ae2 => 1,
523 ZipEncryption.Aes192Ae1 => 2,
524 ZipEncryption.Aes192Ae2 => 2,
525 ZipEncryption.Aes256Ae1 => 3,
526 ZipEncryption.Aes256Ae2 => 3,
527 _ =>
throw new ArgumentException(
"Invalid ZIP encryption method.", nameof(EncryptionMethod))
530 SaltLen = Strength
switch
535 _ =>
throw new ArgumentException(
"Invalid ZIP encryption method.", nameof(EncryptionMethod))
542 (byte)AesVersion, 0x00,
548 ExtraLen = (ushort)Extra.Length;
550 CompressedSizeTot += SaltLen + 2;
551 if (Ae2 && UncompressedSize < 20)
554 CompressedSizeTot += 10;
557 if (CompressedSizeTot < 0)
558 throw new IOException(
"ZIP output too large (>2GB). ZIP64 is not implemented.");
561 EntryMeta Entry =
new EntryMeta()
563 FileNameBytes = FileNameBytes,
565 CompressionMethod = CompressionMethod,
566 LastWriteTime = SourceLastWriteTime.ToDosTime(),
567 LastWriteDate = SourceLastWriteTime.ToDosDate(),
569 CompressedSize = CompressedSizeTot,
570 UncompressedSize = UncompressedSize,
571 HeaderOffset = (int)HeaderOffset,
575 Entries[FileIndex++] = Entry;
579 Output.WriteUInt32(PK_LOCAL_FILE_HEADER);
580 Output.WriteUInt16(VersionToExtract);
581 Output.WriteUInt16(Flags);
582 Output.WriteUInt16(CompressionMethod);
583 Output.WriteUInt16(Entry.LastWriteTime);
584 Output.WriteUInt16(Entry.LastWriteDate);
588 Output.WriteUInt32(0);
589 Output.WriteUInt32(0);
590 Output.WriteUInt32(0);
594 Output.WriteUInt32(FileCrc32);
595 Output.WriteUInt32((uint)CompressedSizeTot);
596 Output.WriteUInt32((uint)UncompressedSize);
599 Output.WriteUInt16(FileNameLength);
600 Output.WriteUInt16(ExtraLen);
601 Output.Write(FileNameBytes, 0, FileNameBytes.Length);
604 Output.Write(Extra, 0, ExtraLen);
610 uint Key0 = 0x12345678;
611 uint Key1 = 0x23456789;
612 uint Key2 = 0x34567890;
614 foreach (
char ch
in Password)
617 throw new ArgumentException(
"Password contains invalid character: " + ch.ToString(), nameof(Password));
619 UpdateKeys(ref Key0, ref Key1, ref Key2, (
byte)ch);
622 using RandomNumberGenerator Rnd = RandomNumberGenerator.Create();
623 byte[] EncryptionHeader =
new byte[12];
625 Rnd.GetBytes(EncryptionHeader, 0, 11);
626 EncryptionHeader[11] = (byte)(FileCrc32 >> 24);
631 foreach (
byte b
in EncryptionHeader)
633 byte EncryptedByte = EncryptByte(ref Key0, ref Key1, ref Key2, b);
634 Output.WriteByte(EncryptedByte);
638 foreach (
byte b
in Compressed)
640 byte EncryptedByte = EncryptByte(ref Key0, ref Key1, ref Key2, b);
641 Output.WriteByte(EncryptedByte);
646 int KeyLen = SaltLen << 1;
647 byte[] Salt =
new byte[SaltLen];
648 using (RandomNumberGenerator rnd = RandomNumberGenerator.Create())
653 using Rfc2898DeriveBytes Pbkdf2 =
new Rfc2898DeriveBytes(
654 Password, Salt, 1000, HashAlgorithmName.SHA1);
656 byte[] EncryptionKey = Pbkdf2.GetBytes(KeyLen);
657 byte[] MacKey = Pbkdf2.GetBytes(KeyLen);
658 byte[] PasswordVerifier = Pbkdf2.GetBytes(2);
661 Output.Write(Salt, 0, SaltLen);
662 Output.Write(PasswordVerifier, 0, 2);
667 byte[] EncryptedPayload =
new byte[CompressedSize0];
668 using (Aes Aes = Aes.Create())
670 Aes.KeySize = KeyLen * 8;
672 Aes.Mode = CipherMode.ECB;
673 Aes.Padding = PaddingMode.None;
675 using ICryptoTransform Ecb = Aes.CreateEncryptor(EncryptionKey,
new byte[16]);
676 byte[] CounterBin =
new byte[16];
677 byte[] KeyStream =
new byte[16];
679 int BytesLeft = CompressedSize0;
683 while (Offset < CompressedSize0)
686 while (++CounterBin[i] == 0)
689 Ecb.TransformBlock(CounterBin, 0, 16, KeyStream, 0);
692 BlockSize = BytesLeft;
694 for (i = 0; i < BlockSize; i++, Offset++)
695 EncryptedPayload[Offset] = (
byte)(Compressed[Offset] ^ KeyStream[i]);
697 BytesLeft -= BlockSize;
702 Output.Write(EncryptedPayload, 0, CompressedSize0);
706 using HMACSHA1 Hmac =
new HMACSHA1(MacKey);
707 byte[] Auth = Hmac.ComputeHash(EncryptedPayload);
708 Output.Write(Auth, 0, 10);
712 Output.Write(Compressed, 0, Compressed.Length);
718 Output.WriteUInt32(PK_DATA_DESCRIPTOR);
719 Output.WriteUInt32(FileCrc32);
720 Output.WriteUInt32((uint)CompressedSizeTot);
721 Output.WriteUInt32((uint)UncompressedSize);
725 if (FileIndex != Count)
726 throw new InvalidOperationException(
"Mismatched number of zip entries.");
731 await Output.FlushAsync();
732 long CentralDirOffset = Output.Position;
733 if (CentralDirOffset >
int.MaxValue)
734 throw new IOException(
"ZIP output too large (>2GB). ZIP64 is not implemented.");
736 foreach (EntryMeta Entry
in Entries)
740 Output.WriteUInt32(PK_CENTRAL_DIRECTORY_FILE_HEADER);
741 Output.WriteUInt16(VersionToExtract);
742 Output.WriteUInt16(VersionToExtract);
743 Output.WriteUInt16(Entry.Flags);
744 Output.WriteUInt16(Entry.CompressionMethod);
745 Output.WriteUInt16(Entry.LastWriteTime);
746 Output.WriteUInt16(Entry.LastWriteDate);
747 Output.WriteUInt32(Entry.Crc32);
748 Output.WriteUInt32((uint)Entry.CompressedSize);
749 Output.WriteUInt32((uint)Entry.UncompressedSize);
751 Output.WriteUInt16((ushort)Entry.FileNameBytes.Length);
752 Output.WriteUInt16(Entry.ExtraLen);
753 Output.WriteUInt16(0);
755 Output.WriteUInt16(0);
756 Output.WriteUInt16(0);
757 Output.WriteUInt32(0);
759 Output.WriteUInt32((uint)Entry.HeaderOffset);
761 Output.Write(Entry.FileNameBytes, 0, Entry.FileNameBytes.Length);
763 if (Entry.ExtraLen > 0)
764 Output.Write(Entry.Extra, 0, Entry.ExtraLen);
772 await Output.FlushAsync();
773 uint CentralDirSize = (uint)(Output.Position - CentralDirOffset);
777 Output.WriteUInt32(PK_END_OF_CENTRAL_DIRECTORY);
778 Output.WriteUInt16(0);
779 Output.WriteUInt16(0);
780 Output.WriteUInt16((ushort)Count);
781 Output.WriteUInt16((ushort)Count);
782 Output.WriteUInt32(CentralDirSize);
783 Output.WriteUInt32((uint)CentralDirOffset);
784 Output.WriteUInt16(0);
786 await Output.FlushAsync();
789 private struct EntryMeta
791 public byte[] FileNameBytes;
793 public ushort CompressionMethod;
794 public ushort LastWriteTime;
795 public ushort LastWriteDate;
797 public int CompressedSize;
798 public int UncompressedSize;
799 public int HeaderOffset;
801 public ushort ExtraLen;
811 private static void UpdateKeys(ref uint Key0, ref uint Key1, ref uint Key2,
byte Value)
813 Key0 = Crc32.
Update(Key0, Value);
815 Key1 = Key1 * 134775813 + 1;
816 Key2 = Crc32.Update(Key2, (
byte)(Key1 >> 24));
827 private static byte EncryptByte(ref uint Key0, ref uint Key1, ref uint Key2,
byte Value)
829 uint Temp = Key2 | 3;
830 byte KeyStreamByte = (byte)((Temp * (Temp ^ 1)) >> 8);
831 byte CipherByte = (byte)(Value ^ KeyStreamByte);
832 UpdateKeys(ref Key0, ref Key1, ref Key2, Value);
841 private static void WriteUInt16(
this Stream Output, ushort Value)
843 Output.WriteByte((
byte)Value);
845 Output.WriteByte((
byte)Value);
853 private static void WriteUInt32(
this Stream Output, uint Value)
855 Output.WriteByte((
byte)Value);
857 Output.WriteByte((
byte)Value);
859 Output.WriteByte((
byte)Value);
861 Output.WriteByte((
byte)Value);
Static class for computing CRC-32 checksums.
static uint Update(uint Crc, byte Value)
Updates an existing CRC value with one byte (standard CRC-32 algorithm).
static uint Compute(byte[] Data)
Computes the CRC-32 of a byte array (polynomial 0xEDB88320, init = 0xFFFFFFFF, xor out = 0xFFFFFFFF).
Static class for creating ZIP files.
static async Task< byte[]> CreateZipFile(string SourceFileName, byte[] SourceFileContents, DateTime SourceLastWriteTime, string Password, ZipEncryption EncryptionMethod)
Creates a ZIP file, possible password-protected, containing a single file.
static Task CreateZipFile(string SourceFileName, Stream SourceFileContents, DateTime SourceLastWriteTime, Stream Output, string Password, ZipEncryption EncryptionMethod)
Creates a ZIP file, possible password-protected, containing a single file.
static Task< byte[]> CreateZipFile(string SourceFileName, byte[] SourceFileContents, DateTime SourceLastWriteTime)
Creates a ZIP file containing a single file.
static Task< byte[]> CreateZipFile(string[] SourceFileNames, byte[][] SourceFileContents, DateTime[] SourceLastWriteTimes)
Creates a ZIP file containing a single file.
static Task CreateZipFile(string[] SourceFileNames, string OutputFileName)
Creates a ZIP file containing a single file.
static Task CreateZipFile(string[] SourceFileNames, string OutputFileName, bool CreateFolder)
Creates a ZIP file containing a single file.
static Task CreateZipFile(string SourceFileName, Stream SourceFileContents, DateTime SourceLastWriteTime, Stream Output)
Creates a ZIP file containing a single file.
static async Task CreateZipFile(string SourceFileName, string OutputFileName, bool CreateFolder, string Password, ZipEncryption EncryptionMethod)
Creates a ZIP file, possible password-protected, containing a single file.
static Task CreateZipFile(IEnumerable< string > SourceFileNames, IEnumerable< Stream > SourceFileContents, IEnumerable< DateTime > SourceLastWriteTimes, Stream Output)
Creates a ZIP file containing a single file.
static async Task CreateZipFile(IEnumerable< string > SourceFileNames, IEnumerable< Stream > SourceFileContents, IEnumerable< DateTime > SourceLastWriteTimes, Stream Output, string Password, ZipEncryption EncryptionMethod)
Creates a ZIP file, possible password-protected, containing a single file.
static Task CreateZipFile(string SourceFileName, string OutputFileName, bool CreateFolder)
Creates a ZIP file containing a single file.
static Task< byte[]> CreateZipFile(string[] SourceFileNames, byte[][] SourceFileContents)
Creates a ZIP file containing a single file.
static Task CreateZipFile(string[] SourceFileNames, string OutputFileName, string Password, ZipEncryption EncryptionMethod)
Creates a ZIP file, possible password-protected, containing a single file.
static Task< byte[]> CreateZipFile(string SourceFileName, byte[] SourceFileContents, string Password, ZipEncryption EncryptionMethod)
Creates a ZIP file, possible password-protected, containing a single file.
static Task CreateZipFile(string SourceFileName, string OutputFileName, string Password, ZipEncryption EncryptionMethod)
Creates a ZIP file, possible password-protected, containing a single file.
static async Task< byte[]> CreateZipFile(string[] SourceFileNames, byte[][] SourceFileContents, DateTime[] SourceLastWriteTimes, string Password, ZipEncryption EncryptionMethod)
Creates a ZIP file, possible password-protected, containing a single file.
static Task< byte[]> CreateZipFile(string[] SourceFileNames, byte[][] SourceFileContents, string Password, ZipEncryption EncryptionMethod)
Creates a ZIP file, possible password-protected, containing a single file.
static Task CreateZipFile(string SourceFileName, string OutputFileName)
Creates a ZIP file containing a single file.
static Task< byte[]> CreateZipFile(string SourceFileName, byte[] SourceFileContents)
Creates a ZIP file containing a single file.
static async Task CreateZipFile(string[] SourceFileNames, string OutputFileName, bool CreateFolder, string Password, ZipEncryption EncryptionMethod)
Creates a ZIP file, possible password-protected, containing a single file.
ZipEncryption
Enumeration containing ZIP Encryption methods.