ADD: Zip - extract WinZip AES encrypted archives

This commit is contained in:
Alexander Koblov 2017-11-03 19:26:09 +00:00
commit 7fdf1222ee
4 changed files with 354 additions and 25 deletions

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="9"/>
<Version Value="10"/>
<PathDelim Value="\"/>
<General>
<SessionStorage Value="InProjectDir"/>
@ -11,7 +11,7 @@
<VersionInfo>
<UseVersionInfo Value="True"/>
<MinorVersionNr Value="1"/>
<StringTable FileDescription="ZIP WCX plugin for Double Commander" LegalCopyright="Copyright (C) 2006-2017 Alexander Koblov" ProductVersion=""/>
<StringTable FileDescription="ZIP WCX plugin for Double Commander" LegalCopyright="Copyright (C) 2006-2017 Alexander Koblov"/>
</VersionInfo>
<BuildModes Count="2">
<Item1 Name="Release" Default="True"/>
@ -85,17 +85,24 @@ end;"/>
<LaunchingApplication PathPlusParams="\usr\X11R6\bin\xterm -T 'Lazarus Run Output' -e $(LazarusDir)\tools\runwait.sh $(TargetCmdLine)"/>
</local>
</RunParams>
<RequiredPackages Count="1">
<RequiredPackages Count="2">
<Item1>
<PackageName Value="dcpcrypt"/>
</Item1>
<Item2>
<PackageName Value="doublecmd_common"/>
<MinVersion Minor="2" Valid="True"/>
</Item1>
</Item2>
</RequiredPackages>
<Units Count="1">
<Units Count="2">
<Unit0>
<Filename Value="Zip.dpr"/>
<IsPartOfProject Value="True"/>
</Unit0>
<Unit1>
<Filename Value="ZipFunc.pas"/>
<IsPartOfProject Value="True"/>
</Unit1>
</Units>
</ProjectOptions>
<CompilerOptions>

View file

@ -157,6 +157,7 @@ uses
AbSWStm,
AbUnzOutStm,
AbUtils,
AbWinZipAes,
DCClassesUtf8;
{ -------------------------------------------------------------------------- }
@ -979,6 +980,9 @@ var
Tries : Integer;
CheckValue : LongInt;
DecryptStream: TAbDfDecryptStream;
FieldSize: Word;
WinZipAesField: PWinZipAesRec = nil;
AesDecryptStream: TAbWinZipAesDecryptStream;
begin
{ validate }
if (Lo(Item.VersionNeededToExtract) > Ab_ZipVersion) then
@ -1013,6 +1017,12 @@ begin
{ get decrypting stream }
if Item.IsEncrypted then begin
try
{ check WinZip AES extra field }
if Item.ExtraField.Get(Ab_WinZipAesID, Pointer(WinZipAesField), FieldSize) then
begin
{ get real compression method }
Item.CompressionMethod := TAbZipCompressionMethod(WinZipAesField.Method);
end;
{ need to decrypt }
Tries := 0;
Abort := False;
@ -1021,14 +1031,27 @@ begin
if Abort then
raise EAbUserAbort.Create;
{ check for valid password }
DecryptStream := TAbDfDecryptStream.Create(Result,
CheckValue, ZipArchive.Password);
if DecryptStream.IsValid then begin
DecryptStream.OwnsStream := True;
Result := DecryptStream;
Break;
if Assigned(WinZipAesField) then
begin
AesDecryptStream := TAbWinZipAesDecryptStream.Create(Result,
WinZipAesField, ZipArchive.Password);
if AesDecryptStream.IsValid then begin
AesDecryptStream.OwnsStream := True;
Result := AesDecryptStream;
Break;
end;
FreeAndNil(AesDecryptStream);
end
else begin
DecryptStream := TAbDfDecryptStream.Create(Result,
CheckValue, ZipArchive.Password);
if DecryptStream.IsValid then begin
DecryptStream.OwnsStream := True;
Result := DecryptStream;
Break;
end;
FreeAndNil(DecryptStream);
end;
FreeAndNil(DecryptStream);
{ prompt again }
Inc(Tries);
if (Tries > ZipArchive.PasswordRetries) then
@ -1045,6 +1068,7 @@ end;
procedure DoExtract(aZipArchive: TAbZipArchive; aItem: TAbZipItem;
aInStream, aOutStream: TStream);
var
Wrong: Boolean;
OutStream : TAbUnzipOutputStream;
begin
if aItem.UncompressedSize = 0 then
@ -1098,12 +1122,21 @@ begin
end;
{ check CRC }
if OutStream.CRC32 <> aItem.CRC32 then
if not (aInStream is TAbWinZipAesDecryptStream) then
Wrong := (OutStream.CRC32 <> aItem.CRC32)
else begin
Wrong := not TAbWinZipAesDecryptStream(aInStream).Verify;
if TAbWinZipAesDecryptStream(aInStream).ExtraField.Version = 1 then
Wrong := Wrong and (OutStream.CRC32 <> aItem.CRC32);
end;
if Wrong then
begin
if Assigned(aZipArchive.OnProcessItemFailure) then
aZipArchive.OnProcessItemFailure(aZipArchive, aItem, ptExtract,
ecAbbrevia, AbZipBadCRC)
else
raise EAbZipBadCRC.Create;
end;
finally
OutStream.Free;
end;

