You've already forked lazarus-ccr
fpspreadsheet: Add specialized reader for password-protected ods files. Temporarily add dependence on Abbrevia to crypto package (until FPC issue #40369 will be fixed). Add sample project for reading encrypted ods file.
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@8910 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
BIN
components/fpspreadsheet/examples/other/crypto/pwd 123.ods
Normal file
BIN
components/fpspreadsheet/examples/other/crypto/pwd 123.ods
Normal file
Binary file not shown.
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CONFIG>
|
||||
<ProjectOptions>
|
||||
<Version Value="12"/>
|
||||
<PathDelim Value="\"/>
|
||||
<General>
|
||||
<Flags>
|
||||
<MainUnitHasCreateFormStatements Value="False"/>
|
||||
<MainUnitHasTitleStatement Value="False"/>
|
||||
<MainUnitHasScaledStatement Value="False"/>
|
||||
</Flags>
|
||||
<SessionStorage Value="InProjectDir"/>
|
||||
<Title Value="read_encrypted_ods"/>
|
||||
<UseAppBundle Value="False"/>
|
||||
<ResourceType Value="res"/>
|
||||
</General>
|
||||
<BuildModes>
|
||||
<Item Name="Default" Default="True"/>
|
||||
</BuildModes>
|
||||
<PublishOptions>
|
||||
<Version Value="2"/>
|
||||
<UseFileFilters Value="True"/>
|
||||
</PublishOptions>
|
||||
<RunParams>
|
||||
<FormatVersion Value="2"/>
|
||||
</RunParams>
|
||||
<RequiredPackages>
|
||||
<Item>
|
||||
<PackageName Value="laz_fpspreadsheet_crypto"/>
|
||||
</Item>
|
||||
<Item>
|
||||
<PackageName Value="laz_fpspreadsheet"/>
|
||||
</Item>
|
||||
</RequiredPackages>
|
||||
<Units>
|
||||
<Unit>
|
||||
<Filename Value="read_encrypted_ods.lpr"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
</Unit>
|
||||
</Units>
|
||||
</ProjectOptions>
|
||||
<CompilerOptions>
|
||||
<Version Value="11"/>
|
||||
<PathDelim Value="\"/>
|
||||
<Target>
|
||||
<Filename Value="read_encrypted_ods"/>
|
||||
</Target>
|
||||
<SearchPaths>
|
||||
<IncludeFiles Value="$(ProjOutDir)"/>
|
||||
<UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
|
||||
</SearchPaths>
|
||||
<Linking>
|
||||
<Debugging>
|
||||
<DebugInfoType Value="dsDwarf3"/>
|
||||
</Debugging>
|
||||
</Linking>
|
||||
</CompilerOptions>
|
||||
<Debugging>
|
||||
<Exceptions>
|
||||
<Item>
|
||||
<Name Value="EAbort"/>
|
||||
</Item>
|
||||
<Item>
|
||||
<Name Value="ECodetoolError"/>
|
||||
</Item>
|
||||
<Item>
|
||||
<Name Value="EFOpenError"/>
|
||||
</Item>
|
||||
</Exceptions>
|
||||
</Debugging>
|
||||
</CONFIG>
|
@@ -0,0 +1,36 @@
|
||||
program read_encrypted_ods;
|
||||
|
||||
uses
|
||||
SysUtils,
|
||||
fpSpreadsheet, fpsTypes, fpsUtils, fpsOpenDocument_Crypto;
|
||||
|
||||
const
|
||||
FILENAME = 'pwd 123.ods';
|
||||
PASSWORD = '123';
|
||||
|
||||
var
|
||||
wb: TsWorkbook;
|
||||
ws: TsWorksheet;
|
||||
cell: PCell;
|
||||
fn: String;
|
||||
fmtID: TsSpreadFormatID;
|
||||
t: TDateTime;
|
||||
begin
|
||||
t := Now;
|
||||
wb := TsWorkbook.Create;
|
||||
try
|
||||
wb.ReadFromFile(FILENAME, sfidOpenDocument_Crypto, PASSWORD, []);
|
||||
ws := wb.GetFirstWorksheet;
|
||||
if ws <> nil then
|
||||
for cell in ws.Cells do
|
||||
WriteLn('cell ', GetCellString(cell^.Row, cell^.Col), ' = "', ws.ReadAsText(cell), '"');
|
||||
finally
|
||||
wb.Free;
|
||||
end;
|
||||
t := Now - t;
|
||||
WriteLn('Time to decrypt and load: ', FormatDateTime('nn:ss.zzz', t), ' seconds');
|
||||
|
||||
Write('Press ENTER to close...');
|
||||
ReadLn;
|
||||
end.
|
||||
|
@@ -14,7 +14,7 @@
|
||||
</CompilerOptions>
|
||||
<Description Value="Encryption / decryption support for FPSpreadsheet"/>
|
||||
<Version Major="1" Minor="14"/>
|
||||
<Files Count="3">
|
||||
<Files Count="4">
|
||||
<Item1>
|
||||
<Filename Value="source\crypto\xlsxdecrypter.pas"/>
|
||||
<UnitName Value="xlsxdecrypter"/>
|
||||
@@ -25,19 +25,26 @@
|
||||
</Item2>
|
||||
<Item3>
|
||||
<Filename Value="source\crypto\fpscryptoproc.pas"/>
|
||||
<UnitName Value="fpscryptoproc"/>
|
||||
<UnitName Value="fpsCryptoProc"/>
|
||||
</Item3>
|
||||
<Item4>
|
||||
<Filename Value="source\crypto\fpsopendocument_crypto.pas"/>
|
||||
<UnitName Value="fpsOpenDocument_Crypto"/>
|
||||
</Item4>
|
||||
</Files>
|
||||
<CompatibilityMode Value="True"/>
|
||||
<RequiredPkgs Count="2">
|
||||
<RequiredPkgs Count="3">
|
||||
<Item1>
|
||||
<PackageName Value="Abbrevia"/>
|
||||
</Item1>
|
||||
<Item2>
|
||||
<PackageName Value="laz_fpspreadsheet"/>
|
||||
<MaxVersion Major="1" Minor="11"/>
|
||||
<MinVersion Major="1" Minor="12" Valid="True"/>
|
||||
</Item1>
|
||||
<Item2>
|
||||
<PackageName Value="FCL"/>
|
||||
</Item2>
|
||||
<Item3>
|
||||
<PackageName Value="FCL"/>
|
||||
</Item3>
|
||||
</RequiredPkgs>
|
||||
<UsageOptions>
|
||||
<UnitPath Value="$(PkgOutDir)"/>
|
||||
|
@@ -83,11 +83,11 @@ type
|
||||
FileName: String;
|
||||
Encrypted: Boolean;
|
||||
// Checksum of encrypted data
|
||||
EncryptionData_Checksum: String;
|
||||
EncryptionData_Checksum: string; // base64
|
||||
EncryptionData_ChecksumType: String;
|
||||
// Encrypting the data with the pwd hash ("key")
|
||||
AlgorithmName: String;
|
||||
InitializationVector: RawByteString;
|
||||
InitializationVector: string; // base64
|
||||
IterationCount: Integer;
|
||||
// Start key generation
|
||||
StartKeyGenerationName: String;
|
||||
@@ -95,7 +95,7 @@ type
|
||||
// Key derivation (encrypting and salting the start key)
|
||||
KeyDerivationName: String;
|
||||
KeySize: Integer;
|
||||
Salt: RawByteString;
|
||||
Salt: string; // base64
|
||||
end;
|
||||
|
||||
{ TsSpreadOpenDocReader }
|
||||
@@ -202,12 +202,12 @@ type
|
||||
ACellNode: TDOMNode); reintroduce;
|
||||
|
||||
function Decrypt(AStream: TStream; ADecryptionInfo: TsOpenDocManifestFileEntry;
|
||||
APassword: RawByteString; ADestStream: TStream): Boolean; virtual;
|
||||
APassword: String; ADestStream: TStream; out AErrorMsg: String): Boolean; virtual;
|
||||
function SupportsDecryption: Boolean; virtual;
|
||||
function UnzipToStream(AStream: TStream; AZippedFile: String;
|
||||
ADestStream: TStream): Boolean; virtual;
|
||||
function UnzipToStream(AStream: TStream; AZippedFile: String;
|
||||
APassword: RawByteString; ADestStream: TStream): Boolean;
|
||||
APassword: RawByteString; ADestStream: TStream; out AErrorMsg: String): Boolean;
|
||||
public
|
||||
constructor Create(AWorkbook: TsBasicWorkbook); override;
|
||||
destructor Destroy; override;
|
||||
@@ -2897,6 +2897,7 @@ var
|
||||
sheet: TsWorksheet;
|
||||
sheetName: String;
|
||||
tablestyleName: String;
|
||||
err: String;
|
||||
isEncrypted: Boolean = false;
|
||||
|
||||
function CreateXMLStream: TStream;
|
||||
@@ -2939,8 +2940,10 @@ begin
|
||||
// process the styles.xml file
|
||||
XMLStream := CreateXMLStream;
|
||||
try
|
||||
if UnzipToStream(AStream, 'styles.xml', APassword, XMLStream) then
|
||||
ReadXMLStream(Doc, XMLStream);
|
||||
if UnzipToStream(AStream, 'styles.xml', APassword, XMLStream, err) then
|
||||
ReadXMLStream(Doc, XMLStream)
|
||||
else
|
||||
raise EFPSpreadsheetReader.Create(err);
|
||||
finally
|
||||
XMLStream.Free;
|
||||
end;
|
||||
@@ -2959,10 +2962,10 @@ begin
|
||||
// process the content.xml file
|
||||
XMLStream := CreateXMLStream;
|
||||
try
|
||||
if UnzipToStream(AStream, 'content.xml', APassword, XMLStream) then
|
||||
if UnzipToStream(AStream, 'content.xml', APassword, XMLStream, err) then
|
||||
ReadXMLStream(Doc, XMLStream)
|
||||
else
|
||||
raise EFPSpreadsheetReader.CreateFmt(rsDefectiveInternalFileStructure, ['ods']);
|
||||
raise EFPSpreadsheetReader.Create(err);
|
||||
finally
|
||||
XMLStream.Free;
|
||||
end;
|
||||
@@ -3050,7 +3053,7 @@ begin
|
||||
// process the meta.xml file
|
||||
XMLStream := CreateXMLStream;
|
||||
try
|
||||
if UnzipToStream(AStream, 'meta.xml', APassword, XMLStream) then
|
||||
if UnzipToStream(AStream, 'meta.xml', APassword, XMLStream, err) then
|
||||
begin
|
||||
ReadXMLStream(Doc, XMLStream);
|
||||
try
|
||||
@@ -3058,7 +3061,8 @@ begin
|
||||
finally
|
||||
FreeAndNil(Doc);
|
||||
end;
|
||||
end;
|
||||
end else
|
||||
raise EFPSpreadsheetReader.Create(err);
|
||||
finally
|
||||
XMLStream.Free;
|
||||
end;
|
||||
@@ -3066,7 +3070,7 @@ begin
|
||||
// process the settings.xml file (Note: it does not always exist!)
|
||||
XMLStream := CreateXMLStream;
|
||||
try
|
||||
if UnzipToStream(AStream, 'settings.xml', APassword, XMLStream) then
|
||||
if UnzipToStream(AStream, 'settings.xml', APassword, XMLStream, err) then
|
||||
begin
|
||||
ReadXMLStream(Doc, XMLStream);
|
||||
try
|
||||
@@ -3075,7 +3079,8 @@ begin
|
||||
finally
|
||||
FreeAndNil(Doc);
|
||||
end;
|
||||
end;
|
||||
end else
|
||||
raise EFPSpreadsheetReader.Create(err);
|
||||
finally
|
||||
XMLStream.Free;
|
||||
end;
|
||||
@@ -5601,8 +5606,8 @@ end;
|
||||
descendant reader class which implements decryption taking advantage of the
|
||||
provided password and the parameters in ADecryptionInfo. }
|
||||
function TsSpreadOpenDocReader.Decrypt(AStream: TStream;
|
||||
ADecryptionInfo: TsOpenDocManifestFileEntry; APassword: RawByteString;
|
||||
ADestStream: TStream): Boolean;
|
||||
ADecryptionInfo: TsOpenDocManifestFileEntry; APassword: String;
|
||||
ADestStream: TStream; out AErrorMsg: String): Boolean;
|
||||
begin
|
||||
Result := false;
|
||||
end;
|
||||
@@ -5621,13 +5626,14 @@ begin
|
||||
end;
|
||||
|
||||
function TsSpreadOpenDocReader.UnzipToStream(AStream: TStream; AZippedFile: String;
|
||||
APassword: RawByteString; ADestStream: TStream): Boolean;
|
||||
APassword: RawByteString; ADestStream: TStream; out AErrorMsg: String): Boolean;
|
||||
var
|
||||
i: Integer;
|
||||
mfe: TsOpenDocManifestFileEntry;
|
||||
isEncrypted: Boolean;
|
||||
tmpStream: TStream;
|
||||
begin
|
||||
AErrorMsg := '';
|
||||
mfe := nil;
|
||||
|
||||
// Find the requested file among the manifest file entries containing
|
||||
@@ -5646,7 +5652,7 @@ begin
|
||||
// Read the encrypted file from the input stream
|
||||
Result := UnzipToStream(AStream, AZippedFile, tmpStream);
|
||||
// Decrypt the file into the destination stream
|
||||
Result := Result and Decrypt(tmpStream, mfe, APassword, ADestStream);
|
||||
Result := Result and Decrypt(tmpStream, mfe, APassword, ADestStream, AErrorMsg);
|
||||
finally
|
||||
tmpStream.Free;
|
||||
end;
|
||||
|
@@ -202,6 +202,8 @@ function SplitStr(const AText: String; ADelimiter: Char): TStringArray;
|
||||
function SafeQuoteStr(AString: String): String;
|
||||
function UnquoteStr(AString: String): String;
|
||||
|
||||
function StringToBytes(const AString: String): TBytes;
|
||||
|
||||
function InitSearchParams(ASearchText: String = ''; AOptions: TsSearchOptions = [];
|
||||
ASearchWithin: TsSearchWithin = swWorksheet): TsSearchParams;
|
||||
function InitReplaceParams(AReplaceText: String = '';
|
||||
@@ -2481,6 +2483,18 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
{@@ ----------------------------------------------------------------------------
|
||||
Copies a string to a TBytes variable
|
||||
|
||||
@@param AString The string to be copied
|
||||
@@returns A dynamic array of byte, type TBytes
|
||||
-------------------------------------------------------------------------------}
|
||||
function StringToBytes(const AString: String): TBytes;
|
||||
begin
|
||||
SetLength(Result, Length(AString));
|
||||
Move(AString[1], Result[0], Length(AString));
|
||||
end;
|
||||
|
||||
{@@ ----------------------------------------------------------------------------
|
||||
Initializes a SearchParams record. This record defines the parameters needed
|
||||
when searching cells.
|
||||
|
@@ -35,12 +35,24 @@ uses
|
||||
aes_type, aes_cbc, aes_ecb;
|
||||
{$ENDIF}
|
||||
{$IFDEF DCPCRYPT}
|
||||
DCPrijndael;
|
||||
DCPcrypt2, DCPrijndael;
|
||||
{$ENDIF}
|
||||
|
||||
function Calc_SHA1(const AText: RawByteString): RawByteString;
|
||||
function Calc_SHA256(AText: RawByteString): RawByteString;
|
||||
function PBKDF2_HMAC_SHA1(pass, salt: RawByteString; count, kLen: Integer): RawByteString;
|
||||
function ConcatToByteArray(const InArray1, InArray2: TBytes): TBytes;
|
||||
procedure ConcatToByteArray(var OutArray: TBytes;
|
||||
Ptr1: PByte; ACount1: Integer; Ptr2: PByte; ACount2: Integer);
|
||||
procedure ConcatToByteArray(var OutArray: TBytes;
|
||||
Ptr: PByte; ACount: Integer; const Arr: TBytes);
|
||||
|
||||
function DecodeBase64(const AString: String): TBytes;
|
||||
|
||||
function Calc_SHA1(const Buf; BufSize: LongWord): TBytes;
|
||||
function Calc_SHA1(Buf: TBytes): TBytes;
|
||||
|
||||
function Calc_SHA256(const Buf; BufSize: LongWord): TBytes;
|
||||
function Calc_SHA256(Buf: TBytes): TBytes;
|
||||
|
||||
function PBKDF2_HMAC_SHA1(pass, salt: TBytes; count, kLen: Integer): TBytes;
|
||||
|
||||
function Decrypt_AES_ECB(const Key; KeySizeBits: LongWord;
|
||||
const InData; var OutData; DataSize: LongWord): String;
|
||||
@@ -48,115 +60,161 @@ function Decrypt_AES_ECB(const Key; KeySizeBits: LongWord;
|
||||
function DecryptStream_AES_ECB(const Key; KeySizeBits: LongWord;
|
||||
ASrcStream, ADestStream: TStream; ASrcStreamSize: QWord): String;
|
||||
|
||||
function Decrypt_AES_CBC(const Key; KeySizeBits: LongWord; InitVector: Pointer;
|
||||
ASrcStream, ADestStream: TStream): String;
|
||||
|
||||
function VerifyDecrypt(AStream: TStream; CheckSum, ChecksumType: string): boolean;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
Math;
|
||||
Math, fpsUtils;
|
||||
|
||||
function Calc_SHA1(const AText: RawByteString): RawByteString;
|
||||
var
|
||||
sha1Digest: TSHA1Digest;
|
||||
function ConcatToByteArray(const InArray1, InArray2: TBytes): TBytes;
|
||||
begin
|
||||
sha1Digest := SHA1String(AText);
|
||||
SetLength(Result, 20);
|
||||
Move(sha1Digest[0], Result[1], 20);
|
||||
ConcatToByteArray(Result, @InArray1[0], Length(InArray1), @InArray2[0], Length(InArray2));
|
||||
end;
|
||||
|
||||
function Calc_SHA256(AText: RawByteString): RawByteString;
|
||||
procedure ConcatToByteArray(var OutArray: TBytes; Ptr1: PByte; ACount1: Integer;
|
||||
Ptr2: PByte; ACount2: Integer);
|
||||
begin
|
||||
SetLength(OutArray, ACount1 + ACount2);
|
||||
if ACount1 > 0 then
|
||||
Move(Ptr1^, OutArray[0], ACount1);
|
||||
if ACount2 > 0 then
|
||||
Move(Ptr2^, OutArray[ACount1], ACount2);
|
||||
end;
|
||||
|
||||
procedure ConcatToByteArray(var OutArray: TBytes; Ptr: PByte; ACount: Integer;
|
||||
const Arr: TBytes);
|
||||
begin
|
||||
ConcatToByteArray(OutArray, Ptr, ACount, @Arr[0], Length(Arr));
|
||||
end;
|
||||
|
||||
function DecodeBase64(const AString: String): TBytes;
|
||||
begin
|
||||
Result := StringToBytes(DecodeStringBase64(AString));
|
||||
end;
|
||||
|
||||
function Calc_SHA1(const Buf; BufSize: LongWord): TBytes;
|
||||
var
|
||||
digest: TSHA1Digest;
|
||||
begin
|
||||
digest := SHA1Buffer(Buf, BufSize);
|
||||
SetLength(Result, Length(digest));
|
||||
Move(digest[0], Result[0], Length(digest));
|
||||
end;
|
||||
|
||||
function Calc_SHA1(Buf: TBytes): TBytes;
|
||||
begin
|
||||
Result := Calc_SHA1(Buf[0], Length(Buf));
|
||||
end;
|
||||
|
||||
function Calc_SHA256(const Buf; BufSize: LongWord): TBytes;
|
||||
var
|
||||
sha256: TSHA256;
|
||||
begin
|
||||
sha256.Init;
|
||||
sha256.Update(@AText[1], Length(AText));
|
||||
sha256.Update(@Buf, BufSize);
|
||||
sha256.Final;
|
||||
SetLength(Result, 32);
|
||||
Move(sha256.Digest[0], Result[1], 32);
|
||||
SetLength(Result, SizeOf(TSHA256Digest));
|
||||
Move(sha256.Digest[0], Result[0], SizeOf(TSHA256Digest));
|
||||
end;
|
||||
|
||||
function RPad(x: RawByteString; c: Char; s: Integer): RawByteString;
|
||||
function Calc_SHA256(Buf: TBytes): TBytes;
|
||||
begin
|
||||
Result := Calc_SHA256(Buf[0], Length(Buf));
|
||||
end;
|
||||
|
||||
function RPad(Data: TBytes; PadByte: Byte; ALen: Integer): TBytes;
|
||||
var
|
||||
L: Integer;
|
||||
begin
|
||||
L := Length(x);
|
||||
if L < s then
|
||||
L := Length(Data);
|
||||
if L < ALen then
|
||||
begin
|
||||
SetLength(Result, s);
|
||||
Move(x[1], Result[1], L);
|
||||
FillChar(Result[L+1], s-L, c);
|
||||
SetLength(Result, ALen);
|
||||
Move(Data[0], Result[0], L);
|
||||
FillChar(Result[L], ALen-L, PadByte);
|
||||
end else
|
||||
Result := x;
|
||||
Result := Data;
|
||||
end;
|
||||
|
||||
function Fill(c: Char; Len: Integer): RawByteString; inline;
|
||||
function Fill(b: Byte; Len: Integer): TBytes; inline;
|
||||
begin
|
||||
SetLength(Result, Len);
|
||||
FillChar(Result[1], Len, c);
|
||||
FillChar(Result[0], Len, b);
|
||||
end;
|
||||
|
||||
function XorBlock(s, x: RawByteString): RawByteString; inline;
|
||||
function XorBlock(s, x: TBytes): TBytes; inline;
|
||||
var
|
||||
L, i: Integer;
|
||||
Ps, Px: PByte;
|
||||
begin
|
||||
L := Length(s);
|
||||
SetLength(Result, L);
|
||||
Ps := PByte(@s[1]);
|
||||
Px := PByte(@x[1]);
|
||||
for i := 1 to L do
|
||||
Ps := PByte(@s[0]);
|
||||
Px := PByte(@x[0]);
|
||||
for i := 0 to L-1 do
|
||||
begin
|
||||
Result[i] := Char(Ps^ xor Px^);
|
||||
Result[i] := Ps^ xor Px^;
|
||||
inc(Ps);
|
||||
inc(Px);
|
||||
end;
|
||||
end;
|
||||
|
||||
function Calc_HMAC_SHA1(message, key: RawByteString): RawByteString;
|
||||
function Calc_HMAC_SHA1(message, key: TBytes): TBytes;
|
||||
const
|
||||
blockSize = 64;
|
||||
begin
|
||||
if Length(key) > blocksize then
|
||||
key := Calc_SHA1(key);
|
||||
key := RPad(key, #0, blocksize);
|
||||
key := RPad(key, 0, blocksize);
|
||||
|
||||
Result := Calc_SHA1(XorBlock(key, Fill(#$36, blocksize)) + message);
|
||||
Result := Calc_SHA1(XorBlock(key, Fill(#$5c, blocksize)) + Result);
|
||||
Result := Calc_SHA1(ConcatToByteArray(XorBlock(key, Fill($36, blocksize)), message));
|
||||
Result := Calc_SHA1(ConcatToByteArray(XorBlock(key, Fill($5c, blockSize)), Result));
|
||||
|
||||
//Result := Calc_SHA1(XorBlock(key, Fill($36, blocksize)) + message);
|
||||
//Result := Calc_SHA1(XorBlock(key, Fill($5c, blocksize)) + Result);
|
||||
end;
|
||||
|
||||
// https://keit.co/dcpcrypt-hmac-rfc2104/
|
||||
function PBKDF2_HMAC_SHA1(pass, salt: RawByteString; count, kLen: Integer): RawByteString;
|
||||
function PBKDF2_HMAC_SHA1(pass, salt: TBytes; count, kLen: Integer): TBytes;
|
||||
|
||||
function IntX(i: Integer): RawByteString;
|
||||
function IntX(i: LongInt): TBytes;
|
||||
type
|
||||
Int4 = record
|
||||
i24, i16, i8, i0: char;
|
||||
i24, i16, i8, i0: byte;
|
||||
end;
|
||||
begin
|
||||
SetLength(Result, 4);
|
||||
Result[1] := Int4(i).i0;
|
||||
Result[2] := Int4(i).i8;
|
||||
Result[3] := Int4(i).i16;
|
||||
Result[4] := Int4(i).i24;
|
||||
Result[0] := Int4(i).i0;
|
||||
Result[1] := Int4(i).i8;
|
||||
Result[2] := Int4(i).i16;
|
||||
Result[3] := Int4(i).i24;
|
||||
end;
|
||||
|
||||
var
|
||||
D, I, J: Integer;
|
||||
T, F, U: RawByteString;
|
||||
T, F, U: TBytes;
|
||||
begin
|
||||
T := '';
|
||||
T := nil;
|
||||
D := Ceil(kLen / 20); //(hash.GetHashSize div 8));
|
||||
for i := 1 to D do
|
||||
begin
|
||||
F := Calc_HMAC_SHA1(salt + IntX(i), pass);
|
||||
F := Calc_HMAC_SHA1(ConcatToByteArray(salt, IntX(i)), pass);
|
||||
U := F;
|
||||
for j := 2 to count do
|
||||
begin
|
||||
U := Calc_HMAC_SHA1(U, pass);
|
||||
F := XorBlock(F, U);
|
||||
end;
|
||||
T := T + F;
|
||||
T := ConcatToByteArray(T, F); // T := T + F;
|
||||
end;
|
||||
Result := Copy(T, 1, kLen);
|
||||
Result := nil;
|
||||
SetLength(Result, kLen);
|
||||
Move(T[0], Result[0], kLen);
|
||||
// Result := Copy(T, 1, kLen);
|
||||
end;
|
||||
|
||||
function Decrypt_AES_ECB(const Key; KeySizeBits: LongWord;
|
||||
@@ -252,5 +310,105 @@ begin
|
||||
{$ENDIF}
|
||||
end;
|
||||
|
||||
{ Decrypts the data in the stream ASrcStream and stores the result in the
|
||||
stream ADestStream.
|
||||
Decryption algorithm is AES method CBC.
|
||||
The hashed password is provided as parameter Key, its length in bits is
|
||||
given by KeySizeBits.
|
||||
The initialization vector is specified in InitVector.
|
||||
If an error occurs, the function result returns an error message with
|
||||
error code (error codes are listed in unit AES_Type). Otherwise the
|
||||
function result is an empty string. }
|
||||
function Decrypt_AES_CBC(const Key; KeySizeBits: LongWord; InitVector: Pointer;
|
||||
ASrcStream, ADestStream: TStream): String;
|
||||
{$IFDEF WOLFGANG_EHRHARDT_LIB}
|
||||
const
|
||||
BUF_SIZE = $4000; {must be a multiple of AESBLKSIZE=16 for CBC}
|
||||
var
|
||||
ctx: TAESContext;
|
||||
buffer: array[0..BUF_SIZE-1] of byte;
|
||||
len, err: Integer;
|
||||
n: Word;
|
||||
begin
|
||||
Result := '';
|
||||
|
||||
err := AES_CBC_Init_Decr(Key, KeySizebits, PAESBlock(InitVector)^, ctx{%H-});
|
||||
if err <> 0 then
|
||||
begin
|
||||
Result := 'Decrypt init error ' + IntToStr(err);
|
||||
exit;
|
||||
end;
|
||||
|
||||
len := ASrcStream.Size;
|
||||
while len > 0 do
|
||||
begin
|
||||
if len > SizeOf(buffer) then
|
||||
n := SizeOf(buffer)
|
||||
else
|
||||
n := len;
|
||||
ASrcStream.Read(buffer{%H-}, n);
|
||||
dec(len, n);
|
||||
err := AES_CBC_Decrypt(@buffer, @buffer, n, ctx);
|
||||
if err <> 0 then
|
||||
begin
|
||||
Result := 'Decrypt error: ' + IntToStr(err);
|
||||
exit;
|
||||
end;
|
||||
ADestStream.Write(buffer, n);
|
||||
end;
|
||||
end;
|
||||
{$ENDIF}
|
||||
|
||||
{$IFDEF DCPCRYPT}
|
||||
var
|
||||
AES_cipher: TDCP_rijndael;
|
||||
begin
|
||||
AES_cipher := TDCP_rijndael.Create(nil);
|
||||
try
|
||||
AES_cipher.Init(Key, KeySizebits, InitVector);
|
||||
AES_cipher.CipherMode := cmCBC;
|
||||
AES_cipher.DecryptStream(ASrcStream, ADestStream, ASrcStream.Size);
|
||||
finally
|
||||
AES_cipher.Free;
|
||||
end;
|
||||
end;
|
||||
{$ENDIF}
|
||||
|
||||
function VerifyDecrypt(AStream: TStream; CheckSum, ChecksumType: string): boolean;
|
||||
var
|
||||
p: Int64;
|
||||
buffer: Array of byte = nil;
|
||||
expCheckSum: TBytes;
|
||||
currCheckSumSHA1: TSHA1Digest;
|
||||
lSHA256: TSHA256;
|
||||
currChecksumSHA256: TSHA256Digest;
|
||||
begin
|
||||
Result := false;
|
||||
p := AStream.Position;
|
||||
expCheckSum := DecodeBase64(CheckSum);
|
||||
case Uppercase(ChecksumType) of
|
||||
'SHA1/1K', 'SHA1-1K':
|
||||
begin
|
||||
SetLength(buffer, 1024);
|
||||
AStream.Write(buffer[0], Length(buffer));
|
||||
currCheckSumSHA1 := SHA1Buffer(buffer[0], 1024);
|
||||
if Length(expCheckSum) = Length(TSHA1Digest) then
|
||||
Result := CompareMem(@expCheckSum[0], @currCheckSumSHA1[0], Length(TSHA1Digest));
|
||||
end;
|
||||
'SHA256/1K', 'SHA256-1K':
|
||||
begin
|
||||
SetLength(buffer, 1024);
|
||||
AStream.Read(buffer[0], Length(buffer));
|
||||
lSHA256.Init;
|
||||
lSHA256.Update(@buffer[0], Length(Buffer));
|
||||
lSHA256.Final;
|
||||
currCheckSumSHA256 := lSHA256.Digest;
|
||||
if Length(expCheckSum) = Length(TSHA256Digest) then
|
||||
Result := CompareMem(@expChecksum[0], @currCheckSumSHA256[0], Length(TSHA256Digest));
|
||||
end;
|
||||
end;
|
||||
AStream.Position := p;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
|
@@ -0,0 +1,192 @@
|
||||
unit fpsOpenDocument_Crypto;
|
||||
|
||||
{$MODE ObjFPC}{$H+}
|
||||
{$DEFINE UNZIP_ABBREVIA} // Remove this define when zipper is fixed.
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils,
|
||||
{$IFDEF UNZIP_ABBREVIA}
|
||||
ABUnzper,
|
||||
{$ENDIF}
|
||||
fpsTypes, fpsOpenDocument;
|
||||
|
||||
type
|
||||
TsSpreadOpenDocReaderCrypto = class(TsSpreadOpenDocReader)
|
||||
private
|
||||
function CalcPasswordHash(ADecryptionInfo: TsOpenDocManifestFileEntry;
|
||||
APassword: String): TBytes;
|
||||
protected
|
||||
function Decrypt(AStream: TStream; ADecryptionInfo: TsOpenDocManifestFileEntry;
|
||||
APassword: String; ADestStream: TStream; out AErrorMsg: String): Boolean; override;
|
||||
function SupportsDecryption: Boolean; override;
|
||||
{$IFDEF UNZIP_ABBREVIA}
|
||||
function UnzipToStream(AStream: TStream; AZippedFile: String; ADestStream: TStream): Boolean; override;
|
||||
{$ENDIF}
|
||||
end;
|
||||
|
||||
var
|
||||
sfidOpenDocument_Crypto: TsSpreadFormatID;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
zStream,
|
||||
fpsReaderWriter, fpsCryptoProc;
|
||||
|
||||
{ Decompresses the source stream and stored the output in the destination stream. }
|
||||
procedure Decompress(ASrcStream, ADestStream: TStream);
|
||||
var
|
||||
decompressor: TDecompressionStream;
|
||||
begin
|
||||
decompressor := TDecompressionStream.Create(ASrcStream, true);
|
||||
try
|
||||
ADestStream.CopyFrom(decompressor, 0); // 0 --> entire src stream
|
||||
finally
|
||||
decompressor.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
{-------------------------------------------------------------------------------
|
||||
TsSpreadOpenDocReaderCrypto
|
||||
-------------------------------------------------------------------------------}
|
||||
|
||||
{ AStream contains one encrypted xml file of the ods file structure. The method
|
||||
decrypts the stream based on the information provided in ADecryptionInfo and
|
||||
using the given (unhashed) user password. The output is stored in the
|
||||
destination stream. }
|
||||
function TsSpreadOpenDocReaderCrypto.Decrypt(AStream: TStream;
|
||||
ADecryptionInfo: TsOpenDocManifestFileEntry; APassword: String;
|
||||
ADestStream: TStream; out AErrorMsg: String): Boolean;
|
||||
var
|
||||
pwdHash: TBytes;
|
||||
iv: TBytes;
|
||||
tmpStream: TStream;
|
||||
algorithm: String;
|
||||
begin
|
||||
Result := false;
|
||||
|
||||
algorithm := LowerCase(ADecryptionInfo.AlgorithmName);
|
||||
if (algorithm = 'aes128-cbc') or (algorithm='aes192-cbc') or (algorithm='aes256-cbc') then
|
||||
algorithm := 'aes'
|
||||
else
|
||||
algorithm := '';
|
||||
|
||||
if algorithm = '' then
|
||||
exit;
|
||||
|
||||
tmpStream := TMemoryStream.Create;
|
||||
try
|
||||
// Calculated password hash
|
||||
pwdHash := CalcPasswordHash(ADecryptionInfo, APassword);
|
||||
|
||||
// Decrypt
|
||||
iv := DecodeBase64(ADecryptionInfo.InitializationVector);
|
||||
case algorithm of
|
||||
'aes':
|
||||
AErrorMsg := Decrypt_AES_CBC(pwdHash[0], Length(pwdHash)*8, @iv[0], AStream, tmpStream);
|
||||
else
|
||||
AErrorMsg := 'Encryption method not supported.';
|
||||
end;
|
||||
if (AErrorMsg <> '') then
|
||||
exit;
|
||||
|
||||
// Verify decrypted (but still compressed) stream
|
||||
// OpenDocument-v1.2-part3, section 3.8.3: "The digest is build from the compressed unencrypted file"
|
||||
tmpStream.Position := 0;
|
||||
if not VerifyDecrypt(tmpStream, ADecryptionInfo.EncryptionData_CheckSum, ADecryptionInfo.EncryptionData_ChecksumType) then
|
||||
AErrorMsg := 'Checksum error';
|
||||
if AErrorMsg <> '' then
|
||||
exit;
|
||||
|
||||
// Decompress the decrypted stream
|
||||
Decompress(tmpStream, ADestStream);
|
||||
ADestStream.Position := 0;
|
||||
|
||||
// Success!
|
||||
Result := true;
|
||||
finally
|
||||
tmpStream.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ Calculates the hash value of the user-provided passwort.
|
||||
Hash creation is determined by information stored in ADecryptionInfo. }
|
||||
function TsSpreadOpenDocReaderCrypto.CalcPasswordHash(
|
||||
ADecryptionInfo: TsOpenDocManifestFileEntry;
|
||||
APassword: String): TBytes;
|
||||
var
|
||||
pwdHash: TBytes;
|
||||
salt: TBytes;
|
||||
numIterations: Integer;
|
||||
keySize: Integer;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
// Generate start key
|
||||
case LowerCase(ADecryptionInfo.StartKeyGenerationName) of
|
||||
'sha256': pwdHash := Calc_SHA256(APassword[1], Length(APassword));
|
||||
else
|
||||
raise EFpSpreadsheetReader.Create('Unsupported start key generator ' + ADecryptionInfo.StartKeyGenerationName);
|
||||
end;
|
||||
|
||||
// Generate derived key
|
||||
numIterations := ADecryptionInfo.IterationCount;
|
||||
keySize := ADecryptionInfo.KeySize;
|
||||
salt := DecodeBase64(ADecryptionInfo.Salt);
|
||||
if LowerCase(ADecryptionInfo.KeyDerivationName) = 'pbkdf2' then
|
||||
Result := PBKDF2_HMAC_SHA1(pwdHash, salt, numIterations, keySize)
|
||||
else
|
||||
raise EFpSpreadsheetReader.Create('Unsupported key generation method ' + ADecryptionInfo.KeyDerivationName);
|
||||
end;
|
||||
|
||||
|
||||
{ Tells the calling routine that this reader is able to decrypt ods files. }
|
||||
function TsSpreadOpenDocReaderCrypto.SupportsDecryption: Boolean;
|
||||
begin
|
||||
Result := true;
|
||||
end;
|
||||
|
||||
|
||||
{$IFDEF UNZIP_ABBREVIA}
|
||||
{ Extracts the specified file from the compressed stream (AStream) to the
|
||||
ADestStream.
|
||||
Uses the ABBREVIA library for this purpose (because FCL Stripper fails to
|
||||
extract the encrypted file). }
|
||||
function TsSpreadOpenDocReaderCrypto.UnzipToStream(AStream: TStream;
|
||||
AZippedFile: String; ADestStream: TStream): Boolean;
|
||||
var
|
||||
unzipper: TABUnzipper;
|
||||
begin
|
||||
Result := false;
|
||||
unzipper := TABUnzipper.Create(nil);
|
||||
try
|
||||
unzipper.Stream := AStream;
|
||||
try
|
||||
unzipper.ExtractToStream(AZippedFile, ADestStream);
|
||||
ADestStream.Position := 0;
|
||||
Result := true;
|
||||
except
|
||||
raise;
|
||||
end;
|
||||
finally
|
||||
unzipper.Free;
|
||||
end;
|
||||
end;
|
||||
{$ENDIF}
|
||||
|
||||
|
||||
{==============================================================================}
|
||||
initialization
|
||||
{==============================================================================}
|
||||
|
||||
{ Registers this reader for fpSpreadsheet }
|
||||
|
||||
sfidOpenDocument_Crypto := RegisterSpreadFormat(sfUser,
|
||||
TsSpreadOpenDocReaderCrypto, nil,
|
||||
STR_FILEFORMAT_OPENDOCUMENT, 'ODS', [STR_OPENDOCUMENT_CALC_EXTENSION]
|
||||
);
|
||||
|
||||
end.
|
||||
|
@@ -125,22 +125,6 @@ implementation
|
||||
uses
|
||||
fpolebasic;
|
||||
|
||||
procedure ConcatToByteArray(var OutArray: TBytes; Ptr1: PByte; ACount1: Integer;
|
||||
Ptr2: PByte; ACount2: Integer);
|
||||
begin
|
||||
SetLength(OutArray, ACount1 + ACount2);
|
||||
if ACount1 > 0 then
|
||||
Move(Ptr1^, OutArray[0], ACount1);
|
||||
if ACount2 > 0 then
|
||||
Move(Ptr2^, OutArray[ACount1], ACount2);
|
||||
end;
|
||||
|
||||
procedure ConcatToByteArray(var OutArray: TBytes; Ptr: PByte; ACount: Integer;
|
||||
const Arr: TBytes);
|
||||
begin
|
||||
ConcatToByteArray(OutArray, Ptr, ACount, @Arr[0], Length(Arr));
|
||||
end;
|
||||
|
||||
function TExcelFileDecryptor.InitEncryptionInfo(AStream: TStream): string;
|
||||
var
|
||||
EncInfoStream: TMemoryStream;
|
||||
@@ -403,7 +387,6 @@ begin
|
||||
|
||||
try
|
||||
inStream := TFileStream.Create(inFileName, fmOpenRead);
|
||||
|
||||
inStream.Position := 0;
|
||||
Result := Decrypt(inStream, outStream, APassword);
|
||||
finally
|
||||
|
Reference in New Issue
Block a user