Skip to content

Commit f9ccb5a

Browse files
author
David Pierson
committed
Added AES to ZipFile and ZipOutputStream
1 parent 5f464df commit f9ccb5a

8 files changed

Lines changed: 639 additions & 21 deletions

File tree

src/Encryption/ZipAESStream.cs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
//
2+
// ZipAESStream.cs
3+
//
4+
// Copyright 2009 David Pierson
5+
//
6+
// This program is free software; you can redistribute it and/or
7+
// modify it under the terms of the GNU General Public License
8+
// as published by the Free Software Foundation; either version 2
9+
// of the License, or (at your option) any later version.
10+
//
11+
// This program is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU General Public License for more details.
15+
//
16+
// You should have received a copy of the GNU General Public License
17+
// along with this program; if not, write to the Free Software
18+
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19+
//
20+
// Linking this library statically or dynamically with other modules is
21+
// making a combined work based on this library. Thus, the terms and
22+
// conditions of the GNU General Public License cover the whole
23+
// combination.
24+
//
25+
// As a special exception, the copyright holders of this library give you
26+
// permission to link this library with independent modules to produce an
27+
// executable, regardless of the license terms of these independent
28+
// modules, and to copy and distribute the resulting executable under
29+
// terms of your choice, provided that you also meet, for each linked
30+
// independent module, the terms and conditions of the license of that
31+
// module. An independent module is a module which is not derived from
32+
// or based on this library. If you modify this library, you may extend
33+
// this exception to your version of the library, but you are not
34+
// obligated to do so. If you do not wish to do so, delete this
35+
// exception statement from your version.
36+
//
37+
38+
#if !NETCF_1_0
39+
40+
using System;
41+
using System.IO;
42+
using System.Security.Cryptography;
43+
44+
namespace ICSharpCode.SharpZipLib.Encryption {
45+
46+
// Based on information from http://www.winzip.com/aes_info.htm
47+
// and http://www.gladman.me.uk/cryptography_technology/fileencrypt/
48+
49+
/// <summary>
50+
/// Encrypts and decrypts AES ZIP
51+
/// </summary>
52+
public class ZipAESStream : CryptoStream {
53+
54+
/// <summary>
55+
/// Constructor
56+
/// </summary>
57+
/// <param name="stream">The stream on which to perform the cryptographic transformation.</param>
58+
/// <param name="transform">Instance of ZipAESTransform</param>
59+
/// <param name="mode">Read or Write</param>
60+
public ZipAESStream(Stream stream, ZipAESTransform transform, CryptoStreamMode mode)
61+
: base(stream, transform, mode) {
62+
63+
_stream = stream;
64+
_transform = transform;
65+
_slideBuffer = new byte[1024];
66+
67+
_blockAndAuth = CRYPTO_BLOCK_SIZE + AUTH_CODE_LENGTH;
68+
69+
// mode:
70+
// CryptoStreamMode.Read means we read from "stream" and pass decrypted to our Read() method.
71+
// Write bypasses this stream and uses the Transform directly.
72+
if (mode != CryptoStreamMode.Read) {
73+
throw new Exception("ZipAESStream only for read");
74+
}
75+
}
76+
77+
// The final n bytes of the AES stream contain the Auth Code.
78+
private const int AUTH_CODE_LENGTH = 10;
79+
80+
private Stream _stream;
81+
private ZipAESTransform _transform;
82+
private byte[] _slideBuffer;
83+
private int _slideBufStartPos;
84+
private int _slideBufFreePos;
85+
// Blocksize is always 16 here, even for AES-256 which has transform.InputBlockSize of 32.
86+
private const int CRYPTO_BLOCK_SIZE = 16;
87+
private int _blockAndAuth;
88+
89+
/// <summary>
90+
/// Reads a sequence of bytes from the current CryptoStream into buffer,
91+
/// and advances the position within the stream by the number of bytes read.
92+
/// </summary>
93+
public override int Read(byte[] outBuffer, int offset, int count) {
94+
int nBytes = 0;
95+
while (nBytes < count) {
96+
// Calculate buffer quantities vs read-ahead size, and check for sufficient free space
97+
int byteCount = _slideBufFreePos - _slideBufStartPos;
98+
99+
// Need to handle final block and Auth Code specially, but don't know total data length.
100+
// Maintain a read-ahead equal to the length of (crypto block + Auth Code).
101+
// When that runs out we can detect these final sections.
102+
int lengthToRead = _blockAndAuth - byteCount;
103+
if (_slideBuffer.Length - _slideBufFreePos < lengthToRead) {
104+
// Shift the data to the beginning of the buffer
105+
int iTo = 0;
106+
for (int iFrom = _slideBufStartPos; iFrom < _slideBufFreePos; iFrom++, iTo++) {
107+
_slideBuffer[iTo] = _slideBuffer[iFrom];
108+
}
109+
_slideBufFreePos -= _slideBufStartPos; // Note the -=
110+
_slideBufStartPos = 0;
111+
}
112+
int obtained = _stream.Read(_slideBuffer, _slideBufFreePos, lengthToRead);
113+
_slideBufFreePos += obtained;
114+
115+
// Recalculate how much data we now have
116+
byteCount = _slideBufFreePos - _slideBufStartPos;
117+
if (byteCount >= _blockAndAuth) {
118+
// At least a 16 byte block and an auth code remains.
119+
_transform.TransformBlock(_slideBuffer,
120+
_slideBufStartPos,
121+
CRYPTO_BLOCK_SIZE,
122+
outBuffer,
123+
offset);
124+
nBytes += CRYPTO_BLOCK_SIZE;
125+
offset += CRYPTO_BLOCK_SIZE;
126+
_slideBufStartPos += CRYPTO_BLOCK_SIZE;
127+
} else {
128+
// Last round.
129+
if (byteCount > AUTH_CODE_LENGTH) {
130+
// At least one byte of data plus auth code
131+
int finalBlock = byteCount - AUTH_CODE_LENGTH;
132+
_transform.TransformBlock(_slideBuffer,
133+
_slideBufStartPos,
134+
finalBlock,
135+
outBuffer,
136+
offset);
137+
138+
nBytes += finalBlock;
139+
_slideBufStartPos += finalBlock;
140+
}
141+
else if (byteCount < AUTH_CODE_LENGTH)
142+
throw new Exception("Internal error missed auth code"); // Coding bug
143+
// Final block done. Check Auth code.
144+
byte[] calcAuthCode = _transform.GetAuthCode();
145+
for (int i = 0; i < AUTH_CODE_LENGTH; i++) {
146+
if (calcAuthCode[i] != _slideBuffer[_slideBufStartPos + i]) {
147+
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"
148+
+ "The file may be damaged.");
149+
}
150+
}
151+
152+
break; // Reached the auth code
153+
}
154+
}
155+
return nBytes;
156+
}
157+
158+
/// <summary>
159+
/// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
160+
/// </summary>
161+
/// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream. </param>
162+
/// <param name="offset">The byte offset in buffer at which to begin copying bytes to the current stream. </param>
163+
/// <param name="count">The number of bytes to be written to the current stream. </param>
164+
public override void Write(byte[] buffer, int offset, int count) {
165+
// ZipAESStream is used for reading but not for writing. Writing uses the ZipAESTransform directly.
166+
throw new NotImplementedException();
167+
}
168+
}
169+
}
170+
#endif