View file

@ -0,0 +1,199 @@
(* ***** BEGIN LICENSE BLOCK *****
*
* WinZip AES decryption stream
*
* Copyright (C) 2017 Alexander Koblov (alexx2000@mail.ru)
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* ***** END LICENSE BLOCK ***** *)
unit AbWinZipAes;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, DCPrijndael, HMAC;
const
Ab_WinZipAesID : Word = $9901;
type
{ TWinZipAesRec }
PWinZipAesRec = ^TWinZipAesRec;
TWinZipAesRec = packed record
Version: Word;
Vendor: Word;
Strength: Byte;
Method: Word;
end;
{ TAbWinZipAesDecryptStream }
TAbWinZipAesDecryptStream = class(TStream)
private
FKey : TBytes;
FOwnsStream : Boolean;
FReady : Boolean;
FStream : TStream;
FDataStream : TStream;
FPassword : AnsiString;
FContext : THMAC_Context;
FDecoder : TDCP_rijndael;
FExtraField : TWinZipAesRec;
public
constructor Create(aStream : TStream;
aExtraField: PWinZipAesRec;
const aPassword : AnsiString);
destructor Destroy; override;
function IsValid : Boolean;
function Verify : Boolean;
function Read(var aBuffer; aCount : Longint) : Longint; override;
function Seek(aOffset : Longint; aOrigin : Word) : Longint; override;
function Write(const aBuffer; aCount : Longint) : Longint; override;
property ExtraField : TWinZipAesRec read FExtraField;
property OwnsStream : Boolean read FOwnsStream write FOwnsStream;
end;
implementation
uses
AbUnzOutStm, DCPcrypt2, SHA1, Hash, kdf;
const
CTR : array[0..15] of Byte = (1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0);
const
MAC_LENGTH = 10;
PWD_VER_LENGTH = 2;
KEY_LENGTH: array[1..3] of Byte = (16, 24, 32);
SALT_LENGTH: array[1..3] of Byte = (8, 12, 16);
{ TAbWinZipAesDecryptStream }
constructor TAbWinZipAesDecryptStream.Create(aStream: TStream;
aExtraField: PWinZipAesRec; const aPassword: AnsiString);
begin
inherited Create;
FStream := aStream;
FExtraField := aExtraField^;
FPassword := aPassword;
FDecoder := TDCP_rijndael.Create(nil);
end;
destructor TAbWinZipAesDecryptStream.Destroy;
begin
FDecoder.Free;
FDataStream.Free;
if FOwnsStream then
FStream.Free;
inherited Destroy;
end;
function TAbWinZipAesDecryptStream.IsValid: Boolean;
var
F: WordRec;
Salt: AnsiString;
HashDesc: PHashDesc;
AKeyLength: Integer;
ASaltLength: Integer;
AExtraLength: Integer;
begin
// Integer mode value indicating AES encryption strength
if not FExtraField.Strength in [1..3] then Exit(False);
AKeyLength := KEY_LENGTH[FExtraField.Strength];
ASaltLength := SALT_LENGTH[FExtraField.Strength];
AExtraLength := AKeyLength * 2 + PWD_VER_LENGTH;
SetLength(FKey, AExtraLength);
HashDesc:= FindHash_by_ID(_SHA1);
// Read salt value
SetLength(Salt, ASaltLength);
FStream.Read(Salt[1], ASaltLength);
// Read password verification value
FStream.Read({%H-}F, PWD_VER_LENGTH);
pbkdf2(HashDesc, Pointer(FPassword), Length(FPassword),
Pointer(Salt), Length(Salt), 1000, FKey[0], AExtraLength);
Result := (FKey[AExtraLength - 2] = F.Lo) and (FKey[AExtraLength - 1] = F.Hi);
if Result then
begin
FReady := True;
FDecoder.Init(FKey[0], AKeyLength * 8, @CTR[0]);
// Initialize for authentication using second key part
hmac_init(FContext, HashDesc, @FKey[AKeyLength], AKeyLength);
// Create encrypted file data stream
AExtraLength := ASaltLength + PWD_VER_LENGTH + MAC_LENGTH;
FDataStream := TAbUnzipSubsetStream.Create(FStream, FStream.Size - AExtraLength);
end
else begin
FReady := False;
FStream.Seek(-(ASaltLength + PWD_VER_LENGTH), soCurrent);
end
end;
function TAbWinZipAesDecryptStream.Verify: Boolean;
var
AMac: THashDigest;
ABuffer: array[0..MAC_LENGTH - 1] of Byte;
begin
hmac_final(FContext, {%H-}AMac);
FStream.Read({%H-}ABuffer[0], MAC_LENGTH);
Result := CompareByte(ABuffer[0], AMac[0], MAC_LENGTH) = 0;
end;
function TAbWinZipAesDecryptStream.Read(var aBuffer; aCount: Longint): Longint;
begin
Assert(FReady, 'TAbWinZipAesDecryptStream.Read: the stream header has not been verified');
Result := FDataStream.Read(aBuffer, aCount);
if Result > 0 then
begin
hmac_updateXL(FContext, @aBuffer, Result);
FDecoder.DecryptCTR(aBuffer, aBuffer, Result);
end;
end;
function TAbWinZipAesDecryptStream.Seek(aOffset: Longint; aOrigin: Word): Longint;
begin
Result := FDataStream.Seek(aOffset, aOrigin);
end;
function TAbWinZipAesDecryptStream.Write(const aBuffer; aCount: Longint): Longint;
begin
Assert(False, 'TAbWinZipAesDecryptStream.Write: the stream is read-only');
Result := 0;
end;
end.

