Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added AES to ZipFile and ZipOutputStream

  • Loading branch information...
commit f9ccb5ae8f833460a30df3b8345e4ddd8f8a3420 1 parent 5f464df
David Pierson authored
View
170 src/Encryption/ZipAESStream.cs
@@ -0,0 +1,170 @@
+//
+// ZipAESStream.cs
+//
+// Copyright 2009 David Pierson
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+//
+// Linking this library statically or dynamically with other modules is
+// making a combined work based on this library. Thus, the terms and
+// conditions of the GNU General Public License cover the whole
+// combination.
+//
+// As a special exception, the copyright holders of this library give you
+// permission to link this library with independent modules to produce an
+// executable, regardless of the license terms of these independent
+// modules, and to copy and distribute the resulting executable under
+// terms of your choice, provided that you also meet, for each linked
+// independent module, the terms and conditions of the license of that
+// module. An independent module is a module which is not derived from
+// or based on this library. If you modify this library, you may extend
+// this exception to your version of the library, but you are not
+// obligated to do so. If you do not wish to do so, delete this
+// exception statement from your version.
+//
+
+#if !NETCF_1_0
+
+using System;
+using System.IO;
+using System.Security.Cryptography;
+
+namespace ICSharpCode.SharpZipLib.Encryption {
+
+ // Based on information from http://www.winzip.com/aes_info.htm
+ // and http://www.gladman.me.uk/cryptography_technology/fileencrypt/
+
+ /// <summary>
+ /// Encrypts and decrypts AES ZIP
+ /// </summary>
+ public class ZipAESStream : CryptoStream {
+
+ /// <summary>
+ /// Constructor
+ /// </summary>
+ /// <param name="stream">The stream on which to perform the cryptographic transformation.</param>
+ /// <param name="transform">Instance of ZipAESTransform</param>
+ /// <param name="mode">Read or Write</param>
+ public ZipAESStream(Stream stream, ZipAESTransform transform, CryptoStreamMode mode)
+ : base(stream, transform, mode) {
+
+ _stream = stream;
+ _transform = transform;
+ _slideBuffer = new byte[1024];
+
+ _blockAndAuth = CRYPTO_BLOCK_SIZE + AUTH_CODE_LENGTH;
+
+ // mode:
+ // CryptoStreamMode.Read means we read from "stream" and pass decrypted to our Read() method.
+ // Write bypasses this stream and uses the Transform directly.
+ if (mode != CryptoStreamMode.Read) {
+ throw new Exception("ZipAESStream only for read");
+ }
+ }
+
+ // The final n bytes of the AES stream contain the Auth Code.
+ private const int AUTH_CODE_LENGTH = 10;
+
+ private Stream _stream;
+ private ZipAESTransform _transform;
+ private byte[] _slideBuffer;
+ private int _slideBufStartPos;
+ private int _slideBufFreePos;
+ // Blocksize is always 16 here, even for AES-256 which has transform.InputBlockSize of 32.
+ private const int CRYPTO_BLOCK_SIZE = 16;
+ private int _blockAndAuth;
+
+ /// <summary>
+ /// Reads a sequence of bytes from the current CryptoStream into buffer,
+ /// and advances the position within the stream by the number of bytes read.
+ /// </summary>
+ public override int Read(byte[] outBuffer, int offset, int count) {
+ int nBytes = 0;
+ while (nBytes < count) {
+ // Calculate buffer quantities vs read-ahead size, and check for sufficient free space
+ int byteCount = _slideBufFreePos - _slideBufStartPos;
+
+ // Need to handle final block and Auth Code specially, but don't know total data length.
+ // Maintain a read-ahead equal to the length of (crypto block + Auth Code).
+ // When that runs out we can detect these final sections.
+ int lengthToRead = _blockAndAuth - byteCount;
+ if (_slideBuffer.Length - _slideBufFreePos < lengthToRead) {
+ // Shift the data to the beginning of the buffer
+ int iTo = 0;
+ for (int iFrom = _slideBufStartPos; iFrom < _slideBufFreePos; iFrom++, iTo++) {
+ _slideBuffer[iTo] = _slideBuffer[iFrom];
+ }
+ _slideBufFreePos -= _slideBufStartPos; // Note the -=
+ _slideBufStartPos = 0;
+ }
+ int obtained = _stream.Read(_slideBuffer, _slideBufFreePos, lengthToRead);
+ _slideBufFreePos += obtained;
+
+ // Recalculate how much data we now have
+ byteCount = _slideBufFreePos - _slideBufStartPos;
+ if (byteCount >= _blockAndAuth) {
+ // At least a 16 byte block and an auth code remains.
+ _transform.TransformBlock(_slideBuffer,
+ _slideBufStartPos,
+ CRYPTO_BLOCK_SIZE,
+ outBuffer,
+ offset);
+ nBytes += CRYPTO_BLOCK_SIZE;
+ offset += CRYPTO_BLOCK_SIZE;
+ _slideBufStartPos += CRYPTO_BLOCK_SIZE;
+ } else {
+ // Last round.
+ if (byteCount > AUTH_CODE_LENGTH) {
+ // At least one byte of data plus auth code
+ int finalBlock = byteCount - AUTH_CODE_LENGTH;
+ _transform.TransformBlock(_slideBuffer,
+ _slideBufStartPos,
+ finalBlock,
+ outBuffer,
+ offset);
+
+ nBytes += finalBlock;
+ _slideBufStartPos += finalBlock;
+ }
+ else if (byteCount < AUTH_CODE_LENGTH)
+ throw new Exception("Internal error missed auth code"); // Coding bug
+ // Final block done. Check Auth code.
+ byte[] calcAuthCode = _transform.GetAuthCode();
+ for (int i = 0; i < AUTH_CODE_LENGTH; i++) {
+ if (calcAuthCode[i] != _slideBuffer[_slideBufStartPos + i]) {
+ throw new Exception("AES Authentication Code does not match. This is a super-CRC check on the data in the file after compression and encryption. \r\n"
+ + "The file may be damaged.");
+ }
+ }
+
+ break; // Reached the auth code
+ }
+ }
+ return nBytes;
+ }
+
+ /// <summary>
+ /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
+ /// </summary>
+ /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream. </param>
+ /// <param name="offset">The byte offset in buffer at which to begin copying bytes to the current stream. </param>
+ /// <param name="count">The number of bytes to be written to the current stream. </param>
+ public override void Write(byte[] buffer, int offset, int count) {
+ // ZipAESStream is used for reading but not for writing. Writing uses the ZipAESTransform directly.
+ throw new NotImplementedException();
+ }
+ }
+}
+#endif
View
196 src/Encryption/ZipAESTransform.cs
@@ -0,0 +1,196 @@
+//
+// ZipAESTransform.cs
+//
+// Copyright 2009 David Pierson
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+//
+// Linking this library statically or dynamically with other modules is
+// making a combined work based on this library. Thus, the terms and
+// conditions of the GNU General Public License cover the whole
+// combination.
+//
+// As a special exception, the copyright holders of this library give you
+// permission to link this library with independent modules to produce an
+// executable, regardless of the license terms of these independent
+// modules, and to copy and distribute the resulting executable under
+// terms of your choice, provided that you also meet, for each linked
+// independent module, the terms and conditions of the license of that
+// module. An independent module is a module which is not derived from
+// or based on this library. If you modify this library, you may extend
+// this exception to your version of the library, but you are not
+// obligated to do so. If you do not wish to do so, delete this
+// exception statement from your version.
+//
+
+#if !NETCF_1_0
+
+using System;
+using System.Security.Cryptography;
+
+namespace ICSharpCode.SharpZipLib.Encryption {
+
+ /// <summary>
+ /// Transforms stream using AES in CTR mode
+ /// </summary>
+ public class ZipAESTransform : ICryptoTransform {
+
+ public const int PWD_VER_LENGTH = 2;
+
+ // WinZip use iteration count of 1000 for PBKDF2 key generation
+ private const int KEY_ROUNDS = 1000;
+
+ // For 128-bit AES (16 bytes) the encryption is implemented as expected.
+ // For 256-bit AES (32 bytes) WinZip do full 256 bit AES of the nonce to create the encryption
+ // block but use only the first 16 bytes of it, and discard the second half.
+ private const int ENCRYPT_BLOCK = 16;
+
+ private int _blockSize;
+ private ICryptoTransform _encryptor;
+ private readonly byte[] _counterNonce;
+ private byte[] _encryptBuffer;
+ private int _encrPos;
+ private byte[] _pwdVerifier;
+ private HMACSHA1 _hmacsha1;
+ private bool _finalised;
+
+ private bool _writeMode;
+
+ /// <summary>
+ /// Constructor.
+ /// </summary>
+ /// <param name="key">Password string</param>
+ /// <param name="saltBytes">Random bytes, length depends on encryption strength.
+ /// 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes.</param>
+ /// <param name="blockSize">The encryption strength, in bytes eg 16 for 128 bits.</param>
+ /// <param name="writeMode">True when creating a zip, false when reading. For the AuthCode.</param>
+ ///
+ public ZipAESTransform(string key, byte[] saltBytes, int blockSize, bool writeMode) {
+
+ if (blockSize != 16 && blockSize != 32) // 24 valid for AES but not supported by Winzip
+ throw new Exception("Invalid blocksize " + blockSize + ". Must be 16 or 32.");
+ if (saltBytes.Length != blockSize / 2)
+ throw new Exception("Invalid salt len. Must be " + blockSize / 2 + " for blocksize " + blockSize);
+ // initialise the encryption buffer and buffer pos
+ _blockSize = blockSize;
+ _encryptBuffer = new byte[_blockSize];
+ _encrPos = ENCRYPT_BLOCK;
+
+ // Needs .NET Framework version 2.0
+ // Performs the equivalent of derive_key in Dr Brian Gladman's pwd2key.c
+ Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(key, saltBytes, KEY_ROUNDS);
+ RijndaelManaged rm = new RijndaelManaged();
+ rm.Mode = CipherMode.ECB; // No feedback from cipher for CTR mode
+ _counterNonce = new byte[_blockSize];
+ byte[] byteKey1 = pdb.GetBytes(_blockSize);
+ byte[] byteKey2 = pdb.GetBytes(_blockSize);
+ _encryptor = rm.CreateEncryptor(byteKey1, byteKey2);
+ _pwdVerifier = pdb.GetBytes(PWD_VER_LENGTH);
+ //
+ _hmacsha1 = new HMACSHA1(byteKey2);
+ _writeMode = writeMode;
+ }
+
+ public void Dispose() {
+ _encryptor.Dispose();
+ }
+
+ /// <summary>
+ /// Implement the ICryptoTransform method.
+ /// </summary>
+ public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) {
+
+ // Pass the data stream to the hash algorithm for generating the Auth Code.
+ // This does not change the inputBuffer. Do this before decryption for read mode.
+ if (!_writeMode) {
+ _hmacsha1.TransformBlock(inputBuffer, inputOffset, inputCount, inputBuffer, inputOffset);
+ }
+ // Encrypt with AES in CTR mode. Regards to Dr Brian Gladman for this.
+ int ix = 0;
+ while (ix < inputCount) {
+ if (_encrPos == ENCRYPT_BLOCK) {
+ /* increment encryption nonce */
+ int j = 0;
+ while (++_counterNonce[j] == 0) {
+ ++j;
+ }
+ /* encrypt the nonce to form next xor buffer */
+ _encryptor.TransformBlock(_counterNonce, 0, _blockSize, _encryptBuffer, 0);
+ _encrPos = 0;
+ }
+ outputBuffer[ix + outputOffset] = (byte)(inputBuffer[ix + inputOffset] ^ _encryptBuffer[_encrPos++]);
+ //
+ ix++;
+ }
+ if (_writeMode) {
+ // This does not change the buffer.
+ _hmacsha1.TransformBlock(outputBuffer, outputOffset, inputCount, outputBuffer, outputOffset);
+ }
+ return inputCount;
+ }
+
+ public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) {
+
+ throw new NotImplementedException("ZipAESTransform.TransformFinalBlock");
+ }
+
+ public int InputBlockSize {
+ get {
+ return _blockSize;
+ }
+ }
+
+ public int OutputBlockSize {
+ get {
+ return _blockSize;
+ }
+ }
+
+ public bool CanTransformMultipleBlocks {
+ get {
+ return true;
+ }
+ }
+
+ public bool CanReuseTransform {
+ get {
+ return true;
+ }
+ }
+
+ /// <summary>
+ /// Returns the 2 byte password verifier
+ /// </summary>
+ public byte[] PwdVerifier {
+ get {
+ return _pwdVerifier;
+ }
+ }
+
+ /// <summary>
+ /// Returns the 10 byte AUTH CODE to be checked or appended immediately following the AES data stream.
+ /// </summary>
+ public byte[] GetAuthCode() {
+ // We usually don't get advance notice of final block. Hash requres a TransformFinal.
+ if (!_finalised) {
+ byte[] dummy = new byte[0];
+ _hmacsha1.TransformFinalBlock(dummy, 0, 0);
+ _finalised = true;
+ }
+ return _hmacsha1.Hash;
+ }
+ }
+}
+#endif
View
2  src/ICSharpCode.SharpZLib.csproj
@@ -50,6 +50,8 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Core\WindowsPathUtils.cs" />
+ <Compile Include="Encryption\ZipAESStream.cs" />
+ <Compile Include="Encryption\ZipAESTransform.cs" />
<Compile Include="Main.cs" />
<Compile Include="AssemblyInfo.cs" />
<Compile Include="Checksums\IChecksum.cs" />
View
38 src/Zip/Compression/Streams/DeflaterOutputStream.cs
@@ -36,6 +36,9 @@
// obligated to do so. If you do not wish to do so, delete this
// exception statement from your version.
+// HISTORY
+// 22-12-2009 DavidPierson Added AES support
+
using System;
using System.IO;
@@ -165,6 +168,9 @@ public virtual void Finish()
}
#else
if (cryptoTransform_ != null) {
+ if (cryptoTransform_ is ZipAESTransform) {
+ AESAuthCode_ = ((ZipAESTransform)cryptoTransform_).GetAuthCode();
+ }
cryptoTransform_.Dispose();
cryptoTransform_ = null;
}
@@ -200,6 +206,7 @@ public bool IsStreamOwner
uint[] keys;
#else
ICryptoTransform cryptoTransform_;
+ protected byte[] AESAuthCode_;
#endif
/// <summary>
@@ -270,6 +277,26 @@ protected void InitializePassword(string password)
#endif
}
+ /// <summary>
+ /// Initializes encryption keys based on given password.
+ /// </summary>
+ protected void InitializeAESPassword(ZipEntry entry, string rawPassword,
+ out byte[] salt, out byte[] pwdVerifier) {
+#if NETCF_1_0
+ // not implemented
+#else
+ salt = new byte[entry.AESSaltLen];
+ // Salt needs to be cryptographically random, and unique per file
+ if (_aesRnd == null)
+ _aesRnd = new RNGCryptoServiceProvider();
+ _aesRnd.GetBytes(salt);
+ int blockSize = entry.AESKeySize / 8; // bits to bytes
+
+ cryptoTransform_ = new ZipAESTransform(rawPassword, salt, blockSize, true);
+ pwdVerifier = ((ZipAESTransform)cryptoTransform_).PwdVerifier;
+#endif
+ }
+
#if NETCF_1_0
/// <summary>
@@ -484,6 +511,9 @@ public override void Close()
keys=null;
#else
if ( cryptoTransform_ != null ) {
+ if (cryptoTransform_ is ZipAESTransform) {
+ AESAuthCode_ = ((ZipAESTransform)cryptoTransform_).GetAuthCode();
+ }
cryptoTransform_.Dispose();
cryptoTransform_ = null;
}
@@ -550,5 +580,13 @@ public override void Write(byte[] buffer, int offset, int count)
bool isStreamOwner_ = true;
#endregion
+
+ #region Static Fields
+
+#if ! NETCF_1_0
+ // Static to help ensure that multiple files within a zip will get different random salt
+ private static RNGCryptoServiceProvider _aesRnd;
+#endif
+ #endregion
}
}
View
16 src/Zip/ZipConstants.cs
@@ -37,6 +37,9 @@
// obligated to do so. If you do not wish to do so, delete this
// exception statement from your version.
+// HISTORY
+// 22-12-2009 DavidPierson Added AES support
+
using System;
using System.Text;
using System.Threading;
@@ -97,7 +100,7 @@ public enum CompressionMethod
BZip2 = 11,
/// <summary>
- /// WinZip special for AES encryption, Not supported by #Zip.
+ /// WinZip special for AES encryption, Now supported by #Zip.
/// </summary>
WinZipAES = 99,
@@ -255,7 +258,7 @@ public sealed class ZipConstants
/// This is also the Zip version for the library when comparing against the version required to extract
/// for an entry. See <see cref="ZipEntry.CanDecompress"/>.
/// </remarks>
- public const int VersionMadeBy = 45;
+ public const int VersionMadeBy = 51; // was 45 before AES
/// <summary>
/// The version made by field for entries in the central header when created by this library
@@ -265,7 +268,7 @@ public sealed class ZipConstants
/// for an entry. See <see cref="ZipInputStream.CanDecompressEntry">ZipInputStream.CanDecompressEntry</see>.
/// </remarks>
[Obsolete("Use VersionMadeBy instead")]
- public const int VERSION_MADE_BY = 45;
+ public const int VERSION_MADE_BY = 51;
/// <summary>
/// The minimum version required to support strong encryption
@@ -277,7 +280,12 @@ public sealed class ZipConstants
/// </summary>
[Obsolete("Use VersionStrongEncryption instead")]
public const int VERSION_STRONG_ENCRYPTION = 50;
-
+
+ /// <summary>
+ /// Version indicating AES encryption
+ /// </summary>
+ public const int VERSION_AES = 51;
+
/// <summary>
/// The version required for Zip64 extensions (4.5 or higher)
/// </summary>
View
118 src/Zip/ZipEntry.cs
@@ -37,6 +37,9 @@
// obligated to do so. If you do not wish to do so, delete this
// exception statement from your version.
+// HISTORY
+// 22-12-2009 DavidPierson Added AES support
+
using System;
using System.IO;
@@ -558,7 +561,10 @@ public int Version
}
else {
int result = 10;
- if ( CentralHeaderRequiresZip64 ) {
+ if (AESKeySize > 0) {
+ result = ZipConstants.VERSION_AES; // Ver 5.1 = AES
+ }
+ else if (CentralHeaderRequiresZip64) {
result = ZipConstants.VersionZip64;
}
else if (CompressionMethod.Deflated == method) {
@@ -579,7 +585,7 @@ public int Version
}
/// <summary>
- /// Get a value indicating wether this entry can be decompressed by the library.
+ /// Get a value indicating whether this entry can be decompressed by the library.
/// </summary>
/// <remarks>This is based on the <see cref="Version"></see> and
/// wether the <see cref="IsCompressionMethodSupported()">compression method</see> is supported.</remarks>
@@ -590,7 +596,8 @@ public bool CanDecompress
((Version == 10) ||
(Version == 11) ||
(Version == 20) ||
- (Version == 45)) &&
+ (Version == 45) ||
+ (Version == 51)) &&
IsCompressionMethodSupported();
}
}
@@ -823,7 +830,18 @@ public long Crc
this.method = value;
}
}
-
+
+ /// <summary>
+ /// Gets the compression method for outputting to the local or central header.
+ /// Returns same value as CompressionMethod except when AES encrypting, which
+ /// places 99 in the method and places the real method in the extra data.
+ /// </summary>
+ public CompressionMethod CompressionMethodForHeader {
+ get {
+ return (AESKeySize > 0) ? CompressionMethod.WinZipAES : method;
+ }
+ }
+
/// <summary>
/// Gets/Sets the extra data.
/// </summary>
@@ -855,7 +873,67 @@ public long Crc
}
}
}
-
+
+ /// <summary>
+ /// For AES encrypted files returns or sets the number of bits of encryption (128, 192 or 256).
+ /// When setting, only 0 (off), 128 or 256 is supported.
+ /// </summary>
+ public int AESKeySize {
+ get {
+ // the strength (1 or 3) is in the entry header
+ switch (_aesEncryptionStrength) {
+ case 0: return 0; // Not AES
+ case 1: return 128;
+ case 2: return 192; // Not used by WinZip
+ case 3: return 256;
+ default: throw new ZipException("Invalid AESEncryptionStrength " + _aesEncryptionStrength);
+ }
+ }
+ set {
+ switch (value) {
+ case 0: _aesEncryptionStrength = 0; break;
+ case 128: _aesEncryptionStrength = 1; break;
+ case 256: _aesEncryptionStrength = 3; break;
+ default: throw new ZipException("AESKeySize must be 0, 128 or 256: " + value);
+ }
+ }
+ }
+
+ /// <summary>
+ /// AES Encryption strength for storage in extra data in entry header.
+ /// 1 is 128 bit, 2 is 192 bit, 3 is 256 bit.
+ /// </summary>
+ public byte AESEncryptionStrength {
+ get {
+ return (byte)_aesEncryptionStrength;
+ }
+ }
+
+ /// <summary>
+ /// Returns the length of the salt, in bytes
+ /// </summary>
+ public int AESSaltLen {
+ get {
+ // Key size -> Salt length: 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes.
+ return AESKeySize / 16;
+ }
+ }
+
+ /// <summary>
+ /// Number of extra bytes required to hold the AES Header fields (Salt, Pwd verify, AuthCode)
+ /// </summary>
+ public int AESOverheadSize {
+ get {
+ // File format:
+ // Bytes Content
+ // Variable Salt value
+ // 2 Password verification value
+ // Variable Encrypted file data
+ // 10 Authentication code
+ return 12 + AESSaltLen;
+ }
+ }
+
/// <summary>
/// Process extra data fields updating the entry based on the contents.
/// </summary>
@@ -940,6 +1018,34 @@ internal void ProcessExtraData(bool localHeader)
new TimeSpan ( 0, 0, 0, iTime, 0 )).ToLocalTime();
}
}
+ if (method == CompressionMethod.WinZipAES) {
+ ProcessAESExtraData(extraData);
+ }
+ }
+
+ // For AES the method in the entry is 99, and the real compression method is in the extradata
+ //
+ private void ProcessAESExtraData(ZipExtraData extraData) {
+
+ if (extraData.Find(0x9901)) {
+ // Set version and flag for Zipfile.CreateAndInitDecryptionStream
+ versionToExtract = ZipConstants.VERSION_AES; // Ver 5.1 = AES see "Version" getter
+ // Set StrongEncryption flag for ZipFile.CreateAndInitDecryptionStream
+ Flags = Flags | (int)GeneralBitFlags.StrongEncryption;
+ //
+ // Unpack AES extra data field see http://www.winzip.com/aes_info.htm
+ int length = extraData.ValueLength; // Data size currently 7
+ if (length < 7)
+ throw new ZipException("AES Extra Data Length " + length + " invalid.");
+ int ver = extraData.ReadShort(); // Version number (1=AE-1 2=AE-2)
+ int vendorId = extraData.ReadShort(); // 2-character vendor ID 0x4541 = "AE"
+ int encrStrength = extraData.ReadByte(); // encryption strength 1 = 128 2 = 192 3 = 256
+ int actualCompress = extraData.ReadShort(); // The actual compression method used to compress the file
+ _aesVer = ver;
+ _aesEncryptionStrength = encrStrength;
+ method = (CompressionMethod)actualCompress;
+ } else
+ throw new ZipException("AES Extra Data missing");
}
/// <summary>
@@ -1124,6 +1230,8 @@ public static string CleanName(string name)
bool forceZip64_;
byte cryptoCheckValue_;
+ int _aesVer; // Version number (2 = AE-2 ?). Assigned but not used.
+ int _aesEncryptionStrength; // Encryption strength 1 = 128 2 = 192 3 = 256
#endregion
}
}
View
31 src/Zip/ZipFile.cs
@@ -37,6 +37,9 @@
// obligated to do so. If you do not wish to do so, delete this
// exception statement from your version.
+// HISTORY
+// 22-12-2009 DavidPierson Added AES support
+
using System;
using System.Collections;
using System.IO;
@@ -386,6 +389,7 @@ public string Password
key = null;
}
else {
+ rawPassword_ = value;
key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(value));
}
}
@@ -3277,7 +3281,31 @@ Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry)
CheckClassicPassword(result, entry);
}
else {
- throw new ZipException("Decryption method not supported");
+ if (entry.Version == ZipConstants.VERSION_AES) {
+ //
+ OnKeysRequired(entry.Name);
+ if (HaveKeys == false) {
+ throw new ZipException("No password available for AES encrypted stream");
+ }
+ int saltLen = entry.AESSaltLen;
+ byte[] saltBytes = new byte[saltLen];
+ int saltIn = baseStream.Read(saltBytes, 0, saltLen);
+ if (saltIn != saltLen)
+ throw new ZipException("AES Salt expected " + saltLen + " got " + saltIn);
+ //
+ byte[] pwdVerifyRead = new byte[2];
+ baseStream.Read(pwdVerifyRead, 0, 2);
+ int blockSize = entry.AESKeySize / 8; // bits to bytes
+
+ ZipAESTransform decryptor = new ZipAESTransform(rawPassword_, saltBytes, blockSize, false);
+ byte[] pwdVerifyCalc = decryptor.PwdVerifier;
+ if (pwdVerifyCalc[0] != pwdVerifyRead[0] || pwdVerifyCalc[1] != pwdVerifyRead[1])
+ throw new Exception("Invalid password for AES");
+ result = new ZipAESStream(baseStream, decryptor, CryptoStreamMode.Read);
+ }
+ else {
+ throw new ZipException("Decryption method not supported");
+ }
}
return result;
@@ -3335,6 +3363,7 @@ static void WriteEncryptionHeader(Stream stream, long crcValue)
bool isDisposed_;
string name_;
string comment_;
+ string rawPassword_;
Stream baseStream_;
bool isStreamOwner;
long offsetOfFirstEntry;
View
89 src/Zip/ZipOutputStream.cs
@@ -37,6 +37,9 @@
// obligated to do so. If you do not wish to do so, delete this
// exception statement from your version.
+// HISTORY
+// 22-12-2009 DavidPierson Added AES support
+
using System;
using System.IO;
using System.Collections;
@@ -335,7 +338,7 @@ public void PutNextEntry(ZipEntry entry)
WriteLeShort(entry.Version);
WriteLeShort(entry.Flags);
- WriteLeShort((byte)method);
+ WriteLeShort((byte)entry.CompressionMethodForHeader);
WriteLeInt((int)entry.DosTime);
// TODO: Refactor header writing. Its done in several places.
@@ -401,7 +404,11 @@ public void PutNextEntry(ZipEntry entry)
else {
ed.Delete(1);
}
-
+
+ if (entry.AESKeySize > 0) {
+ AddExtraDataAES(entry, ed);
+ }
+
byte[] extra = ed.GetEntryData();
WriteLeShort(name.Length);
@@ -420,6 +427,9 @@ public void PutNextEntry(ZipEntry entry)
}
offset += ZipConstants.LocalHeaderBaseSize + name.Length + extra.Length;
+ // Fix offsetOfCentraldir for AES
+ if (entry.AESKeySize > 0)
+ offset += entry.AESOverheadSize;
// Activate the entry.
curEntry = entry;
@@ -429,12 +439,17 @@ public void PutNextEntry(ZipEntry entry)
deflater_.SetLevel(compressionLevel);
}
size = 0;
-
- if (entry.IsCrypted) {
- if (entry.Crc < 0) { // so testing Zip will says its ok
- WriteEncryptionHeader(entry.DosTime << 16);
+
+ if (entry.IsCrypted == true) {
+ // DPI
+ if (entry.AESKeySize > 0) {
+ WriteAESHeader(entry);
} else {
- WriteEncryptionHeader(entry.Crc);
+ if (entry.Crc < 0) { // so testing Zip will says its ok
+ WriteEncryptionHeader(entry.DosTime << 16);
+ } else {
+ WriteEncryptionHeader(entry.Crc);
+ }
}
}
}
@@ -466,7 +481,12 @@ public void CloseEntry()
deflater_.Reset();
}
}
-
+
+ // Write the AES Authentication Code (a hash of the compressed and encrypted data)
+ if (curEntry.AESKeySize > 0) {
+ baseOutputStream_.Write(AESAuthCode_, 0, 10);
+ }
+
if (curEntry.Size < 0) {
curEntry.Size = size;
} else if (curEntry.Size != size) {
@@ -488,7 +508,12 @@ public void CloseEntry()
offset += csize;
if (curEntry.IsCrypted) {
- curEntry.CompressedSize += ZipConstants.CryptoHeaderSize;
+ if (curEntry.AESKeySize > 0) {
+ curEntry.CompressedSize += curEntry.AESOverheadSize;
+
+ } else {
+ curEntry.CompressedSize += ZipConstants.CryptoHeaderSize;
+ }
}
// Patch the header if possible
@@ -551,7 +576,45 @@ void WriteEncryptionHeader(long crcValue)
EncryptBlock(cryptBuffer, 0, cryptBuffer.Length);
baseOutputStream_.Write(cryptBuffer, 0, cryptBuffer.Length);
}
-
+
+ private static void AddExtraDataAES(ZipEntry entry, ZipExtraData extraData) {
+
+ // Vendor Version: AE-1 IS 1. AE-2 is 2. With AE-2 no CRC is required and 0 is stored.
+ const int VENDOR_VERSION = 2;
+ // Vendor ID is the two ASCII characters "AE".
+ const int VENDOR_ID = 0x4541; //not 6965;
+ extraData.StartNewEntry();
+ // Pack AES extra data field see http://www.winzip.com/aes_info.htm
+ //extraData.AddLeShort(7); // Data size (currently 7)
+ extraData.AddLeShort(VENDOR_VERSION); // 2 = AE-2
+ extraData.AddLeShort(VENDOR_ID); // "AE"
+ extraData.AddData(entry.AESEncryptionStrength); // 1 = 128, 2 = 192, 3 = 256
+ extraData.AddLeShort((int)entry.CompressionMethod); // The actual compression method used to compress the file
+ extraData.AddNewEntry(0x9901);
+ }
+
+ // Replaces WriteEncryptionHeader for AES
+ //
+ private void WriteAESHeader(ZipEntry entry) {
+ byte[] salt;
+ byte[] pwdVerifier;
+ InitializeAESPassword(entry, Password, out salt, out pwdVerifier);
+ // File format for AES:
+ // Size (bytes) Content
+ // ------------ -------
+ // Variable Salt value
+ // 2 Password verification value
+ // Variable Encrypted file data
+ // 10 Authentication code
+ //
+ // Value in the "compressed size" fields of the local file header and the central directory entry
+ // is the total size of all the items listed above. In other words, it is the total size of the
+ // salt value, password verification value, encrypted data, and authentication code.
+ baseOutputStream_.Write(salt, 0, salt.Length);
+ baseOutputStream_.Write(pwdVerifier, 0, pwdVerifier.Length);
+
+ }
+
/// <summary>
/// Writes the given buffer to the current entry.
/// </summary>
@@ -655,7 +718,7 @@ public override void Finish()
WriteLeShort(ZipConstants.VersionMadeBy);
WriteLeShort(entry.Version);
WriteLeShort(entry.Flags);
- WriteLeShort((short)entry.CompressionMethod);
+ WriteLeShort((short)entry.CompressionMethodForHeader);
WriteLeInt((int)entry.DosTime);
WriteLeInt((int)entry.Crc);
@@ -710,6 +773,10 @@ public override void Finish()
ed.Delete(1);
}
+ if (entry.AESKeySize > 0) {
+ AddExtraDataAES(entry, ed);
+ }
+
byte[] extra = ed.GetEntryData();
byte[] entryComment =
Please sign in to comment.
Something went wrong with that request. Please try again.