src/Encryption/ZipAESTransform.cs

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
//
2+
// ZipAESTransform.cs
3+
//
4+
// Copyright 2009 David Pierson
5+
//
6+
// This program is free software; you can redistribute it and/or
7+
// modify it under the terms of the GNU General Public License
8+
// as published by the Free Software Foundation; either version 2
9+
// of the License, or (at your option) any later version.
10+
//
11+
// This program is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU General Public License for more details.
15+
//
16+
// You should have received a copy of the GNU General Public License
17+
// along with this program; if not, write to the Free Software
18+
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19+
//
20+
// Linking this library statically or dynamically with other modules is
21+
// making a combined work based on this library. Thus, the terms and
22+
// conditions of the GNU General Public License cover the whole
23+
// combination.
24+
//
25+
// As a special exception, the copyright holders of this library give you
26+
// permission to link this library with independent modules to produce an
27+
// executable, regardless of the license terms of these independent
28+
// modules, and to copy and distribute the resulting executable under
29+
// terms of your choice, provided that you also meet, for each linked
30+
// independent module, the terms and conditions of the license of that
31+
// module. An independent module is a module which is not derived from
32+
// or based on this library. If you modify this library, you may extend
33+
// this exception to your version of the library, but you are not
34+
// obligated to do so. If you do not wish to do so, delete this
35+
// exception statement from your version.
36+
//
37+
38+
#if !NETCF_1_0
39+
40+
using System;
41+
using System.Security.Cryptography;
42+
43+
namespace ICSharpCode.SharpZipLib.Encryption {
44+
45+
/// <summary>
46+
/// Transforms stream using AES in CTR mode
47+
/// </summary>
48+
public class ZipAESTransform : ICryptoTransform {
49+
50+
public const int PWD_VER_LENGTH = 2;
51+
52+
// WinZip use iteration count of 1000 for PBKDF2 key generation
53+
private const int KEY_ROUNDS = 1000;
54+
55+
// For 128-bit AES (16 bytes) the encryption is implemented as expected.
56+
// For 256-bit AES (32 bytes) WinZip do full 256 bit AES of the nonce to create the encryption
57+
// block but use only the first 16 bytes of it, and discard the second half.
58+
private const int ENCRYPT_BLOCK = 16;
59+
60+
private int _blockSize;
61+
private ICryptoTransform _encryptor;
62+
private readonly byte[] _counterNonce;
63+
private byte[] _encryptBuffer;
64+
private int _encrPos;
65+
private byte[] _pwdVerifier;
66+
private HMACSHA1 _hmacsha1;
67+
private bool _finalised;
68+
69+
private bool _writeMode;
70+
71+
/// <summary>
72+
/// Constructor.
73+
/// </summary>
74+
/// <param name="key">Password string</param>
75+
/// <param name="saltBytes">Random bytes, length depends on encryption strength.
76+
/// 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes.</param>
77+
/// <param name="blockSize">The encryption strength, in bytes eg 16 for 128 bits.</param>
78+
/// <param name="writeMode">True when creating a zip, false when reading. For the AuthCode.</param>
79+
///
80+
public ZipAESTransform(string key, byte[] saltBytes, int blockSize, bool writeMode) {
81+
82+
if (blockSize != 16 && blockSize != 32) // 24 valid for AES but not supported by Winzip
83+
throw new Exception("Invalid blocksize " + blockSize + ". Must be 16 or 32.");
84+
if (saltBytes.Length != blockSize / 2)
85+
throw new Exception("Invalid salt len. Must be " + blockSize / 2 + " for blocksize " + blockSize);
86+
// initialise the encryption buffer and buffer pos
87+
_blockSize = blockSize;
88+
_encryptBuffer = new byte[_blockSize];
89+
_encrPos = ENCRYPT_BLOCK;
90+
91+
// Needs .NET Framework version 2.0
92+
// Performs the equivalent of derive_key in Dr Brian Gladman's pwd2key.c
93+
Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(key, saltBytes, KEY_ROUNDS);
94+
RijndaelManaged rm = new RijndaelManaged();
95+
rm.Mode = CipherMode.ECB; // No feedback from cipher for CTR mode
96+
_counterNonce = new byte[_blockSize];
97+
byte[] byteKey1 = pdb.GetBytes(_blockSize);
98+
byte[] byteKey2 = pdb.GetBytes(_blockSize);
99+
_encryptor = rm.CreateEncryptor(byteKey1, byteKey2);
100+
_pwdVerifier = pdb.GetBytes(PWD_VER_LENGTH);
101+
//
102+
_hmacsha1 = new HMACSHA1(byteKey2);
103+
_writeMode = writeMode;
104+
}
105+
106+
public void Dispose() {
107+
_encryptor.Dispose();
108+
}
109+
110+
/// <summary>
111+
/// Implement the ICryptoTransform method.
112+
/// </summary>
113+
public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) {
114+
115+
// Pass the data stream to the hash algorithm for generating the Auth Code.
116+
// This does not change the inputBuffer. Do this before decryption for read mode.
117+
if (!_writeMode) {
118+
_hmacsha1.TransformBlock(inputBuffer, inputOffset, inputCount, inputBuffer, inputOffset);
119+
}
120+
// Encrypt with AES in CTR mode. Regards to Dr Brian Gladman for this.
121+
int ix = 0;
122+
while (ix < inputCount) {
123+
if (_encrPos == ENCRYPT_BLOCK) {
124+
/* increment encryption nonce */
125+
int j = 0;
126+
while (++_counterNonce[j] == 0) {
127+
++j;
128+
}
129+
/* encrypt the nonce to form next xor buffer */
130+
_encryptor.TransformBlock(_counterNonce, 0, _blockSize, _encryptBuffer, 0);
131+
_encrPos = 0;
132+
}
133+
outputBuffer[ix + outputOffset] = (byte)(inputBuffer[ix + inputOffset] ^ _encryptBuffer[_encrPos++]);
134+
//
135+
ix++;
136+
}
137+
if (_writeMode) {
138+
// This does not change the buffer.
139+
_hmacsha1.TransformBlock(outputBuffer, outputOffset, inputCount, outputBuffer, outputOffset);
140+
}
141+
return inputCount;
142+
}
143+
144+
public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) {
145+
146+
throw new NotImplementedException("ZipAESTransform.TransformFinalBlock");
147+
}
148+
149+
public int InputBlockSize {
150+
get {
151+
return _blockSize;
152+
}
153+
}
154+
155+
public int OutputBlockSize {
156+
get {
157+
return _blockSize;
158+
}
159+
}
160+
161+
public bool CanTransformMultipleBlocks {
162+
get {
163+
return true;
164+
}
165+
}
166+
167+
public bool CanReuseTransform {
168+
get {
169+
return true;
170+
}
171+
}
172+
173+
/// <summary>
174+
/// Returns the 2 byte password verifier
175+
/// </summary>
176+
public byte[] PwdVerifier {
177+
get {
178+
return _pwdVerifier;
179+
}
180+
}
181+
182+
/// <summary>
183+
/// Returns the 10 byte AUTH CODE to be checked or appended immediately following the AES data stream.
184+
/// </summary>
185+
public byte[] GetAuthCode() {
186+
// We usually don't get advance notice of final block. Hash requres a TransformFinal.
187+
if (!_finalised) {
188+
byte[] dummy = new byte[0];
189+
_hmacsha1.TransformFinalBlock(dummy, 0, 0);
190+
_finalised = true;
191+
}
192+
return _hmacsha1.Hash;
193+
}
194+
}
195+
}
196+
#endif

src/ICSharpCode.SharpZLib.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
</ItemGroup>
5151
<ItemGroup>
5252
<Compile Include="Core\WindowsPathUtils.cs" />
53+
<Compile Include="Encryption\ZipAESStream.cs" />
54+
<Compile Include="Encryption\ZipAESTransform.cs" />
5355
<Compile Include="Main.cs" />
5456
<Compile Include="AssemblyInfo.cs" />
5557
<Compile Include="Checksums\IChecksum.cs" />

0 commit comments

Comments
 (0)