From d25851c07f80ffce329e0e51fd4c92a0f7efe99f Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Tue, 14 Mar 2017 21:48:29 +0000 Subject: [PATCH] fpspreadsheet: Initial commit of experimental xlsx decryption support (xlsx decryptor written by forum user shobits1). git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@5806 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- .../ooxmldemo_crypto/encrypted_workbook.xlsx | Bin 0 -> 12800 bytes .../ooxml_decrypt_and_read.lpi | 70 ++ .../ooxml_decrypt_and_read.pas | 84 +++ .../ooxmldemo_crypto/protected_workbook.xlsx | Bin 0 -> 12800 bytes .../read_write/ooxmldemo_crypto/readme.txt | 8 + .../fpspreadsheet/source/common/fpscsv.pas | 14 +- .../fpspreadsheet/source/common/fpshtml.pas | 6 +- .../source/common/fpsopendocument.pas | 7 +- .../source/common/fpspreadsheet.pas | 35 +- .../source/common/fpsreaderwriter.pas | 19 +- .../fpspreadsheet/source/common/xlsbiff2.pas | 7 +- .../fpspreadsheet/source/common/xlsbiff5.pas | 7 +- .../fpspreadsheet/source/common/xlsbiff8.pas | 7 +- .../fpspreadsheet/source/common/xlsxooxml.pas | 9 +- .../source/crypto/xlsxdecrypter.pas | 596 ++++++++++++++++++ .../source/crypto/xlsxooxml_crypto.pas | 68 ++ .../source/laz_fpspreadsheet_crypto.lpk | 45 ++ 17 files changed, 934 insertions(+), 48 deletions(-) create mode 100644 components/fpspreadsheet/examples/read_write/ooxmldemo_crypto/encrypted_workbook.xlsx create mode 100644 components/fpspreadsheet/examples/read_write/ooxmldemo_crypto/ooxml_decrypt_and_read.lpi create mode 100644 components/fpspreadsheet/examples/read_write/ooxmldemo_crypto/ooxml_decrypt_and_read.pas create mode 100644 components/fpspreadsheet/examples/read_write/ooxmldemo_crypto/protected_workbook.xlsx create mode 100644 components/fpspreadsheet/examples/read_write/ooxmldemo_crypto/readme.txt create mode 100644 components/fpspreadsheet/source/crypto/xlsxdecrypter.pas create mode 100644 components/fpspreadsheet/source/crypto/xlsxooxml_crypto.pas create mode 100644 components/fpspreadsheet/source/laz_fpspreadsheet_crypto.lpk diff --git a/components/fpspreadsheet/examples/read_write/ooxmldemo_crypto/encrypted_workbook.xlsx b/components/fpspreadsheet/examples/read_write/ooxmldemo_crypto/encrypted_workbook.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..66c477b517e7c3609aa69523c53a5e04b8eab533 GIT binary patch literal 12800 zcmeHrby$_#*6*UbyQBnZ>29P!8l?r15+tOg8zdGW-RYu3Lb|(Cx>R5hf(YUw<$ITV zd!PO7z3)ByocsOpo&B7#_`PG+JI9=(=6so)AlS%zL-`)`JCXz;fxh2hfKY$ajsTS5 z_(Av}5F${3*T3J~-2BoA0s+PU(7%xeu7R~8|7t_HtxA=*4FCrK zE&x0LfD3>K01yHo0zeFa1OO=jG63*7{zLz}HJ}Z01b(g{I*>f*8NgjYF96H^bs_~k z=I0LKe)9jKxOi|pYSuIY5->;_{s0Ir_3;m>PJr|$AWM)Y$Q)>S3OucW`adEDAPfDJ z59j}AJiH$bfH3_pBT+&307-<0SH{2ZXc!dL=2jDt_$3MLO z+xY)aOvAPO_aSk>#_E9g!DG-7@IyzC6%Y+vAh{p@2#*U}z%SuG%Jrv_0{5Sv0E+)K z!F>$=zRj;3J^>wof0P$^fiXHi@)bO8tbaHDmuUK9WCJh#j(*DjEA#0BS}+HsJqL8; z2$UUwxgG+5uOA*y-XI0wCjycI@d2D0cyfXSK!U*96o3*xP~!Q~CJSit9?%2tl>yp$ z0B!RCE!-d>5buw1zM%WSe1Fw0|6NzV?ETNy`qNHu{*(TwBXwYpPCvWkj56{F-z^MU0x#)iQkR!+) zs9F9vtA6=5`H*6W6x6zoKji*kdO<}5E7!D`y>>wOvyMs~J784+qL_kDj za6AWE8_UpR2&{^?oxXE#^0{$ISF~~S&$a(uIX(R^JA;~hjuqEwV^PUhrSEPUqrmQJ zFD>pyG|X@i)3A#cN9oCO1|iMoS|J}qUa`37?Wrv1;BVna8qMhMps+Sy^?fUue*WOg z^a#cRWI4o=^VNUq|6cki+Mm7Jcs*9KpN_QhS>muRP6k3g=-aOSNgQ$IAS@$IxPvcw zsq9Kk^cDT`_hZ$&-l=xdmc@mA%zorht_hG5v$-MpQa{ zXk_hwR?6Uu!|!&rpW<_1r1@#`P6v_}qss`Rw=UB8Tow4*HxyiaoVFS?osc&gIp$qH z7ovZOmA?ZWOL9!#&}S*iET!tsp?`+y@w68me+=tJ{eq!YFXvcKVfGe1jzPoPOR>he zaVwQa(0a2;rde*ZL)Eq^%wo1&)z`DL)Z}8R=MT;zTaNR1FfyrbHQ2|L*vKrFF5GSU zHby3y^V&f4qla2U)1}6{C5j&N`Gs7OCUivmtj%xkw<`Dzki`5BX9Hfwrz^cN-I4#g zQxU%Bd9KeiIaDDlqJoUYU=&76SQfKO=_o1b_mM}BBL;8pC<^0smYI=&&-K&_ySEud zxU!Mu!FJFj+48XQJYro&?;>>KT`2LS%GNvVxX{93%&DOZPtu1sRUjgSR^4;ACAQ$r zse@!yxwP#oh#whd?Rxgo=<5gc8DqPsCfJ(-T7_v^B((+qiLZaJ`1-xQicY+7(XieJ zd+?**AkC$`mGi5&CVoK1$nS6)CPXe_wFR2-I8ZE9npg~AMtRCV z>qcKjH$ni72kBjx3J#N_WD4s0a2&y|r^Jy=9r+7E#|C4N6GN0#Pr~{nl2cwrosrN3 z{-pGIC0jcRk$C^4%dXuApsanhgOR(tTW-oawI5vAgOATkE}k^c zxVBooM$t<%i10$chBL+#3tPVt;tv=#KP2!gNc$c_pU|0sY|HdjTrk z^DuK7rMI3mt6n}Z-Ll}bJ8E?wdFDP(zV!XrfTfvws4|Wusr^|VTUGQ4HJT5I_(1f7 zDQUu4pniSh;lg|crhiDP#p@+~hD!c(e>VulN@(vmLmdsw2G7p0HWMO9y0NJ4#&&Lp z zPmfbgXB*mPVftV)DFtN+gfxFd_23L3Q6Qf&(>)wtXP02k4?OVFbkEN7dPuvGHdCX} zdlRjJ?jEI10fp5=5(lVpNaAdKU=Y*6li=D=_66^n@2%k#NX%YxiIYYTP?AD0BG4>o z5t;clFzt9C9Z;<%KURMBK@u-T`sFsXfPRciWtXDCa7Ce!r{l*Fx;3k4;QKgpr$@L42OnrzTm$~q;H`MNy zYrj2$5fW!S`psQcF~KW`my4(lm7)9fnqo@Sa6<5$2I!_<1g7R*Xks6wTK@4+lJQ|1nB{<0m_Yg+ z>#VD0Y^qOI^1Ht5#XHZj?D$_nqIl|TSqvSdJ;c;ex75jZRlKMfzkBz?d!aSeSHH{Wil4HPxItu8k8+5^JIiW zUAeo*J48{9yb;$EcUU`54-SlknMEc)8MHi5cE`Gcq7pX;N)_FI^I;z)O({hz6<-kr zf=VgeHj2>pde!XHwjc+aBZPK@kC1U|ZmbsXfo7h&`g-u3a|lP?LczHDMOe-L4R+Mn z#6wW?BklMumrvJc3|G%_qb0&pNK_l38k8(r<@>PeCCb)PKVvKjBlPD`hd>L#KJ317 zV}VCu&FIS7>3e!>QgfY;W}SpT78u?g`?bz8) zUq{|szO?^h60m|lG^XlQ|NZIK7m%S}Q_Hvq+8oRuvG{R5ihV|x#>#z{#yq)|=v6Ey zefxD&25JRG*)8gKLs)apb%tiTwig$dxP)`QF{VYIh`7$UFTCkKPJFrh=3HcO3k4~v z=X4>_u;j^b_L~=%*>8N5A*!(Os{(?Fi5J>a!q8|}*4SH%EMC0hXT@}EPg5Si;stc) zZgMl+?~*?qo1b40qHu}%=V{HB%Wjk1J)W{Hp1s0hynyML_*4{@>^HwvL7`DM zo|chZ=YL_q&gjl`sZx^Rp(8uRJsKh|GhNI6Haa*dUYMyCZ-Y5>0$<06QTy^PI%OKE zeHz)da#~YSf=6gNhm2ycE22#cogI<`zsc>O5HTfHwOZ1%5QR;ajEgv#N5!7iyyKVQ zWtMJ4-2#49yFQd}&wQ1grfds@46Rw%in;dP#ZD6WJl^0#XPAnvI<|B3JNj;$ z^%VzW4@aM#ZItK?y!j$5)kB5W>s~fzcldGy-=0M!i%rw7oANkfo*cZ}*k&Qc&+t6K zcIHYzT-W~GZoI5F1PpNfA&dW6r^|6y` zGBpW3^MdS1u?}-<$A92$mX7IPqp84TZB}QJv0@A27ghfNq$&5sppQ{p zByqB=992_4boX0s5(=_eiE(2%bUiow#fcK*k+G_}qmC`F)&b>~w&jOzO+lmCY zvBM??Ww{9<*f52xx=4g;jda8#Wg zA<%_s0Vz@}M{u6=79zNNVxNQB4@Erao3HIyL$PCZrPNxu$r7Q{v{JU`1ex=cwaJsE z_nla314o*MOroS#cR!;F$f`C+1s8U;;|+g!CQ?MnCvD&wI<(vna;u9Yjo+(qB{^pH zd^+}OgL0GN+xs(xMV14F$`^|D~rf=4$pnu}8%Y zd8dczQ{2V}71%;>!AxpfTQQBJ#UB*WPyhxFN}O|A;gMdO+a`VT+PUwkBgt zDJq`)y-u?>;}h{k%;fJU+FfPI<+&)$WvNLkiYu%#kYgF93==ZJ93y+V zD|k?yFk;bVT{G{f==q)@ASF*+~TiRP5D-dsq(dAv|sS=t; zI|4q-Zck+01nn%GuF5y_L5}Y#Hy);u=D_{8vzM! zqeF@QOQNBjT;XJCANS-G{sOzpZyL~!+}A_z`B7FymH9>) zYcU2(y;e?OJTvIHZqIT6@0Pa)~F@oWPdo0yC{srJ0G!* z5Om8oK-H%+zwWcN4{4o5x^}TM^#sDuEx}|t-`0CIVZ>qeyU3B?ST=2oDY-(`eX~lmp~;)3EPS8t+M-jq zRSGeT7b+lB(LsEqcTB(kFh}?f$_oky=WBy+Nw@=fps=Zy1{EHs(PzW!YpMHv*_V0k zf+@MP!FpMGcSVQO^er7exgwohMH;jXEVyAIhxk>GZ=IjlSZNhvM7qUUxySPzBD#+1 zO;a<}s-1Sp5VJeSmz$emm}sG1IYQfo6tNT|yE+RsbNxaNV;(6Ex8>UHyQ?4d9F4<=b%m+2;x%$?7uUG{vOl+=INwZJ#nA=lhnCDcF4Ec zAHO)KTgOZmFPYDAr95kMG~gGD^MoO&O?(nOka5esS}SrgnTlC-b^Y4b>x1)t@yHXu zAqao(ld$ITJWN~~R!4!6nQ-#T>Yi`Rl~ut;1n=vHxi#*JMqThn?EBL0iWKgYA{^z0 zCI=(K2>WF5;hV+&&+zileRS5hA}FZdq&mDMqJB+hVKas71aA23(AIsnJt6pkjV^{0 znVfOa+zegV*3dWdPPZ5)1zDSGlJw2BETdE}*ut90!7#@XA$A7lWO#d2mgDW*o!35zY_t!w4kXC-3Ey7I_ z?1^ub0u3rbM{=f|UsN7%@>FSYpr|PflJ#4uu+yhUrLA|bqVZxYY+ z{EhY_|F_Il_#BLfQ9j+gh6hAK#zLRC(@3#U?b6tyb$NTGSLDr#kl&|!j#15jSNKrQ zGmo#pv9!3G?%O7QZQnorPNss(tNrF6YY8QhI^Er z;za~@Nr-p*4qGB&VFa7dRbQco41?C8#sUL!IvE=e(qeASB-%?6+5BaF<@%5Z7aa5S z6)iQ-t?cJ%8ZEEzDJhTjCq-dOt%@{qxb*vN?jtMEp_}{JiHXM}Up6?S-^I11sa0}x z9WF+ph7m?jC{}0nsHmSZ;}Bj>T)Onm6>4aDbxZIsfNQNrbcc3moy6@I`u%56j>~!` zKbOAqp3~mNosWmw>&N20*Tpk}kvR?tc&Ph{9)EU`!lylSX5ZZ8rVjt`HG0ywzcK%| z78V#?1+AWfGLPvy-Q~D^GgMsmb2yV{YV2#+dVSosvPl^9>QHPq;M?&d{6>Tjc@fkA zp)pI-#4E?)!ew!hl(`te{dHyADCcKEiIx#Vx&Fo;StOk4Js%w!!)aXDj0&@Qk+L!3 z!+Q3t5NYmM<Ayd!pRQJTJ*{FLp2Rk4#tmkMheHi zY4SCGZ3GF%W|>tZedUHZwDs;nr|EiNL^B{ITWy{^GWS_r`PgJj4Ob*_ z2@l6>bLo>3>#jd_!Y)e9S_!!TDY z8lSos*E+u;HkYx-bo(96vwn5^@Z%lX>|oN4{cbJ z_&vcfG~+Z<(Y)HNB{%camMt%^>c-n0bso;;Q#$rzB32wV`@z7h^~&EK=kNsd>0V4i zdKNK2q&M?BZAW|Hy;10eYA8Nl05WROYFUA15(bam!``$}j*|_sK2$gFCK>j8Ggcal zae1?FDctk6GMx~Q;tmk8W7Mrtw`=1mqXVRpJIgf9?6Vf8UG`=LF}i6f0YS*|7G?tI zjRkC?pNzv>Nd2f`5L@4v(enklwTCZ@tlJc_60okCU0*PLejSS}gs2j!m2*TkhhAJ* z6l=Hfm7IBf+UNQUi_}@>QC$EprjBgqn_WNryU12BO&^=dczrCHdnw|NqROJn&kMCf zc%()#SPY@Wu1lx;Q-N(O+~BFWiqY&oS9%u}epC_V!HWUVb-GEW9gg@agi|FvXQeXS z;4YJ~MvPY^XT$){i>R^ZahlumUjErE)ExEc*@?2lVRxpKpJLWk_eN4jm`N2v|$*5j!akDhFv@Ow_)cH^*aBXbIOz;W}%rIHiWU z@s;wVIv}`s+=INgYt6|_lIk*1IeI)xEA5DL=SdM&60JFN#Pc*i-1g5+wbL3pf_X+R zvCAX)oYf1st|{jiVp0+6d?77CRoufRri^f{3Oz0Muq^2gCJ zcKihsTS(64wOQGd!J}B(^xo1Gks^1BB$Yj1r3(;fI1DuOlS#jQD!-#k^9p?Mm|6^S zYo@!B>6{^;A>84a(X!gX*taJT0t}MwapASxeQE7?REY80e#o+p)+)^7dpfi2uzqCs zB#yn5n<)}cHXJcx<&f62`_(8>%OvPjdg6MdV&HiV!V2px3r@@EjOk{mti4d|O^SNj z_A7^-eH9!@HkP%qRzpOn0~_fz3YO`ESQ+%p{C!zd*;KghID)WWusv z)IQS-KbNHM@oR2hGx{jMfQN=E0a@8Ze-}vUGk2G{pK>y!QL=L+rc@xj0XD28)2S(* z)0d~_&hFx~*W(i`FW8OzowEGo(;Pi+ySs$>C%c%fM2kzdCm)azpVLKV27%v{Tm4pv*)ae>oMO?aZ-83ocBQo zMob8!E|QDeVC2@X#!L%1Y%HGmwzLNAVpR<*>JsxCr;&+YGn+Ha-BIs%DPK`DZqD#- z8{<4RquLAPIm%dQ#-y%3yz<}`msJosA5Ww%o)3;~_m&HsCnw36JuA`FQwS(wH0d6@ zsl1-6o4{-ducM6SJ79eoudg4UwZ(J&`|qE+Y;JVjuta1Q=7b0>qlmGouNHx z7E1zflLt5JRHtZWN2yMpdUMH0Dj5#Z@^%czRC4PQTDA_wR|`R#=`?bB3Brey%b0dk}>?J9=9YXvwZ)bz4rfLZ+_FNI#L#$gkNDl8TE~wnBfeVo!z&)xuox(o#{@ z7z9blY?haT=nl5ydU0$6J!!DE8|?97I4RHjdn>rF2V+uhV#$)RyXneB2T#g};*ocZ zIqC==7cI_a7;rblw5REiNshgl9ov=E2_fONJYud+JqK%yXcJvXDDWk7(7RJPOU5zo V5Q<=Ng>iR=l0Oy4`MZ4o{};1kM$iBN literal 0 HcmV?d00001 diff --git a/components/fpspreadsheet/examples/read_write/ooxmldemo_crypto/ooxml_decrypt_and_read.lpi b/components/fpspreadsheet/examples/read_write/ooxmldemo_crypto/ooxml_decrypt_and_read.lpi new file mode 100644 index 000000000..500d24220 --- /dev/null +++ b/components/fpspreadsheet/examples/read_write/ooxmldemo_crypto/ooxml_decrypt_and_read.lpi @@ -0,0 +1,70 @@ + + + + + + + + + + + + + <UseAppBundle Value="False"/> + </General> + <BuildModes Count="1"> + <Item1 Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + <IgnoreBinaries Value="False"/> + <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/> + <ExcludeFileFilter Value="*.(bak|ppu|ppw|o|so);*~;backup"/> + </PublishOptions> + <RunParams> + <local> + <FormatVersion Value="1"/> + <LaunchingApplication PathPlusParams="\usr\X11R6\bin\xterm -T 'Lazarus Run Output' -e $(LazarusDir)\tools\runwait.sh $(TargetCmdLine)"/> + </local> + </RunParams> + <RequiredPackages Count="3"> + <Item1> + <PackageName Value="laz_fpspreadsheet_crypto"/> + </Item1> + <Item2> + <PackageName Value="laz_fpspreadsheet"/> + </Item2> + <Item3> + <PackageName Value="LazUtils"/> + </Item3> + </RequiredPackages> + <Units Count="1"> + <Unit0> + <Filename Value="ooxml_decrypt_and_read.pas"/> + <IsPartOfProject Value="True"/> + </Unit0> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <Target> + <Filename Value="ooxml_decrypt_and_read"/> + </Target> + <SearchPaths> + <OtherUnitFiles Value="..\..\..\source\common"/> + <UnitOutputDirectory Value="..\..\lib\$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Parsing> + <SyntaxOptions> + <UseAnsiStrings Value="False"/> + </SyntaxOptions> + </Parsing> + <Linking> + <Debugging> + <DebugInfoType Value="dsDwarf2Set"/> + <UseExternalDbgSyms Value="True"/> + </Debugging> + </Linking> + </CompilerOptions> +</CONFIG> diff --git a/components/fpspreadsheet/examples/read_write/ooxmldemo_crypto/ooxml_decrypt_and_read.pas b/components/fpspreadsheet/examples/read_write/ooxmldemo_crypto/ooxml_decrypt_and_read.pas new file mode 100644 index 000000000..d932d0937 --- /dev/null +++ b/components/fpspreadsheet/examples/read_write/ooxmldemo_crypto/ooxml_decrypt_and_read.pas @@ -0,0 +1,84 @@ +{ +ooxml_decrypt_and_read.lpr + +Demonstrates how to read an Excel 2007 xlsx file which is workbook-protected +and thus encrypted by an internal password. + +Basic operating procedure +- Add package laz_fpspreadsheet_crypto +- Use xlsxooxml_crypto (instead of xlsxooxml) +- In Workbook.ReadFromFile specify the file format id spfidOOXML_crypto instead + of the the file format sfOOXML. +} + +program ooxml_decrypt_and_read; + +{$mode delphi}{$H+} + +uses + Classes, SysUtils, LazUTF8, fpstypes, fpspreadsheet, laz_fpspreadsheet, + xlsxooxml_crypto; + +var + MyWorkbook: TsWorkbook; + MyWorksheet: TsWorksheet; + InputFilename: String; + MyDir: string; + cell: PCell; + i: Integer; + password: String; + Prot_enc: Integer = 1; // 0 - protected, 1 - encrypted workbook + +begin + MyDir := ExtractFilePath(ParamStr(0)); + + // Open the input file + MyDir := ExtractFilePath(ParamStr(0)); + + case Prot_enc of + 0: begin + InputFileName := MyDir + 'protected_workbook.xlsx'; + password := ''; + end; + 1: begin + InputFileName := MyDir + 'encrypted_workbook.xlsx'; + password := 'test'; + end; + end; + + if not FileExists(InputFileName) then begin + WriteLn('Input file ', InputFileName, ' does not exist. Please run opendocwrite first.'); + Halt; + end; + WriteLn('Opening input file ', InputFilename); + + // Create the spreadsheet + MyWorkbook := TsWorkbook.Create; + + MyWorkbook.Options := MyWorkbook.Options + [boReadFormulas]; + MyWorkbook.ReadFromFile(InputFilename, sfidOOXML_crypto, password); + + MyWorksheet := MyWorkbook.GetFirstWorksheet; + + // Write all cells with contents to the console + WriteLn(''); + WriteLn('Contents of the first worksheet of the file:'); + WriteLn(''); + + for cell in MyWorksheet.Cells do + WriteLn( + 'Row: ', cell^.Row, + ' Col: ', cell^.Col, + ' Value: ', UTF8ToConsole(MyWorkSheet.ReadAsText(cell^.Row, cell^.Col)) + ); + + // Finalization + MyWorkbook.Free; + + {$ifdef WINDOWS} + WriteLn; + WriteLn('Press ENTER to quit...'); + ReadLn; + {$ENDIF} +end. + diff --git a/components/fpspreadsheet/examples/read_write/ooxmldemo_crypto/protected_workbook.xlsx b/components/fpspreadsheet/examples/read_write/ooxmldemo_crypto/protected_workbook.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..fa66a2fc18b9137e14af5776cc2a645519c6fa27 GIT binary patch literal 12800 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 G{r_L5<E#My literal 0 HcmV?d00001 diff --git a/components/fpspreadsheet/examples/read_write/ooxmldemo_crypto/readme.txt b/components/fpspreadsheet/examples/read_write/ooxmldemo_crypto/readme.txt new file mode 100644 index 000000000..015a38c90 --- /dev/null +++ b/components/fpspreadsheet/examples/read_write/ooxmldemo_crypto/readme.txt @@ -0,0 +1,8 @@ +This demo program reads encrypted xlsx files. + +File "encrypted_workbook.xlsx" was encrypted in the File menu of Excel 2007. +The password is "test". + +The file "protected_workbook.xlsx" was worksheet-protected in the Inspect +menu of Excel 2007. The password "test" was specified, it is not the password +used for encryption of the file, it is only needed to unlock worksheet protection. \ No newline at end of file diff --git a/components/fpspreadsheet/source/common/fpscsv.pas b/components/fpspreadsheet/source/common/fpscsv.pas index 8eab2af86..4fbddcabb 100644 --- a/components/fpspreadsheet/source/common/fpscsv.pas +++ b/components/fpspreadsheet/source/common/fpscsv.pas @@ -22,8 +22,10 @@ type procedure ReadNumber(AStream: TStream); override; public constructor Create(AWorkbook: TsWorkbook); override; - procedure ReadFromFile(AFileName: String; AParams: TsStreamParams = []); override; - procedure ReadFromStream(AStream: TStream; AParams: TsStreamParams = []); override; + procedure ReadFromFile(AFileName: String; APassword: String = ''; + AParams: TsStreamParams = []); override; + procedure ReadFromStream(AStream: TStream; APassword: String = ''; + AParams: TsStreamParams = []); override; procedure ReadFromStrings(AStrings: TStrings; AParams: TsStreamParams = []); override; end; @@ -173,14 +175,14 @@ begin Unused(AStream); end; -procedure TsCSVReader.ReadFromFile(AFileName: String; +procedure TsCSVReader.ReadFromFile(AFileName: String; APassword: String = ''; AParams: TsStreamParams = []); begin FWorksheetName := ChangeFileExt(ExtractFileName(AFileName), ''); - inherited ReadFromFile(AFilename, AParams); + inherited ReadFromFile(AFilename, APassword, AParams); end; -procedure TsCSVReader.ReadFromStream(AStream: TStream; +procedure TsCSVReader.ReadFromStream(AStream: TStream; APassword: String = ''; AParams: TsStreamParams = []); var Parser: TCSVParser; @@ -227,7 +229,7 @@ var begin Stream := TStringStream.Create(AStrings.Text); try - ReadFromStream(Stream, AParams); + ReadFromStream(Stream, '', AParams); finally Stream.Free; end; diff --git a/components/fpspreadsheet/source/common/fpshtml.pas b/components/fpspreadsheet/source/common/fpshtml.pas index b27fc5411..e56e628ed 100644 --- a/components/fpspreadsheet/source/common/fpshtml.pas +++ b/components/fpspreadsheet/source/common/fpshtml.pas @@ -57,7 +57,8 @@ type public constructor Create(AWorkbook: TsWorkbook); override; destructor Destroy; override; - procedure ReadFromStream(AStream: TStream; AParams: TsStreamParams = []); override; + procedure ReadFromStream(AStream: TStream; APassword: String = ''; + AParams: TsStreamParams = []); override; procedure ReadFromStrings(AStrings: TStrings; AParams: TsStreamParams = []); override; end; @@ -1045,11 +1046,12 @@ begin SetLength(FCurrRichTextParams, 0); end; -procedure TsHTMLReader.ReadFromStream(AStream: TStream; +procedure TsHTMLReader.ReadFromStream(AStream: TStream; APassword: String = ''; AParams: TsStreamParams = []); var list: TStringList; begin + Unused(APassword); list := TStringList.Create; try list.LoadFromStream(AStream); diff --git a/components/fpspreadsheet/source/common/fpsopendocument.pas b/components/fpspreadsheet/source/common/fpsopendocument.pas index e347f8cb4..2edea32a8 100644 --- a/components/fpspreadsheet/source/common/fpsopendocument.pas +++ b/components/fpspreadsheet/source/common/fpsopendocument.pas @@ -165,7 +165,8 @@ type destructor Destroy; override; { General reading methods } - procedure ReadFromStream(AStream: TStream; AParams: TsStreamParams = []); override; + procedure ReadFromStream(AStream: TStream; + APassword: String = ''; AParams: TsStreamParams = []); override; end; { TsSpreadOpenDocWriter } @@ -2422,7 +2423,7 @@ begin end; procedure TsSpreadOpenDocReader.ReadFromStream(AStream: TStream; - AParams: TsStreamParams = []); + APassword: String = ''; AParams: TsStreamParams = []); var Doc : TXMLDocument; BodyNode, SpreadSheetNode, TableNode: TDOMNode; @@ -2445,7 +2446,7 @@ var end; begin - Unused(AParams); + Unused(APassword, AParams); Doc := nil; try diff --git a/components/fpspreadsheet/source/common/fpspreadsheet.pas b/components/fpspreadsheet/source/common/fpspreadsheet.pas index bc3a24547..bf42719c9 100644 --- a/components/fpspreadsheet/source/common/fpspreadsheet.pas +++ b/components/fpspreadsheet/source/common/fpspreadsheet.pas @@ -756,15 +756,15 @@ type destructor Destroy; override; procedure ReadFromFile(AFileName: string; AFormatID: TsSpreadFormatID; - AParams: TsStreamParams = []); overload; + APassword: String = ''; AParams: TsStreamParams = []); overload; procedure ReadFromFile(AFileName: string; AFormat: TsSpreadsheetFormat; AParams: TsStreamParams = []); overload; - procedure ReadFromFile(AFileName: string; + procedure ReadFromFile(AFileName: string; APassword: String = ''; AParams: TsStreamParams = []); overload; procedure ReadFromFileIgnoringExtension(AFileName: string; - AParams: TsStreamParams = []); + APassword: String = ''; AParams: TsStreamParams = []); procedure ReadFromStream(AStream: TStream; AFormatID: TsSpreadFormatID; - AParams: TsStreamParams = []); overload; + APassword: String = ''; AParams: TsStreamParams = []); overload; procedure ReadFromStream(AStream: TStream; AFormat: TsSpreadsheetFormat; AParams: TsStreamParams = []); overload; @@ -8199,7 +8199,7 @@ procedure TsWorkbook.ReadFromFile(AFileName: string; begin if AFormat = sfUser then raise Exception.Create('[TsWorkbook.ReadFromFile] Don''t call this method for user-provided file formats.'); - ReadFromFile(AFilename, ord(AFormat), AParams); + ReadFromFile(AFilename, ord(AFormat), '', AParams); end; {@@ ---------------------------------------------------------------------------- @@ -8209,8 +8209,8 @@ end; @param AFileName Name of the file to be read @param AFormatID Identifier of the file format assumed -------------------------------------------------------------------------------} -procedure TsWorkbook.ReadFromFile(AFileName: string; - AFormatID: TsSpreadFormatID; AParams: TsStreamParams = []); +procedure TsWorkbook.ReadFromFile(AFileName: string; AFormatID: TsSpreadFormatID; + APassword: String = ''; AParams: TsStreamParams = []); var AReader: TsBasicSpreadReader; ok: Boolean; @@ -8219,7 +8219,7 @@ begin raise Exception.CreateFmt(rsFileNotFound, [AFileName]); if AFormatID = sfIDUnknown then begin - ReadFromFile(AFileName, AParams); + ReadFromFile(AFileName, APassword, AParams); exit; end; @@ -8231,7 +8231,7 @@ begin FReadWriteFlag := rwfRead; inc(FLockCount); // This locks various notifications from being sent try - AReader.ReadFromFile(AFileName, AParams); + AReader.ReadFromFile(AFileName, APassword, AParams); ok := true; UpdateCaches; if (boAutoCalc in Options) then @@ -8253,7 +8253,8 @@ end; the extension. In the case of the ambiguous xls extension, it will simply assume that it is BIFF8. Note that it could be BIFF2 or 5 as well. -------------------------------------------------------------------------------} -procedure TsWorkbook.ReadFromFile(AFileName: string; AParams: TsStreamParams = []); +procedure TsWorkbook.ReadFromFile(AFileName: string; APassword: String = ''; + AParams: TsStreamParams = []); var formatID: TsSpreadFormatID; canLoad, success: Boolean; @@ -8293,7 +8294,7 @@ begin success := false; for i:=0 to High(fileformats) do begin try - ReadFromFile(AFileName, fileformats[i], AParams); + ReadFromFile(AFileName, fileformats[i], APassword, AParams); success := true; break; // Exit the loop if we reach this point successfully. except @@ -8310,7 +8311,7 @@ end; Reads the document from a file, but ignores the extension. -------------------------------------------------------------------------------} procedure TsWorkbook.ReadFromFileIgnoringExtension(AFileName: string; - AParams: TsStreamParams = []); + APassword: String = ''; AParams: TsStreamParams = []); var formatID: TsSpreadFormatID; fileformats: TsSpreadFormatIDArray; @@ -8319,7 +8320,7 @@ begin fileformats := GetSpreadFormats(faRead, [ord(sfOOXML), ord(sfOpenDocument), ord(sfExcel8)]); for formatID in fileformats do begin try - ReadFromFile(AFileName, formatID, AParams); + ReadFromFile(AFileName, formatID, APassword, AParams); success := true; break; except @@ -8342,7 +8343,7 @@ procedure TsWorkbook.ReadFromStream(AStream: TStream; begin if AFormat = sfUser then raise Exception.Create('[TsWorkbook.ReadFromFile] Don''t call this method for user-provided file formats.'); - ReadFromStream(AStream, ord(AFormat), AParams); + ReadFromStream(AStream, ord(AFormat), '', AParams); end; {@@ ---------------------------------------------------------------------------- @@ -8352,8 +8353,8 @@ end; @param AFormatID Identifier of the file format assumed. @param AParams Optional parameters to control stream access. -------------------------------------------------------------------------------} -procedure TsWorkbook.ReadFromStream(AStream: TStream; - AFormatID: TsSpreadFormatID; AParams: TsStreamParams = []); +procedure TsWorkbook.ReadFromStream(AStream: TStream; AFormatID: TsSpreadFormatID; + APassword: String = ''; AParams: TsStreamParams = []); var AReader: TsBasicSpreadReader; ok: Boolean; @@ -8366,7 +8367,7 @@ begin inc(FLockCount); try AStream.Position := 0; - AReader.ReadFromStream(AStream, AParams); + AReader.ReadFromStream(AStream, APassword, AParams); ok := true; UpdateCaches; if (boAutoCalc in Options) then diff --git a/components/fpspreadsheet/source/common/fpsreaderwriter.pas b/components/fpspreadsheet/source/common/fpsreaderwriter.pas index c163dbdf0..99e03d43a 100644 --- a/components/fpspreadsheet/source/common/fpsreaderwriter.pas +++ b/components/fpspreadsheet/source/common/fpsreaderwriter.pas @@ -47,8 +47,10 @@ type TsBasicSpreadReader = class(TsBasicSpreadReaderWriter) public { General writing methods } - procedure ReadFromFile(AFileName: string; AParams: TsStreamParams = []); virtual; abstract; - procedure ReadFromStream(AStream: TStream; AParams: TsStreamParams = []); virtual; abstract; + procedure ReadFromFile(AFileName: string; APassword: String = ''; + AParams: TsStreamParams = []); virtual; abstract; + procedure ReadFromStream(AStream: TStream; APassword: String = ''; + AParams: TsStreamParams = []); virtual; abstract; procedure ReadFromStrings(AStrings: TStrings; AParams: TsStreamParams = []); virtual; abstract; end; @@ -112,8 +114,10 @@ type destructor Destroy; override; { General writing methods } - procedure ReadFromFile(AFileName: string; AParams: TsStreamParams = []); override; - procedure ReadFromStream(AStream: TStream; AParams: TsStreamParams = []); override; + procedure ReadFromFile(AFileName: string; APassword: String = ''; + AParams: TsStreamParams = []); override; + procedure ReadFromStream(AStream: TStream; APassword: String = ''; + AParams: TsStreamParams = []); override; procedure ReadFromStrings(AStrings: TStrings; AParams: TsStreamParams = []); override; {@@ List of number formats found in the workbook. } @@ -460,7 +464,7 @@ end; @see TsWorkbook -------------------------------------------------------------------------------} procedure TsCustomSpreadReader.ReadFromFile(AFileName: string; - AParams: TsStreamParams = []); + APassword: String = ''; AParams: TsStreamParams = []); var stream, fs: TStream; begin @@ -482,7 +486,7 @@ begin end; try - ReadFromStream(stream, AParams); + ReadFromStream(stream, APassword, AParams); finally stream.Free; end; @@ -501,11 +505,12 @@ end; @param AData Workbook which is filled by the data from the stream. -------------------------------------------------------------------------------} procedure TsCustomSpreadReader.ReadFromStream(AStream: TStream; - AParams: TsStreamParams = []); + APassword: String; AParams: TsStreamParams = []); var AStringStream: TStringStream; AStrings: TStringList; begin + Unused(APassword); AStringStream := TStringStream.Create(''); AStrings := TStringList.Create; try diff --git a/components/fpspreadsheet/source/common/xlsbiff2.pas b/components/fpspreadsheet/source/common/xlsbiff2.pas index 215936a23..ba836fb52 100644 --- a/components/fpspreadsheet/source/common/xlsbiff2.pas +++ b/components/fpspreadsheet/source/common/xlsbiff2.pas @@ -77,7 +77,8 @@ type public constructor Create(AWorkbook: TsWorkbook); override; { General reading methods } - procedure ReadFromStream(AStream: TStream; AParams: TsStreamParams = []); override; + procedure ReadFromStream(AStream: TStream; APassword: String = ''; + AParams: TsStreamParams = []); override; end; @@ -576,14 +577,14 @@ end; procedure TsSpreadBIFF2Reader.ReadFromStream(AStream: TStream; - AParams: TsStreamParams = []); + APassword: String = ''; AParams: TsStreamParams = []); var BIFF2EOF: Boolean; RecordType: Word; CurStreamPos: Int64; BOFFound: Boolean; begin - Unused(AParams); + Unused(APassword, AParams); BIFF2EOF := False; { In BIFF2 files there is only one worksheet, let's create it } diff --git a/components/fpspreadsheet/source/common/xlsbiff5.pas b/components/fpspreadsheet/source/common/xlsbiff5.pas index 8d0ec3851..b5ce0b012 100644 --- a/components/fpspreadsheet/source/common/xlsbiff5.pas +++ b/components/fpspreadsheet/source/common/xlsbiff5.pas @@ -89,7 +89,8 @@ type procedure ReadXF(AStream: TStream); public { General reading methods } - procedure ReadFromStream(AStream: TStream; AParams: TsStreamParams = []); override; + procedure ReadFromStream(AStream: TStream; APassword: String = ''; + AParams: TsStreamParams = []); override; end; { TsSpreadBIFF5Writer } @@ -925,13 +926,13 @@ begin end; procedure TsSpreadBIFF5Reader.ReadFromStream(AStream: TStream; - AParams: TsStreamParams = []); + APassword: String = ''; AParams: TsStreamParams = []); var OLEStream: TMemoryStream; OLEStorage: TOLEStorage; OLEDocument: TOLEDocument; begin - Unused(AParams); + Unused(APassword, AParams); OLEStream := TMemoryStream.Create; try diff --git a/components/fpspreadsheet/source/common/xlsbiff8.pas b/components/fpspreadsheet/source/common/xlsbiff8.pas index 0995d136f..b760568f4 100644 --- a/components/fpspreadsheet/source/common/xlsbiff8.pas +++ b/components/fpspreadsheet/source/common/xlsbiff8.pas @@ -126,7 +126,8 @@ type procedure ReadXF(const AStream: TStream); public destructor Destroy; override; - procedure ReadFromStream(AStream: TStream; AParams: TsStreamParams = []); override; + procedure ReadFromStream(AStream: TStream; + APassword: String = ''; AParams: TsStreamParams = []); override; end; { TsSpreadBIFF8Writer } @@ -958,13 +959,13 @@ begin end; procedure TsSpreadBIFF8Reader.ReadFromStream(AStream: TStream; - AParams: TsStreamParams = []); + APassword: String = ''; AParams: TsStreamParams = []); var OLEStream: TMemoryStream; OLEStorage: TOLEStorage; OLEDocument: TOLEDocument; begin - Unused(AParams); + Unused(APassword, AParams); OLEStream := TMemoryStream.Create; try // Only one stream is necessary for any number of worksheets diff --git a/components/fpspreadsheet/source/common/xlsxooxml.pas b/components/fpspreadsheet/source/common/xlsxooxml.pas index 70196d19e..89c3f24c0 100644 --- a/components/fpspreadsheet/source/common/xlsxooxml.pas +++ b/components/fpspreadsheet/source/common/xlsxooxml.pas @@ -44,7 +44,7 @@ uses fpszipper, {$ENDIF} fpsTypes, fpSpreadsheet, fpsUtils, fpsReaderWriter, fpsNumFormat, fpsPalette, - fpsxmlcommon, xlsCommon; + fpsxmlcommon, xlsCommon; //, xlsxdecrypter; type @@ -105,7 +105,8 @@ type public constructor Create(AWorkbook: TsWorkbook); override; destructor Destroy; override; - procedure ReadFromStream(AStream: TStream; AParams: TsStreamParams = []); override; + procedure ReadFromStream(AStream: TStream; APassword: String = ''; + AParams: TsStreamParams = []); override; end; { TsSpreadOOXMLWriter } @@ -2352,7 +2353,7 @@ begin end; procedure TsSpreadOOXMLReader.ReadFromStream(AStream: TStream; - AParams: TsStreamParams = []); + APassword: String = ''; AParams: TsStreamParams = []); var Doc : TXMLDocument; RelsNode: TDOMNode; @@ -2374,7 +2375,7 @@ var end; begin - Unused(AParams); + Unused(APassword, AParams); Doc := nil; try diff --git a/components/fpspreadsheet/source/crypto/xlsxdecrypter.pas b/components/fpspreadsheet/source/crypto/xlsxdecrypter.pas new file mode 100644 index 000000000..b2a751bc1 --- /dev/null +++ b/components/fpspreadsheet/source/crypto/xlsxdecrypter.pas @@ -0,0 +1,596 @@ +unit xlsxdecrypter; +{ + Some of the ideas are aquired from http://www.lyquidity.com/devblog/?p=35 + (the `internal` or `default password`): VelvetSweatshop +} +{$ifdef fpc} + {$mode objfpc}{$H+} +// {$mode delphi} +{$endif} + +interface + +uses + Classes + , SysUtils + , sha1 + , DCPrijndael + ; + + const + CFB_Signature = $E11AB1A1E011CFD0; // Compound File Binary Signature + // Weird is the documentation is equal to + // $D0CF11E0A1B11AE1, but here is inversed + // maybe related to litle endian thing?!! + + // EncryptionHeaderFlags as defined in 2.3.1 [MS-OFFCRYPTO] + ehfAES = $00000004; + //ehfExternal = $00000008; + //ehfDocProps = $00000010; + ehfCryptoAPI = $00000020; + + // AlgorithmID + algRC4 = $00006801; + algAES128 = $0000660E; + algAES192 = $0000660F; + algAES256 = $00006610; + + // HashID + hsSHA1 = $00008004; + + // ProviderType + prRC4 = $00000001; + prAES = $00000018; + + + type + + TVersion = packed record + Major : Word; + Minor : Word + end; + + { Defined in Section 2.3.2, 2.3.4.5 [MS-OFFCRYPTO] } + TEncryptionHeader = record + Flags : DWord; { defined in section 2.3.1 [MS-OFFCRYPTO] } + SizeExtra : DWord; { Must be equal to 0 } + AlgorithmID : DWord; { $00006801 -- RC4 } + { $0000660E -- AES128} + { $0000660F -- AES192} + { $00006610 -- AES256} + + HashID : DWord; { $00008004 -- SHA1 } + + KeySize : DWord; { RC4 -- 40bits to 128bits (8-bit increments) } + { AES128 -- 128 bits } + { AES192 -- 192 bits } + { AES256 -- 256 bits } + + ProviderType: DWord; { $00000001 -- RC4 } + { $00000018 -- AES } + + Reserved1 : DWord; { Ignored } + Reserved2 : DWord; { Must be equal to 0 } + CSP_Name : string; + end; + + { Defined in Section 2.3.3 [MS-OFFCRYPTO] } + TEncryptionVerifier = record + SaltSize : DWord; + Salt : array[0..15] of Byte; + EncryptedVerifier : array[0..15] of Byte; + VerifierHashSize : DWord; + EncryptedVerifierHash: array[0..31] of Byte; // RC4 needs only 20 bytes + end; + + // The EncryptionInfo Stream as define in 2.3.4.5 [MS-OFFCRYPTO] + TEncryptionInfo = record + Version : TVersion; + Flags : DWord; + HeaderSize: DWord; + Header : TEncryptionHeader; + Verifier : TEncryptionVerifier; + end; + + { TExcelFileDecryptor } + TExcelFileDecryptor = class + private + FEncInfo : TEncryptionInfo; + FEncryptionKey : TBytes; + + // return empty string if everything done right otherwise the error message. + function InitEncryptionInfo(AStream: TStream): string; + + //CheckPasswordInternal should be called after InitEncryptionInfo + function CheckPasswordInternal( APassword: UnicodeString ): Boolean; + + public + // return empty string if everything done right otherwise the error message. + function Decrypt(inFileName: string; outStream: TStream): string; overload; + function Decrypt(inStream: TStream; outStream: TStream):string; overload; + + // made this private because I don't know if it'll work with other passwords + function Decrypt(inFileName: string; outStream: TStream; APassword: UnicodeString): string; overload; + function Decrypt(inStream: TStream; outStream: TStream; APassword: UnicodeString): string; overload; + + // return true if the password is correct. + function CheckPassword(AFileName: string; APassword: UnicodeString): Boolean; + function CheckPassword(AStream: TStream; APassword: UnicodeString): Boolean; + + function isEncryptedAndSupported(AFileName: string): Boolean; + function isEncryptedAndSupported(AStream: TStream): Boolean; + end; + + + +implementation + +uses + fpolebasic + ; + +procedure ConcatToByteArray(var outArray: TBytes; Arr1: TBytes; Arr2: TBytes); +var + LenArr1 : Integer; + LenArr2 : Integer; +begin + LenArr1 := Length(Arr1); + LenArr2 := Length(Arr2); + + SetLength( outArray, LenArr1 + LenArr2 ); + + if LenArr1 > 0 then + Move(Arr1[0], outArray[0], LenArr1); + + if LenArr2 > 0 then + Move(Arr2[0], outArray[LenArr1], LenArr2); +end; + +procedure ConcatToByteArray(var outArray: TBytes; AValue: DWord; Arr: TBytes); +var + LenArr : Integer; +begin + LenArr := Length(Arr); + + SetLength( outArray, 4 + LenArr ); + + Move(AValue, outArray[0], 4); + + if LenArr > 0 then + Move(Arr[0], outArray[4], LenArr); +end; + +procedure ConcatToByteArray(var outArray: TBytes; Arr: TBytes; AValue: DWord); +var + LenArr : Integer; +begin + LenArr := Length(Arr); + + SetLength( outArray, 4 + LenArr ); + + if LenArr > 0 then + Move(Arr[0], outArray[0], LenArr); + + Move(AValue, outArray[LenArr], 4); +end; + +function TExcelFileDecryptor.InitEncryptionInfo(AStream: TStream): string; +var + EncInfoStream: TMemoryStream; + OLEStorage: TOLEStorage; + OLEDocument: TOLEDocument; + FileSignature: QWord; + Pos : Int64; + + Err : string; +begin + Err := ''; + + if not Assigned(AStream) then + Exit( 'Stream is null' ); + + AStream.Position := 0; + FileSignature := AStream.ReadQWord; + if FileSignature <> QWord(CFB_Signature) then + Exit( 'Wrong file signature' ); + + EncInfoStream := TMemoryStream.Create; + try + OLEStorage := TOLEStorage.Create; + try + OLEDocument.Stream := EncInfoStream; + AStream.Position := 0; + OLEStorage.ReadOLEStream(AStream, OLEDocument, 'EncryptionInfo'); + if OLEDocument.Stream.Size = 0 then + raise Exception.Create('EncryptionInfo stream not found.'); + + EncInfoStream.Position := 0; + + { Major Version: $0002 = Excel 2003 + $0003 = Excel 2007 | 2007 SP1 + $0004 = Excel 2007 SP2 (not sure about 2010 | 2013) } + FEncInfo.Version.Major := EncInfoStream.ReadWord; + if (FEncInfo.Version.Major <> 3) and (FEncInfo.Version.Major <> 4) then + raise Exception.Create('File must be created with 2007 or 2010'); + + { Minor Version: must be equal to $0002 } + FEncInfo.Version.Minor := EncInfoStream.ReadWord; + if FEncInfo.Version.Minor <> 2 then + raise Exception.Create('Incorrect File Version'); + + FEncInfo.Flags := EncInfoStream.ReadDWord; + FEncInfo.HeaderSize := EncInfoStream.ReadDWord; + + /// + /// ENCRYPTION HEADER + /// + Pos := EncInfoStream.Position; + With FEncInfo.Header do + begin + Flags := EncInfoStream.ReadDWord; + if (Flags and ehfCryptoAPI) <> ehfCryptoAPI then + raise Exception.Create('File not encrypted'); + if (Flags and ehfAES) <> ehfAES then + raise Exception.Create('Encryption must be AES'); + + SizeExtra := EncInfoStream.ReadDWord; + if SizeExtra <> 0 then + raise Exception.Create('Wrong Header.SizeExtra'); + + AlgorithmID := EncInfoStream.ReadDWord; + if ( AlgorithmID <> algAES128 ) + and( AlgorithmID <> algAES192 ) + and( AlgorithmID <> algAES256 ) + //and( AlgorithmID <> algRC4 ) // not used by ECMA-376 format + then + raise Exception.Create('Unknown Encryption Algorithm'); + + HashID := EncInfoStream.ReadDWord; + if HashID <> hsSHA1 then + raise Exception.Create('Unknown Hashing Algorithm'); + + KeySize := EncInfoStream.ReadDWord; + if ( (AlgorithmID = algAES128) and (KeySize <> 128) ) + or( (AlgorithmID = algAES192) and (KeySize <> 192) ) + or( (AlgorithmID = algAES256) and (KeySize <> 256) ) + //or( (AlgorithmID = algRC4) and (KeySize < 40 or KeySize > 128) ) + then + raise Exception.Create('Incorrect Key Size'); + + ProviderType:= EncInfoStream.ReadDWord; + if ( ProviderType <> prAES ) + //and( FEncInfo.Header.ProviderType <> prRC4 ) + then + raise Exception.Create('Unknown Provider'); + + Reserved1 := EncInfoStream.ReadDWord; + Reserved2 := EncInfoStream.ReadDWord; + if Reserved2 <> 0 then + raise Exception.Create('Reserved2 must equal to 0'); + + //CSP_Name := Not needed + // CSP: Should be Microsoft Enhanced RSA and AES Cryptographic Provider + // or Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype) + //Skip CSP Name + EncInfoStream.Position := Pos + FEncInfo.HeaderSize; + end; + + /// + /// ENCRYPTION VERIFIER + /// + with FEncInfo.Verifier do + begin + SaltSize := EncInfoStream.ReadDWord; + if FEncInfo.Verifier.SaltSize <> 16 then + raise Exception.Create('Incorrect salt size'); + + EncInfoStream.ReadBuffer(Salt[0], SaltSize); + EncInfoStream.ReadBuffer(EncryptedVerifier[0], SaltSize); + + VerifierHashSize := EncInfoStream.ReadDWord; + + if FEncInfo.Header.ProviderType = prAES then + EncInfoStream.ReadBuffer( EncryptedVerifierHash[0], 32); + { for RC4 + else if FEncInfo.Header.ProviderType = prRC4 then + EncInfoStream.ReadBuffer( EncryptedVerifierHash[0], 20); } + end; + + Err := ''; + except + on E: Exception do + Err := E.Message; + end; + finally + if Assigned(OLEStorage) then + OLEStorage.Free; + + EncInfoStream.Free; + end; + + Result := Err; +end; + +function TExcelFileDecryptor.CheckPasswordInternal(APassword: UnicodeString): Boolean; +var + AES_Cipher: TDCP_rijndael; + + ConcArr : TBytes; + LastHash: TSHA1Digest; + + Iterator, i: DWord; + + X1_Buff: array[0..63] of byte; + X2_Buff: array[0..63] of byte; + X1_Hash: TSHA1Digest; + X2_Hash: TSHA1Digest; + + EncryptionKeySize : Integer; + + Verifier : array[0..15] of Byte; + VerifierHash: array[0..31] of Byte;// Needs only 20bytes to hold the SHA1 + // but needs 32bytes to hold the decrypted hash +begin + // if no password used, use microsoft default. + if APassword = '' then + APassword := 'VelvetSweatshop'; + + //// [MS-OFFCRYPTO] + //// 2.3.4.7 ECMA-376 Document Encryption Key Generation + + // 1.1.Concat Salt and Password + // Calculate SHA1(0) = SHA1(salt + password) + ConcatToByteArray( ConcArr + , FEncInfo.Verifier.Salt + , TEncoding.Unicode.GetBytes(APassword)); + LastHash := SHA1Buffer( ConcArr[0], Length(ConcArr) ); + + // 1.2.Calculate SHA1(n) = SHA1(iterator + SHA1(n-1) ) -- iterator is 32bit + for Iterator := 0 to 49999 do + begin + ConcatToByteArray(ConcArr, Iterator, LastHash); + LastHash := SHA1Buffer( ConcArr[0], Length(ConcArr) ); + end; + + // 1.3.Claculate final hash, SHA1(final) = SHA1(H(n) + block) -- block = 0 (32bit) + ConcatToByteArray(ConcArr, LastHash, 0); + LastHash := SHA1Buffer( ConcArr[0], Length(ConcArr) ); + + + //// 2.Derive the encryption key. + // 2.1 cbRequiredKeyLength for AES is 128,192,256bit ?!!! must be < 40bytes + // 2.2 cbHash = 20bytes ( 160bit),, length of SHA1 hash + // 2.3 + 2.4 Claculate X1 and X2 the SHA of the generated 64bit Arrays. + + // FillByte(X1_Buff[0], 64, $36); + // FillByte(X2_Buff[0], 64, $5C); + for i := 0 to 19 do + begin + X1_Buff[i] := LastHash[i] xor $36; + X2_Buff[i] := LastHash[i] xor $5C; + end; + for i := 20 to 63 do + begin + X1_Buff[i] := $36; + X2_Buff[i] := $5C; + end; + + X1_Hash := SHA1Buffer( X1_Buff[0], Length(X1_Buff) ); + X2_Hash := SHA1Buffer( X2_Buff[0], Length(X2_Buff) ); + + // 2.5 Concat X1, X2 -> X3 = X1 + X2 (X3 = 40bytes in length) + //ConcatToByteArray( ConcArr, X1_Hash, X2_Hash ); + + // 2.6 Let keyDerived be equal to the first cbRequiredKeyLength bytes of X3. + // We'll fill the Encryption key on the fly, so we won't need X3 + // This Key (FEncryptionKey) is used for decryption method + EncryptionKeySize := FEncInfo.Header.KeySize div 8; // Convert Size from bits to bytes + SetLength(FEncryptionKey, EncryptionKeySize); + if EncryptionKeySize <= 20 then + begin + Move(X1_Hash[0], FEncryptionKey[0], EncryptionKeySize); + end + else + begin + Move(X1_Hash[0], FEncryptionKey[0], EncryptionKeySize); + Move(X2_Hash[0], FEncryptionKey[20], EncryptionKeySize-20); + end; + + //// 2.3.4.9 Password Verification + // 1. Encryption key is FEncryptionKey + + // 2. Decrypt the EncryptedVerifier + AES_Cipher := TDCP_rijndael.Create(nil); + AES_Cipher.Init( FEncryptionKey[0], FEncInfo.Header.KeySize, nil ); + AES_Cipher.DecryptECB(FEncInfo.Verifier.EncryptedVerifier[0] , Verifier[0]); + + // 3. Decrypt the DecryptedVerifierHash + AES_Cipher.Burn; + AES_Cipher.Init( FEncryptionKey[0], FEncInfo.Header.KeySize, nil ); + AES_Cipher.DecryptECB(FEncInfo.Verifier.EncryptedVerifierHash[0] , VerifierHash[0]); + AES_Cipher.DecryptECB(FEncInfo.Verifier.EncryptedVerifierHash[16], VerifierHash[16]); + AES_Cipher.Free; + + // 4. Calculate SHA1(Verifier) + LastHash := SHA1Buffer(Verifier[0], Length(Verifier)); + + // 5. Compare results + Result := (CompareByte( LastHash[0], VerifierHash[0], 20) = 0); +end; + +function TExcelFileDecryptor.Decrypt(inFileName: string; outStream: TStream + ): string; +begin + Result := Decrypt(inFileName, outStream, 'VelvetSweatshop' ); +end; + +function TExcelFileDecryptor.Decrypt(inFileName: string; outStream: TStream; + APassword: UnicodeString): string; +Var + inStream : TFileStream; +begin + if not FileExists(inFileName) then + Exit( inFileName + ' not found.' ); + + try + inStream := TFileStream.Create( inFileName, fmOpenRead ); + + inStream.Position := 0; + Result := Decrypt( inStream, outStream, APassword ); + finally + inStream.Free; + end; +end; + +function TExcelFileDecryptor.Decrypt(inStream: TStream; outStream: TStream + ): string; +begin + Result := Decrypt(inStream, outStream, 'VelvetSweatshop' ); +end; + +function TExcelFileDecryptor.Decrypt(inStream: TStream; outStream: TStream; + APassword: UnicodeString): string; +var + OLEStream: TMemoryStream; + OLEStorage: TOLEStorage; + OLEDocument: TOLEDocument; + + AES_Cipher : TDCP_rijndael; + inData : TBytes; + outData : TBytes; + StreamSize : QWord; + KeySizeByte: Integer; + + Err : string; +begin + if (not Assigned(inStream)) or (not Assigned(outStream)) then + Exit( 'streams must be assigned' ); + + Err := InitEncryptionInfo(inStream); + if Err <> '' then + Exit( 'Error when initializing Encryption Info'#10#13 + Err ); + + if not CheckPasswordInternal(APassword) then + Exit( 'Wrong password' ); + + // read the encoded stream into memory + OLEStream := TMemoryStream.Create; + try + OLEStorage := TOLEStorage.Create; + try + OLEDocument.Stream := OLEStream; + inStream.Position := 0; + OLEStorage.ReadOLEStream(inStream, OLEDocument, 'EncryptedPackage'); + if OLEDocument.Stream.Size = 0 then + raise Exception.Create('EncryptedPackage stream not found.'); + + // Start decryption + OLEStream.Position:=0; + outStream.Position:=0; + + StreamSize := OLEStream.ReadQWord; + + KeySizeByte := FEncInfo.Header.KeySize div 8; + SetLength(inData, KeySizeByte); + SetLength(outData, KeySizeByte); + + AES_Cipher := TDCP_rijndael.Create(nil); + AES_Cipher.Init( FEncryptionKey[0], FEncInfo.Header.KeySize, nil ); + + While StreamSize > 0 do + begin + OLEStream.ReadBuffer(inData[0], KeySizeByte); + AES_Cipher.DecryptECB(inData[0], outData[0]); + + if StreamSize < KeySizeByte then + outStream.WriteBuffer(outData[0], StreamSize) // Last block less then key size + else + outStream.WriteBuffer(outData[0], KeySizeByte); + + if StreamSize < KeySizeByte then + StreamSize := 0 + else + Dec(StreamSize, KeySizeByte); + end; + + AES_Cipher.Free; + + ///// + except + Err := 'EncryptedPackage not found'; + end; + finally + if Assigned(OLEStorage) then + OLEStorage.Free; + + OLEStream.Free; + end; + Exit( Err ); +end; + +function TExcelFileDecryptor.isEncryptedAndSupported(AFileName: string + ): Boolean; +var + AStream : TStream; +begin + if not FileExists(AFileName) then + Exit( False ); + + try + AStream := TFileStream.Create( AFileName, fmOpenRead ); + + AStream.Position := 0; + //FStream.CopyFrom(AStream, AStream.Size); + + Result := isEncryptedAndSupported( AStream ); + finally + AStream.Free; + end; +end; + +function TExcelFileDecryptor.isEncryptedAndSupported(AStream: TStream + ): Boolean; +begin + if not Assigned(AStream) then + Exit( False ); + + if InitEncryptionInfo(AStream) <> '' then + Exit( False ); + + Result := True; +end; + +function TExcelFileDecryptor.CheckPassword(AFileName: string; + APassword: UnicodeString): Boolean; +var + AStream : TStream; +begin + if not FileExists(AFileName) then + Exit( False ); + + try + AStream := TFileStream.Create( AFileName, fmOpenRead ); + + AStream.Position := 0; + + Result := CheckPassword( AStream, APassword ); + finally + AStream.Free; + end; +end; + +function TExcelFileDecryptor.CheckPassword(AStream: TStream; + APassword: UnicodeString): Boolean; +begin + if not Assigned(AStream) then + Exit( False ); + + AStream.Position := 0; + if InitEncryptionInfo(AStream) <> '' then + Exit( False ); + + Result := CheckPasswordInternal(APassword); +end; + +end. + diff --git a/components/fpspreadsheet/source/crypto/xlsxooxml_crypto.pas b/components/fpspreadsheet/source/crypto/xlsxooxml_crypto.pas new file mode 100644 index 000000000..7d23a6f16 --- /dev/null +++ b/components/fpspreadsheet/source/crypto/xlsxooxml_crypto.pas @@ -0,0 +1,68 @@ +unit xlsxooxml_crypto; + +interface + +uses + Classes, + fpstypes, xlsxooxml, xlsxdecrypter; + +type + TsSpreadOOXMLReaderCrypto = class(TsSpreadOOXMLReader) + public + procedure ReadFromStream(AStream: TStream; APassword: String = ''; + AParams: TsStreamParams = []); override; + end; + +var + sfidOOXML_Crypto: TsSpreadFormatID; + + +implementation + +uses + fpsReaderWriter; + +procedure TsSpreadOOXMLReaderCrypto.ReadFromStream(AStream: TStream; + APassword: String = ''; AParams: TsStreamParams = []); +var + ExcelDecrypt : TExcelFileDecryptor; + DecryptedStream: TStream; +begin + ExcelDecrypt := TExcelFileDecryptor.Create; + try + AStream.Position := 0; + if ExcelDecrypt.isEncryptedAndSupported(AStream) then + begin + DecryptedStream := TMemoryStream.Create; + try + ExcelDecrypt.Decrypt(AStream, DecryptedStream, APassword); + // Discard encrypted stream and load decrypted one. + AStream.Free; + AStream := TMemoryStream.Create; + DecryptedStream.Position := 0; + AStream.CopyFrom(DecryptedStream, DecryptedStream.Size); + AStream.Position := 0; + finally + DecryptedStream.Free; + end; + end; + finally + ExcelDecrypt.Free; + AStream.Position := 0; + end; + + inherited; +end; + + +initialization + + // Registers this reader/writer for fpSpreadsheet + sfidOOXML_Crypto := RegisterSpreadFormat(sfUser, + TsSpreadOOXMLReaderCrypto, nil, + STR_FILEFORMAT_EXCEL_XLSX, 'OOXML', [STR_OOXML_EXCEL_EXTENSION, '.xlsm'] + ); + +end. + +end. diff --git a/components/fpspreadsheet/source/laz_fpspreadsheet_crypto.lpk b/components/fpspreadsheet/source/laz_fpspreadsheet_crypto.lpk new file mode 100644 index 000000000..450c0e251 --- /dev/null +++ b/components/fpspreadsheet/source/laz_fpspreadsheet_crypto.lpk @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<CONFIG> + <Package Version="4"> + <PathDelim Value="\"/> + <Name Value="laz_fpspreadsheet_crypto"/> + <Type Value="RunTimeOnly"/> + <CompilerOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <SearchPaths> + <OtherUnitFiles Value="crypto"/> + <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)\"/> + </SearchPaths> + </CompilerOptions> + <Description Value="Encryption / decryption support for FPSpreadsheet"/> + <Version Major="1" Minor="9"/> + <Files Count="2"> + <Item1> + <Filename Value="crypto\xlsxdecrypter.pas"/> + <UnitName Value="xlsxdecrypter"/> + </Item1> + <Item2> + <Filename Value="crypto\xlsxooxml_crypto.pas"/> + <UnitName Value="xlsxooxml_crypto"/> + </Item2> + </Files> + <RequiredPkgs Count="3"> + <Item1> + <PackageName Value="dcpcrypt"/> + </Item1> + <Item2> + <PackageName Value="laz_fpspreadsheet"/> + </Item2> + <Item3> + <PackageName Value="FCL"/> + </Item3> + </RequiredPkgs> + <UsageOptions> + <UnitPath Value="$(PkgOutDir)"/> + </UsageOptions> + <PublishOptions> + <Version Value="2"/> + </PublishOptions> + </Package> +</CONFIG>