View file

@ -1713,17 +1713,18 @@ Index: AbUnzPrc.pas
AbBitBkt,
AbConst,
AbDfBase,
@@ -153,7 +156,8 @@
@@ -153,7 +156,9 @@
AbSpanSt,
AbSWStm,
AbUnzOutStm,
- AbUtils;
+ AbUtils,
+ AbWinZipAes,
+ DCClassesUtf8;
{ -------------------------------------------------------------------------- }
procedure AbReverseBits(var W : Word);
@@ -944,11 +948,30 @@
@@ -944,11 +949,30 @@
InStream.ReadBuffer(Header, SizeOf(Header));
SetLength(Properties, Header.PropSize);
InStream.ReadBuffer(Properties[0], Header.PropSize);
@ -1756,7 +1757,73 @@ Index: AbUnzPrc.pas
function ExtractPrep(ZipArchive: TAbZipArchive; Item: TAbZipItem): TStream;
var
LFH : TAbZipLocalFileHeader;
@@ -1062,6 +1085,11 @@
@@ -956,6 +980,9 @@
Tries : Integer;
CheckValue : LongInt;
DecryptStream: TAbDfDecryptStream;
+ FieldSize: Word;
+ WinZipAesField: PWinZipAesRec = nil;
+ AesDecryptStream: TAbWinZipAesDecryptStream;
begin
{ validate }
if (Lo(Item.VersionNeededToExtract) > Ab_ZipVersion) then
@@ -990,6 +1017,12 @@
{ get decrypting stream }
if Item.IsEncrypted then begin
try
+ { check WinZip AES extra field }
+ if Item.ExtraField.Get(Ab_WinZipAesID, Pointer(WinZipAesField), FieldSize) then
+ begin
+ { get real compression method }
+ Item.CompressionMethod := TAbZipCompressionMethod(WinZipAesField.Method);
+ end;
{ need to decrypt }
Tries := 0;
Abort := False;
@@ -998,14 +1031,27 @@
if Abort then
raise EAbUserAbort.Create;
{ check for valid password }
- DecryptStream := TAbDfDecryptStream.Create(Result,
- CheckValue, ZipArchive.Password);
- if DecryptStream.IsValid then begin
- DecryptStream.OwnsStream := True;
- Result := DecryptStream;
- Break;
+ if Assigned(WinZipAesField) then
+ begin
+ AesDecryptStream := TAbWinZipAesDecryptStream.Create(Result,
+ WinZipAesField, ZipArchive.Password);
+ if AesDecryptStream.IsValid then begin
+ AesDecryptStream.OwnsStream := True;
+ Result := AesDecryptStream;
+ Break;
+ end;
+ FreeAndNil(AesDecryptStream);
+ end
+ else begin
+ DecryptStream := TAbDfDecryptStream.Create(Result,
+ CheckValue, ZipArchive.Password);
+ if DecryptStream.IsValid then begin
+ DecryptStream.OwnsStream := True;
+ Result := DecryptStream;
+ Break;
+ end;
+ FreeAndNil(DecryptStream);
end;
- FreeAndNil(DecryptStream);
{ prompt again }
Inc(Tries);
if (Tries > ZipArchive.PasswordRetries) then
@@ -1022,6 +1068,7 @@
procedure DoExtract(aZipArchive: TAbZipArchive; aItem: TAbZipItem;
aInStream, aOutStream: TStream);
var
+ Wrong: Boolean;
OutStream : TAbUnzipOutputStream;
begin
if aItem.UncompressedSize = 0 then
@@ -1062,6 +1109,11 @@
DecompressWavPack(aInStream, OutStream);
end;
{$ENDIF}
@ -1768,7 +1835,30 @@ Index: AbUnzPrc.pas
cmShrunk..cmImploded: begin
DoLegacyUnzip(aZipArchive, aItem, aInStream, OutStream);
end;
@@ -1111,7 +1139,7 @@
@@ -1070,12 +1122,21 @@
end;
{ check CRC }
- if OutStream.CRC32 <> aItem.CRC32 then
+ if not (aInStream is TAbWinZipAesDecryptStream) then
+ Wrong := (OutStream.CRC32 <> aItem.CRC32)
+ else begin
+ Wrong := not TAbWinZipAesDecryptStream(aInStream).Verify;
+ if TAbWinZipAesDecryptStream(aInStream).ExtraField.Version = 1 then
+ Wrong := Wrong and (OutStream.CRC32 <> aItem.CRC32);
+ end;
+ if Wrong then
+ begin
if Assigned(aZipArchive.OnProcessItemFailure) then
aZipArchive.OnProcessItemFailure(aZipArchive, aItem, ptExtract,
ecAbbrevia, AbZipBadCRC)
else
raise EAbZipBadCRC.Create;
+ end;
finally
OutStream.Free;
end;
@@ -1111,7 +1172,7 @@
else begin
InStream := ExtractPrep(ZipArchive, Item);
try
@ -1777,15 +1867,15 @@ Index: AbUnzPrc.pas
try
try {OutStream}
DoExtract(ZipArchive, Item, InStream, OutStream);
@@ -1141,6 +1169,7 @@
@@ -1141,6 +1202,7 @@
LFH : TAbZipLocalFileHeader;
Zip64Field : PZip64LocalHeaderRec;
ZipArchive : TAbZipArchive;
+ DD : TAbZipDataDescriptor = nil;
begin
ZipArchive := TAbZipArchive(Sender);
@@ -1162,6 +1191,12 @@
@@ -1162,6 +1224,12 @@
{get the item's local file header}
ZipArchive.FStream.Seek(Item.RelativeOffset, soBeginning);
LFH.LoadFromStream(ZipArchive.FStream);
@ -1796,9 +1886,9 @@ Index: AbUnzPrc.pas
+ DD.LoadFromStream(ZipArchive.FStream);
+ end;
ZipArchive.FStream.Seek(Item.RelativeOffset, soBeginning);
{currently a single exception is raised for any LFH error}
@@ -1173,8 +1208,15 @@
@@ -1173,8 +1241,15 @@
raise EAbZipInvalidLFH.Create;
if (LFH.LastModFileDate <> Item.LastModFileDate) then
raise EAbZipInvalidLFH.Create;
@ -1816,7 +1906,7 @@ Index: AbUnzPrc.pas
if LFH.ExtraField.Get(Ab_Zip64SubfieldID, Pointer(Zip64Field), FieldSize) then begin
if (Zip64Field.CompressedSize <> Item.CompressedSize) then
raise EAbZipInvalidLFH.Create;
@@ -1181,6 +1223,13 @@
@@ -1181,6 +1256,13 @@
if (Zip64Field.UncompressedSize <> Item.UncompressedSize) then
raise EAbZipInvalidLFH.Create;
end
@ -1830,13 +1920,13 @@ Index: AbUnzPrc.pas
else begin
if (LFH.CompressedSize <> Item.CompressedSize) then
raise EAbZipInvalidLFH.Create;
@@ -1195,6 +1244,7 @@
@@ -1195,6 +1277,7 @@
finally
BitBucket.Free;
LFH.Free;
+ DD.Free;
end;
end;
Index: AbUtils.pas
===================================================================