z`~B)dS_%=)}L`8y>ekbQV5Ui9Itu0pfyJ9uSVe3v&zwd&y!sU3U{1htoFSt#pqpLE!>@e6IsJGU$=Pq+6ZPU&n#$bActT3HF+kTX5 ztW^`;;J9VvL9S7M7_Zul(7XYOq=0P5G+E*~bO!h&;4nnMWy$5w7lcFwN(5#yx@x*) z8=eJ>YgC_(?*iVgMJhf!MsO9l$-Y<<8slk5EF*aV>QWaXf#Tz^ovn(wz=zc#m5oQ{ zktD(S+yM_Q5z1(gk>`gn5C(6(QLmaJO6jU4=*qiG4(*;JUQuEqCFN7OoR<$ItC6s$ zt#^l6PaMquaxRz(sClFm*?Y6R!R5P5aqmv7Fr5^Nmez6!4{LXBZVNd1-M8!_7NU&q zT%(dNC4dp7dnPtB=o@OCS8&fEF^C*U-E@JspW6Th`uEl-9`{$rDnM&E+AX-+M?-rB zRHI0mxo%-j8!(8rpV_g! _;s{nrqw!L| Q5S+$V$5&qgE8>T9ZOk9m5Z+Tm
Ij zb5U6x^7C16)oG>H8`{rg^^{HsOGa;|p>O2k2ypoxSbst5Y?$$hfuMp+wIpCkb?+xz z3v<}P;w&Fx(g;4o@RtK9WudSNw$7h>Kd%y*{VOAm3CJ)s>oRIDNpt(rw5jCr&`u%9 zKh*cZt~I{u7xrOaw1+!UuHcJhCSZG8I@|UWg2iz7ZC)K=(upj(oN>1nle?Eg2Tn@G zC6Ap`|Hpf;*&uTJZQK#}Xp(V)pK<)6pb)S<*$}8sMQ 63##-h?;MusE8Xb^Eb=414`t~$($84(6)YO<(;t|$KBT}TWwVbkZOhF1R3Zg zE#-Ty#3sPf#on~5)9)S0fnBVJ)?L5ab?zPM#n=pkYSs?JJjXauvk&9|ywfIpyG)D2 z_{jKm*30k`2nf?M)9U3X!N!NqOlLOi7!vCYf|qzDVbWDvOU~ox)zLL^M&tTTsYTvn zXXmKnerWR$w#Dc9*3a%R#mz=U(TyrBL0N#+g2SCsZDSJ8bcm~IeB{8fJv$35$STt& ze`WfI@mE1dFM~S4K7i51sHUUDUw{AoA6LJzB4P(*+nYQ@Jrl*FL5mZBTj~m0xk@-l z?2XYp`Rv;&i|xWl^Xh5cIz}-FiFJ(ZowR6#=jB1k?TZVF00(YAVo7eQT?Y J`K+maA(uDe*{CkZ_5+`z=}HIH)D@y?S(VAeZ8PB? zFAOZ7<*|{}7`bdF&DV(aV-u`(JwM@aa6C3Ti522y&Z ;pgh~5_x!IgI<_@xrweTFdhiqA&yo9!L5PspkMUR{|+zaAMwb`e{o>QjVE z_XmY`xiZ&u>>}xl&)iZRw85?zNAzj%G6SJw@%6W}TbK470>Pc1F| zr&%b2Kapf-EIHl4fx$u!jKLA1-@Z~{=Vi^OhE1Jga8cjiCRWJGe*>(0b|lUyAG!(U zzw)1Tx8aa+l@QEms=MQ#oZ&L%q>*$s`!zcacPhMImYl%-5aq72NZ2wSn-WW)+4C$e z9IAn|=>?p?8dW#k^Nn%M?G*j~uOPK Js@Fj|JNH?I9rtR=Zps~qBRko6D3hDQKpI05JeadB;~A1sOynSE07 zl{u*6-TEAZ#s_FA!Y9ihsm5JqpW97}YFbLqoXBLN=>lWjKPNR)12`b&a#yaJkPSs_ zC_Tdr=!IH5jVwttG_su#_&k69V9tS3?3baw9AI>G7S)pk)Lha$wqx{;W37AJAFp=` zfM{IoXHaaDi^r{q9aPFmJ%Zr5sRuQ@{ & zIVS~`M>0cDkfS)QSGLYnr{(qIM3h0`CmjV_`AGf)OW|Y?;ZHd>)9)p;TMH>kQj0OT zUsK`}U%Zj?d&7w!zsPv@ejk?OWjVv1jT7L|z;ENj7R$f8@a&l}CTQ)`w(Nu@2J`iy zmJoDwP%E!f`(? `A?^Pl1!mUqLUNAW`v5U9KaL8?-Q?$I;KfCF{>Ic-0V$x zZ6CLn 9J}HO5Z0AT1MO&c$j4AnI&nQ)m z$5R=z!~u`hNg@IIbOxXkeTh@V%kIoedz%!|XJ-q#t$2Gg%S?t3I0`C${Gy+VDu5S> z&QlxO&SV11vKHLatmb`4F> {9A5D1i%Eq)(>a=#C+RKlFO8dMpNlZ=iY4PJ>{I^|jwy& IU^G0^8;2vMAyqP^HfktR n_B5U~(kIKyws^_PO~5{ykiCMgly_FO3d%}Xac*~kIWW}ZoR=z ztuBCOTsc5i$68ibBZ;ETlk}dAy@Hm8FH++;ZXmu0S-C}ZT8(uNXPqG$W(q7}x2Mgw zg#1M0+sAy;eWCuHF^@lX+(21?TrjTKru^ ^(U3Mnh*f(6ZJ+h!O`BW5!*bJ z+%`s~7=kX?$SAq E_Y_x;P=#0A|}QYx7HS2L}jDm2N+#Ltpsc-Vu@J z1!IYsp{DNRu^54m= +W;J@Zl7#9%4d035nA^#8-p0(>P@^j_)annlf^ zJm9{#aVWzTUgYvy4B>uB_>56LKjilQ@U|)2mMG9y+Yb8oa>r-k_kymn(Vf*_)=1B~ z_hcTa!*Q=QkW#W5rvgfSi@gbdP1GOHZf=Z(2TN6-2PW8q p^j{%FZ(e-9*&=osp5HEikwM^n#EqBN%>)Hmw#h1f!`Q?3C|JMaKlAVN=tH?X zM-z?dv3;o}0Tm<0UMHE9k7?cbI!$Ve`Z#&>fQX6=xL+U7k<5?R*tS~`=o(-&D!qdl z(msYrk2)5$N_V=XE~IWolt?UfMBtHGZ>?;XHHj ML%LRSOvG;T0Y60oz;skt6~m5jFJ__4}RYo;k14hCxEfUt7aESxI1a9gR`g z>RlR6Q~V1GISh>kl3kq+(nYnFFB>Kl;~PiNL!px)9|U&(0@dY4^2E*#O}7k5%mGdu zm@VH+7*Fos^UoFL`!oGOF(+iX#yD>o{*Q}CfsGIQBKhbpM&udyTzCvh@;KfSXt2+- zmDi3{Al~`+meH!aW9GT{(WnOJn2sVtG2el6X=z&5cTy4LK@3GuX*9o24?EazSQ}u_ zjLYt!d)qr+r&%KCojY4PJP03G+E?_fo>Au0+x>S6yzn3U%Ioe3|3e<>m4ikEZfKIW zyrW_~!R##l-ozpE9qjt!nguJ`pI9r=11;O$y9_XvnNat8hteLUT@#1|*%Y3A#cK h61^z~9 znJGG`e3AT}yqvlf%f%#F_;?05d2kjCJ*(Wfu-faN*DQh`sfq=#R`q8|={SQ)Fr{(< zjo|Z $OM k`nTE!dv7 zMw85>=yc5yy}*Ku$u>JRc5_^HX?Es~7(8uewG+s!up9e+&oi~8*+4NlVrCIImaK?k zC&Cr UFPNsc+Gg(&C*mJ^HFS6J6z*78cs#UmWs<%iS*g^h} zLb0O_MUE|xEUqA^Z;t!!oVczAakXHS5Ipjoadpr;C1IiQq)znzU0N~ih)+EH!u_ch zT4pkaPB`M|VHwjnEP{w8M>$bp)-y~+h|HsGER9m1sSBIqs$w3Z-OTZo3BX!3iv5q~ zvU`gS@eLioV2_zFg9ETDL1{&K4p}i&Vs66>M6jvRICm?WPTVmVe882H$yPWQX`*tL zG0qRP`HSQ_=!U!D`Y-J);~(3r9~mlW>z-Pj@D9^*LUFn%kdE^M_QFI9*Sh_p^Lgk+ ze95<0&`-MnGlIv2w`~%R-a cwMd@n8%qLH VR!%LR~P9~96sG5tU z1JXCv){Z#+ec6m0e-DDV>c$CMl6IC=baFvBPemkyzM4y(g(gWI_2T%c5)Pd~()CZl zs~6JfTqc&`rGf_J;Yw5#SvG>=^=JmzFQ!v&wN=Pwq+!gyI^Fvy#lh@|=erp9lq+eH z%9roh5RGhFjNQKuPpir_VUQN`${5W)+IZ};Hk(~nyzfiJSw2QL {5oFrQ% z+G-r8CYB0ayd{&CVeGUjA5a6FF+HSN4d}1{ILre@Lo}OMq+2zw!*%;TRF!B8c4^kM zhBan{X^;ApYPRWDHW)1bJ&d+9b;0){+w5PYRo2Z9shP6DNvKN?)r%dqr{=OzARW}D zISSQ^JWICNyjA#+hfMN@-+WkRAd9iyO7ql`%s=C-R^J9IZG1_ivLA Tl2U=FQ4#Wh9Mphe5uJzjgr=qkbv&F{%ROqXcTZ4+ypLXRCmfN2EB3?L?oI;=x?p zgwWf>2)@s4J|Lm)ayapfPVhqsX 3SyzH1EGzrP ?OlBprizoi;PB>`UozCudOX=dMb!KA{t)W?<1WC;$0%QCL zaQq(EFzAs;&;8_kngh5uL^Ejmrn&@&DL47(A2e#>Z*Jrky|Qf*v!RSfNm=;(uS&T^ zr8& g; zl&IV64Z vo?_#ZlP#$Ny0o;7Nt@ zdr962+#{tSl=mn}gF`#jz#@U9-}3ym`8b#Fg`-arC{&*H))nSYqjdnarz~HWN0kXw z3?22X{3(LrL3D6UZLQw=p0ZGMnN5v|n5(EUcY_Gffd$+Gg#$thNp&Fa3_Yd%{c#Po zZHlS+31Z d{NVZ=)@@@zna{>iV@Kxbc(|n53?5C-E4|`L>&Zb}J9kGunm9 zb}@RXpFsX@N9y4JGO`d44}|+IUS^+`J|ct>FZ{&E#JM4t;PIn%YyOUGgb=_H3-_>g zzjq0b8D_8|MHwctQc~3fP=H+14}NS6E|Koy7iX7?oo*c~f{*kZ8T-*Mm?4GWjm9l) zq~OHVo 6PN@)5XvrM^ 1l~QcP z4h4fT3Bbs?=W82=S~xOu26EfmQQZ5iCHh}fz4Nw|0U;ViVwyaX4Av!$#sQAYY)VMA zgaV{Tp0P}Q@ZD3mJf*>c)RSLsLXBvh{F qk TXbo?_J6%y$_mVFd5lI4S&e-5t;p|<3BL!zESy8Vf`fZ_xtQuRu~sb5KC6Ruf(!+ z9|zm{i4Ay|-$5jxC?8Yqb5{1o2F14Q&E(tS8uga5I1zDr2MG8;jEE}_uyw~mC?x_q zhUfS}d6f8VRxGj0xgvRq-ao0vxKUXhr~UlY+45w|&Q0OyM1ZLef`hZ-A`2(IBjBdf zr@uzldY-> {@9_*qML &K7z%cKLTiFCz z7>nXYFolpeY`I+I OD 8FXW^`A%6Cxh4bB{S3`yQc<>_1{iV6Xt>IArIE)A_? z%=)M7iBo%X@*(!-kh-uV$2p8J^7ZU>DOFBj8z-wtMggk+nGU>^CAK=2e&CHn^cc8y z<759>L}`tIM&fGQ!6Owg(lBnpPmd6U^#Wy)QKrv(`^{4CE^H;owjQ)Ba_u^1S~1#r z9Y6VMA3&O*!Zh5IGlNiP-0+Zpu}ocJ)jpn(8l~iw3>~~#1dD-oj=V#{36(+z1G4_h zT8Gtqi_YRl+TzgEO< 0!h8 z;?qbycPJx}0>MBXU*NYp1ZcGvxRj$tv@j+o8JWrAhlecLsBjmSUVVO@;7y21q|be< zCW0M7FxS@W;;4;dX{gLW|Hn!fNcQgH{L^=tYqE7(<;jeBzA9-Q7P&`hSKpcfbdN zrtP=l87nJL{F|@HSd0mzt9(xj-B^6qnS1cgVoBb@N>O76 o@XkDAj{YZhypb7v!i^T2sUB2A%}< zow^0WTgLbKbsP(k<_~m<5x2)C3vonpA<+OFXp<)V3RerQ5THUbS5s>0Ol)_uFitAk z&VROB;zrephsS{AUZ|@kJ$e!#GzO$U_E;R63c2e3j22)Qd|60ksE!IFL&zGP98?oc zo=Rg!|5{rmKi%;V{SrGTjjG@!UfPyeA?TwG9D%M_ C3FmPhP*<@JjRN&S>l$Ro^T mSM0ZUS^WW%tE_<()Y9MHM# IE_vtPk{qMH+yoh9%9-Ng#*h*B z%8`zyKDvA*tZf2&gG8UpO2wOOax4G&){yZEkNtHOaJgvQ0F-_}8L;_>D)`UFDJ=+t zo#VoqPImTTwWSZybO+A)gkEJSWP{R!BvT|=Q}d%jY$EqLOb${0?yA@7BXuat?Y^O^ zrA_|8Xof#oJC4dOqQ!w>Teqd$34inXSVRg1$H-SAGLsP8@jy@r?wGQdOP?rCIvm19 zi|*zDh-sK!J?9-Aoz=HccO_7On31csNIIf?3&%JWn%!gihFd&XHV$w#ro|TI80}5; z2X4syP<_No*Ormezig{%OPN u|r#{(ThD2TT2&`r%zqoh{4hbkEZ=So?$ zR6=w9+hx`8$fy{LtiP}F?pvJkGwkdj`#&;r#@FyeFQ%_wb4NzkvCL;$Zl@A!OrL1u zuNfhTl6_w*!)bY^2Y9NgTxUos=7goyn8r-69&HGx--uv^@Efol`e%qs*3>LFP)x!o zcr-;hyL;11cWFc$XM0@-JKI71E{|Csz5FT311ZPeVEgr}yNu4RLk)it)#4G) eYP1;FlrPvNJTu`&uXfmw2*4V#Hl#p_2AX$P KIUQA`MtEzeYdj61aT=SFwfG47 zEE%+6MQv^s(r&Uxhhk-dL!KA*@qQg#5O3xpNt$;3J+iTTOj7@)U`?D1pl{DRfqXb9 z6^pgcv5Dm9HNl(1JH=wmA|UNo5Fm%t-l(Bb27&x>RN{c)B?*c>?Kr6cmjzunpUMR- ziB;fGK({`?Vo?uAVg#8Z =S2Xd;sDQZah_9Ip8(c_l5YE2@0>wSEglqSQB1+ z*@1>dx@U1HKUYN7aK#O#lFroSW@-vPD+8M!96X`PzP0;TP{}2U|5}_os)}X)e?b-t z1u0fNE4VWMQ{eAYOjcjS>8Q#l=nH2T@N);5$xI0{5B$Ns7)ygo2JJ=Ei>m_5>fG5b zuYipz{w$Q^^4eNQ#pM(Qr z@_XxvMdV<7I z;EN4S@gcdr$mak23a3O) -bfa53$Xx;mVYJ5bW Qa$KG#N;8c~fmFAgwi^Ul6;{4BEKD)s+eE50; z6@-QtOQv^Vd`{>y9RDOE6%V2X_GA|(0N;Wi+79Z(jr0#CBbI%!7?;ST{ujlF)Q8jY z%cS~+HvjS(Q;D8E1_?@gNvZr-)Pr-k2OGK8>Rz00G#AgD09rMJuB0B?lmZrLRe+sO zhil+(P{|{fAnD>qs2Vh7VXTYQAZoWYvWS>@Hki!brV&eyl<@u^esXd=h%_gGu(k BIi+9V`MiBB3zx1E7%f$ZVo%QTeD1Gkq&EZz>2Q<9$8%GMQS60{+XBAP1~7hnOs zIe#io{hnR^C+ibKH@hz0h@3Y|r*cbb_{Ga+RnA>ym5aZvoX!-pWiwycl|Vo;W&1I} zXgUv|FvG1a(iO9Sff<ENx~)_ey;o*&k|w zm!HQ{fj}^`92sRy6Jjbleo+tvxObHYH@U+iq6)pws#Lr-ptw)Nq!YGIn*BoiG 9AK~Z` zhs}70Aoo6Zk6Ajl4q|vxPFdq%U;MeMXT8EvX~fEcr#GFR^R>X#^Y`RoX|43~m}DiF zZ5%E1)1qp^(uZ^L7;@y+(@9o6XI+wsu{pM|MEg+MaWef2X99BJwJIYY?9H5Yogi8s zAz(oaznn)+Fl(Qg>%d)I9B5y1_LV)QSWWIc-F*Uoif`=0A=Bt#(i&QYy|2>_zXAEZ zA~mneJ=|9}GO7D=q2S0s7~!pC@|zEl1C zh|YuF*SMQHs)z18QXFBxorm*TxmiiLu*;}#-YId+#5poX@u?b^HE%Yr*2TZNz%~4Q zciNiYO(Q9~k#3v}?BpWv9uI(7X5F^VWZ7-cphXa6H%|0g(i*o&`JN}{NY!cyHhN>2 zwz)|xDvdiyold;WLFh4o@&bg?cJ1b;%@$`#n`iAHLWCMA9?ZL$EHSJ)5l4IPkRd?9 z8J%C^I6x?yz0dz62yQTjwmVOYi5DvRjlgR|f8US8W?`sYe0x@lWCvc$QnHqy?UfJ9 zEdmGm54CHA!mQq$DMSyR!r?XS{|ibXC#WV3==dk0)VRZ&D6t}_WABJruP#4a%Fr9_ zPJPb4 M-E?$0S@H0cyi|JstG+A zQ{}+VFJhH954%o2WFVQ6sF|rC&Jcyy2&ied9<0oukBQmilxX (EK-y^bKMQ% zQ0_J>#uYzW^VZnwUUaXr+k%(_BN`wMsE@KELCs|^fMM&IAg%>AE70!um}FJz00!4f zi2A?g5;$u`g%_c*sZj?W;OX14iFzoAR1X5=m8ut>Ns6%~cn!pUN(1L$#c=KbdO{-l z2Jz=%fzzm@Cul65F?;4*j||CJ4%_4VpNU}e5;6{B`RzV`@Qyw$m|T$&VEH 2fIsm`Ua;s zzwInm0G l!i=Utgn0}0qZ z w&q6iGxG*6FmDWjGDy*>zF{;ebc`?;y|7^(@E`Kf7}k zQuC2}4Kla3|2rU8W&}R> !d99!1Tkz0q-%f-kJ203K~fW^ zH-SZuVJ`(YR2Bj)FI{^2H{4r0yBKSemh}&@xntoR6Y_$82h^3q ;nYs%YVg`$9~<=ix8h3?=#)RE*0IhOsDWid#xWokcqgO5eO@~0Z~_C1)fypj#r6G z_|9X^Q`hg3w@yv0LP~!fD$YoseQ0*=M{eQCvM#PUg-}~oWOYd0@FXEjW+-a(Se&fa zKgV$O?RY$c<#dis-zrI45DIT^yH* zDf_YfJAU
7~8^s#5s{4wU~(9 zSX$m434A^wuW&MyoEdJ?4@f^v3(fIDHRY=1@^vruwCUGH90n#(Iuc>RFTCw|Wlm)V zlLok#kKGs|L|fc!kO&0t0ETrfmTFqLAR8XLXr)4|YA0`?S* GDZL?TMg91>G? ejQ4&x`N174?=rt|MuPN#zhc>h&u#@UTzFm$1kp z)56VKZF14QM65aGt2lgK8BfLu9oY-2s=~q$?MXnqHLSWx^2<{KJn5uWF#2EMd) PHzJYAixowsy_x@c{QR}_%)7$RG}^`@Rfm_9CNT!wwgQKEjdOaiEk zqLZTTJx lVSXIqYWZLL!31T;UojA@i#CKO z{jP*uNWtat7G+|;)sh7e;>OR@Qa^$5@cR2H1V+Nz(b#rxAM>_+nabL(FoF80)5T{j zSeEJj&=%6LixsdDEth|LVOB}*$(Lzwjf &oYA}9!l~v}W6jo>tMUgpBXiy{^D_MoVFtw8j00}wmR$n&fr~&g z%c{}DvWD6=y4n*fCU1#2)yJ*Hl$4cTMyuZa&caV83xt0IhQyVO5-ab`R2!j9nvG z3I+~~FZ$;hIlXDa$!$hGdLN#;-%kxthLY=4j6E!bcB|M~?tek(lO7V0k;`AM$J<6l z*!iHg?PpU%mUbcE17?7$IJK-dmL?qBpTT#@kl?Q$a(mCqq%0Mo9~Rli^{0OS^K4ZNr^M^%`P-su#bPW`p}$ zKJQgpOZ|)zUy-*uAf 6t z8&@CNX~BDkON RiuiZM87M#XIg?xvGV{$7J{25a^Dv$k z8ddOxRp^=Om{AyS)(S{q=|H_zSUOA-9V`*^+&A2Qr~_V^yZZ#xz`C*46S0y91yF++ z& nK_l?=O6Ux6 2CHm3(m z74=?102gk4AF>=6r%4-Y<3IEL&g$A-j>8LKl~1qs+>7X;ubD1whZjKsNe@8R^tCoC z$`3<;-p5f8iU0DvcbhLB; )bhbJyBI+6N zWrU~tvw#Mf=JP)$@Y@Me2HX5EX5a%7k8q@`p-ZZtBrR1tys4wm0pSLp+`QOT=U5!Y zmhd&4A7lNyE6t_dCW7{edS1P*dsBx7Rs-v^lEu&ZPPSnt?#Ag%lfc5nk+8_;1hdXp zGgusFH1rH&?KDLFAv+XSFMblL?UzYPWezQ0-sc`7q6qJ;Eqpz6%nUNbW#)1Mvv7M9 z{k$n)6p>_A6xOo+b95!g>e)_TvIkZZZp36zkxXxAiY>@VIwT@74N~zz@@-U?BwZV` zh&;jR&NA;{Di6z+@(G=szE4qIO-*`t5PFrsQomO}whu &6ZKX7%y>2OOEln0{E5`022YKKA3C3?)>`1^@Gm`BlyLV}ogDr2q8CvX zx-!E4=@w`FHaNpeNyi{Up7<$~VrlK?k6p-@h5|}COyzU_cu<*eRV>(iD)W(VoCE5t zBnd_*(BP0|kO91p8Aj!vce~?r#t_WltqawtPfx+yOkVziXp9CKA^BMIdLWP`J#YKe z3AH)vCYgC~2Fwj4rIk%iny6*;?dFa08rKv`$j?nVI60V@NA-Ytj4=!%Dn3;%Jw&r2 zi>q{05NJD0Ez8rxp8 P-Y4V7EFMOz3#=(R=;qY436|TOV-b$-%9|lPp->b=CbhQA;S;ch9=1?p zc`y*p*pjA@w|%(jP!&`ZbsJp6H%l}98u*^K%*)Xr!O5~qztk_~urg#@ooD~{AK;Vt zVL$uU*Xx#8o~j57DKeAR=k74e+SRPqw4fYnl#}siRy9|(9+(;*4yc`vu7TY`RrLPU zJL(S=>oR+J$31RBR)wh*>S9A~`;4vZ7xdv+9spg~&$o)gibm)nFMnEl;@7EitQY2H zXIVsQ?@|OOarH5?5)M06X=te1l4;yLPC>xd>Jfl&-hYnCh1Smu@`edt!eMgxTwYig zd`t`$GZwlMYQz{5f=FCF$TK2DMqG4E98s-?B$rbb&4c00Zc6E C{d-%@#&>)ZJxW#PEmDM%W* z#OfA>q_)RcYIjYkLq(;s<7O=Ee|ODGE?|%DnELTH7}FRm_rZTzn33z~EK-i~QS*F4 ztR@kpC+v>6pzTxlP2?MOM^Y=2j7L49>fL52bV*E`f2}|Z(zjl9C+MyXB@oG{Z{dzC z-fK33dea7-H!j56QXU42aR^I{tGR(Vlh@PQ=L^8&vuH56I004SHfakFcH6Ed8LyG| z%D^r{UXX-Ju=|ck=sTA_=!ww~5vJpPJ!d@A$9i}NJKY>Z?pPd@V}C~}@;Cc0 9tE_~)D~7}1OnW0&6N0$e_w98#0S`@bUkrFa>S$CP zkjGFOs8*%j`>%PDTKW2(5K7DgW{NbiwdhpAPUGIu=Jus|JXX=k(F+)Jso+a>jI;7a zGyEO&i=*%=3nzAWAz)xvmb>MF+T|wXv2w{(IL}0agtmoY9okjk G{XCvMe`=09kw~wF4K`CBF&8(2WnQaton{H`Y zhJ`lB#!7+OAhHu6**;O`=fyYheMIi@zGShR&kbLL9IM9j6I|u&kh*Mbep_*|f(bE& z0rO%&@d9IQ&gXve_tek(J;pxFvT1&of!ey;3H}(C2WOEh0!rcOS`yV2Hg^-dOfOiN zE0@q<-BC2*Zfk $x>(1h_D#_GIqEwJA&^wxgUV-?uj`aupLR6yAtd zZ74D~;|HVOr%TAsocEE-J3+V;_&^0uh=5BUW>RTrrqZoMSDIvzv>|p%1>O#jZM4l} zKi=!<8Os1?;^4g+H~x`cexw+4eXueG@&){c02W3PFx~{EHfKGqw&F1~2(L8WKU^gz z$eY;sr*~*_s#+ WaJiF$@IwgjEiosHnH8M)&slntZ{< zmZBD7(Tz(*llBO6G4QH7hh~CoH84Euvr|E~&QPu0-i#G5SEUELY !Y zsowY`7TLFr)b$&AG9Mu4;RTrm!YH%l->Ec5E++9Bu?bl$ov$N8E(SW8N0w4M2Uj$G zhy8HKK>~&3FA3%YKF<<=yhf&`iZy;wYCKdMU+H9E6L
9(rXpq?=oEtZH$cH@#n%I5_--RYWa~6K`G+WB4d9=0W7a)X)U*f>vgZ6 z7)!Z?_ruN?6AO{!<-|HiF8}2s+G)JYTYalE6%Fh+13O<<)472)H<>5uOsXxP$mQ_W z4FQ0y6l=jfE@%a5wtnn-*t>F ;3_L_pYh z?K#PV7CSOQ92T#*Q`#x&qUha6z@0hK;1DWpIl&hprH0BxUsB>agbClM=wWQ-k$f7) ztUSjeSDkJV3-Hw*f!t^hL$Fac!GU(2F7pJ)i-XOUD@~pQKdVk5zXX_Uol_lwTK* z%7eR0aakII)PR1&oO#QL<8ZwTB1ZZu^VpDyzB9;e5zWWp=MRoscq2BAUvZ`&2gK~_ ziq#cxk6FFFLWuSW`xZ!M?Rxua`4%h4^fJtXZvs|Nv76WDJQ3DE+ZWilCzo53P%xVN zOq{~OJv&S$Ymdo#zfB&5>_+2 4^QN#Kl1J*5< z1anC*C=2mvXz#P`u~yx?D!zO-p$iJULk J8?8XEL@D#fHHYgT20#j z0_`X2As8Yw7I1v1eFgd6Z~yN*>h2^!5ix8Z|Gh5qyNO$zvVY|LYr(yFcqYElA8}1y z;q7!YCaHHQQpd%nP!8r;RyJ011DyzY&SV3KpF<;-ou1b2vhzdW!ziA?W{b$#KVBfE zg!sASXM2YXuK}HJp#1NlsW4KGyEH(%(*DjVpuKy^_dcFKi)SA&@hV&ta0b7t5L$1n zgCEqhjB2Iza$U?T%QO!} 9yp5j<3#+FoT;#pC* z2LS5Cx`jEN(3! p4AF<@U?^y@T(JswuPaVSI}TKyBA*B 72)U=3$jYH6k|53Yu21xhcqNf!w}^l`YHSvPK7P}QrfNX&A7+$NxDic5m?Tx;Y{ zB^f?X7&oD<>*rEWNyn+UYU~yAe;qtM2AoSZiqxYFt`uO+%}9!_a8|EZ)$69|r)>@s z>R)vyf*1qF&fYBt$9v|f+_^RMT51Yed78%COJNS9L)(rrbHC|~je5N`Db1@Yy_@c| zA%L}El)`$jIBkm5Jw^Hnj&|}3=+9v%%Cq!NroepmycQ-xrq;GZ0um^q^LYp3zC#wh zuf|l48bG}m>Vt!FL1RpH4*AX_?WE*Vb~uORnZqNPJ@Gyz@U|dog&a?Fo#)y?$g4S= zq0^LV^4RV{_I0y=P9?dAJT-cf4s_I<*u42AF>F`URkT`_WtOrL#b?)%?%y73T|WcW zA?$q{?1Ki1UWioBpiS~TpQ_tKbqvrWV jdfjf?cY70q|HHlPQUp zw{t)m&RpE!LI){8Qeb t3fYxNRH*^U9X8@zwo1+itG{YI>__b7 z+&CDpiLXxd(huZRo;UOqt2SvB#2*Dp71kx^!8XhRjnN48pC8~evNdJg?STu5Mp~e% zc 1J0*rJa%0KbuZ|>LDaD)iBdvv-S9+j+p693C-AOk=70eL^d zV|xoGL{wf=uuhcBy*oL~eB4M33UWq-ThD+1jcf$t951Tv94M)|+C(&qJ5aYAY+7Md zr>+;dPriCat{B6pH>BxFTrp;~OIrx$9`}Wy18iPGWqIJHm9Xt`%i^H1Cc|_c%w1PB z{PS?ojAlQ$gUb}kCv{?Xjmo{$K(7Fvl5fW7=SA8AtkTnkhna+wREKp23Gw*^fMsQ9 zPas$d(lE86Dp~3T(o597;$RKk6nNggY7qn$ARvbTsPNhx2jy=|qmWM5(C_R?x#0$7 za56==6(O%wB{z@yWU&!)s8=VnPR1cKYfKDOYk@~^4&4)tIZdE#vF%!O3P^ZN0eq{H zY@9W((J&NcoGskG{E1j~L*J3vC2~wbT-6K?gmCZfa&=dZ)GagHr}LWYxRTh>20*`E zZxK<|g66vO%G>g8-3g?(Uvm@B=kA4)tvppDchimO<1CNK21Mey9b17pn%JLeaZr1E zV!_ql17exkQ;xLw4{OA>kB|Mm9L=7^(}Nnvg696s%rbmp?`$_c?a|m(jMUDVHjAH0 zp++-1#X^(2-MW6on$GaiGcCp8!9t|A=vhXn`sgRg#vY9!aYO0?fIH6u_@E46y~~cx zaLFV6SSe?O45kNbY1Zs3QzbTd%ds+FSN67T`>V^dp2KdxB6LhgZZUP1sT7`;PztV+ zai8*11^V4C`}EJeGB;5T1mXF%;lyNfIA6T_efFFvua;*XGQe1K$fKLz)zAoM4u}Cz zNQ@M1r~s->og2awp8$YahKHzrgd^Ss6>8GDMSL$zWHy3s;?>Tf3c(_bkTsM)=e3=F z_qFw8le34~cYCzNY@!9=362jNUgVr|Rl$n7{MB`gi?!ul#76~2Lay hSPFwHBf{(+gf7r9sE sP+mVx*yVg8=4d#7-& z_hYeN%ziPd_y~Q2*~xr&IQ*r`u``4kLD;FnBKAIk813L !&Z=F|p=2aqSIk3|?--HOKPq8I})jk>B`w2A>Va-~T1o^iUt-f^a2< zy63SIU(8Qx6~~Qm?f}DBWi%WV%G@J>8?#Gym0QDGX2?Z>_slI6;z1<_JQKy;FOWCd zaw__e9h4|VC#La}p(Y6J%m1d2r4!{RH)duWAl&Jzo sj8FyY;iOk_@bXoqW;+pp)j^|Q{-8sU)^8$RJ%9}jQ=Y8JeNFxoL3cKN zvpva>Y@gDSbT@r%X+UI!X@1j*^K9X{SXRJDJJ;qFTG9*@{A13AT{bGYz1KRMrn7Kn zE0^kC+JdIFi2-M@bbw3Z+aB)){e!6!QgvAMV}dP2DZ@u+WcS=|yc>8k>@x9tx><81 zfvJEaWE5kT^Ej|gPxu%d!Nx0KvacdB8W$szoPqCHU?NxqHE20&+uFPwY+=iSKo|&? zMBZ)Nr79+ldEcvN$gq2nZ*!s;>Sx$`geOU(aP;YkRDc7szi`3%x E|G^|E7=T1aU75bgVa=wDI1_H#3)+(`tr&A}ickm
OVfeiM>D367M#H`_Qjho7lu{+~)^NLu zvcP#O>1~oYV7u^U2*4E!XfPm)ct&D}Id_983FSqjeD_)@*rv zAvu~%aPctm0PS!|EsSu3HuZ+~Ey|{izHo({o`B>UiTymR;l;7^pCMob%kwRNFmkSZ zQg@qzFzCqR_+<>lIlLs9{D&g DIWr`-_0;P);qTny z9#Vr!aTDf}O0HCODoMTJ+P$s|*X)Od-k)*OPQP9(KLy*qmaSzW-YKgK PZ5iufgDqZD?`{$zKO-IguIncMdiu@yB}w6QtIotidBO@N85TojO|V5jd@LS zCQ?cI8t5DZ2;0^ZdcV8-P@2^2yLji~UL;A3wKZgI3oUzMT4yWu4v{-FN>pEd^PDuF zqMNzy@|o$@YCzIGedal#Agmu;ooL~ACFT?xNswF?-fU|Z#qt`C-^r-9BttN*RNrV; zzd9Gk{cSsm_k1ZbjHM#p|0i4YcTZa9)i<406rUjBMoxMl4TSAJ@?|{0T+!;kc@|vy zIMNtnB` psXnz;yB3qvi?&L>`Ck6VB3c~xKe8o|&xz0C8oG@!C=#$S zg5vG^;Y7PfQZuvJN3(EZqQ$-1|HDp}S%QUOqsz#1H;RD+b?ytJt?}zoP~S#Zo~zVc z+%^1>2Z=jVPDqtywST-`@3QJC;BK`AM);o-Xu6PQ{Q((tc;cT1VsVKRCY`N)=-7b( zACn=tI_`mAc^{*|vs}yCJunPmbEwbt9CY%IK+K+6atM+%GtfR-(lb^oVjr=OuZ;uC z&IR3ZooI#H$Z31Ajr9L&^PL;XV}_5C#?_{X%mKVMAqzPkhn`Z4%=48;Q(Ebiw*c$J zMiwkIGBTDlG#z~*qW1n5h6JS55tc$Zue_vju7z<+@~SfgJdqBxP`J;I_?L>^r(ymh z-18n}pPy1O=S6E~r|z89^R$A&oa>I;sv2B392rIZ#?=&ic25qBz!L9SzyIZNIua<< zHG#9J5K))gzeCwYx4=sdr+gE2r#$zmSo+Q|HK6+9(@1P@c)S;zhTpxs&`#G(czfH>7xXD!O~{5g{p!i5@fHdp +YfI~x?m>7wxRF8Ow)s{4+N z&=B!GVUHvkBg{I2J`A0;&P*fGaiU(hwDrH$5C874_$rk)9acNUU^p&oV`O>ROMY^8ymkC!O(+X`6J+0{h(> z9HZ^8{9Jwf45ytj1Y{xk%stY4fF%gj+GbRMdQ#=>QICQ$*o!^HeD%dQF>)cnhz>3+ zpz?WE7T3a(=0JGcl%qY(&oev`LcEiFS l%Y zYT7NmU#zYMzMW%C%bfRALv$VpUUF}~!hIF#)-<_PiiY&&UFuCiraSbe6H%pL_*F~y z^$h7*&SID^;=MmhT1+dZ;axO=9(8`IXNl%SXW$z5O_d`m*72(sT}My^)9LESI8SeV zf&u_v@U7y1{4~5dDm6W1D5B~YnI&wP_R7};^;iu3ej1Ggn;* ;1!}2}v z5!9)`Zt~pG_JL%h *h2%ZIi7?MGaS5}3Ls|-Dvj~dmpYD8yvD0%Jrl2uasYM$ zq+o~Cp)yPP;y*|5k&s0ZD8|c6Fr?|dh2sr-`e{YvK(_VT2hW859!>r&Q%J(;rp?`I zDyMj^#`Dh9JJT`TN%MU{mu=2v9K~G$#YN8{f-|+wO$2a()Ijhr&KZs%tev6nE6A~N z@^?_o4;B7@cuJ3C4$)+s5Cqq>^@S~WS6%@UJHk 9oD|YFv7D&4Xn{?JmC@_}6{rc_|&O&UAY`)VT`AX+1?w|I#eK%W^kE zjm7>F@VdELtgr%ErwHWX _DT?y4=IKVC{N(D3d;ih?h zy2EPgg&gM~WCqnWSy{yC*8-`)c!#eW HcC#Ium#;7Qj7$hB|!RQS&TS8Xsy(C zDM%S+d)Vf{`bHG7<&v+R^2yuP{g<{2u!DLG5QfmMQwc98gm1HlY#97KV{lhq*G(O# zuD=gkT8Tb!P^?EvbH1y#BY{F&z!%auptBxL;vT=6FC_C|?-3Rb5_KYk#(oT)&yHF4 zHodI) h`(7BN$|9ae4)@;%Ak#!c;68(T@)Nc!FiFtz*;Ub zIC`bPN%yDlB?zhUM RzZ;^{i+aqGIMnbs;)3YT8zGI&nVHf>ZunmY4?yTiBhYII*0 zP7RO+-^!*;^y&z+Y#Ti^J>O7s>M$ZCkJ(tGjAJ`$);qaL%8Zwz62`3g6}?_K#->un z_E`Ua46|=8E|T^RYs9l_Y`dJPQ(TIEc3QK=g9PuubjyGTG@sTyB18u6;7$SphV>_@ zLL8Yr&4w0W;J*^=?Rw=SFCrj9U-tdh_B3==jlgL`9=3hGFvDc>K`~{lhrdk|-E!O3 z^d2N-gKA9@iFxKhi6fF#Y6
qExQ&PNPg6um9u_Hls zRAN%d{_HYq9?_}7 vvfGsjS4hiYUilDLKJmmYto zwcT~i0bYw2CMW*kVpERgPNFY0y{y&H(yx5HV;o>D2x~gK{nZL}*Qa0VXV;acY5RU) z0aPouB%|YJ`#d>tQX_?MbOeteX%wf+v^r=5W*e zaavz5H_Ri<((vJ77%A>yZaDp%d^MR3{^Z#Bz;c5G#>P<`tVrLgQ}#9Jr_q!olN71@ z@X8C^3tX8Q4`2EP?fI`quDiAu(6v(a4emL$mv+UZg3O6Vm(h~NwIQ4goDiYY`30gb z0*<*ywJ#*xQjqw-?{Pd US&8(s z#M-4{jP!s D)p zOb?z|8LJ9Bw(NnY*of|VUU05$k2tYAYsPqKj9e~weghft;IDp6@YA8rNfcAoVi3Pg zo*^%e4V+d}jz5L e6+Y6!;*FIVa%?Pz~=r #?!;xE^3 P|J=3pXxso~IAm)ug(ZjI+bzTQ{c#Dw;XN(QGw@wj zEnq5CFj}pPSF>Kf&e>kQQ0EVcRYE?V;iI?9b9}}nJ|OAP&;vwP)Tz;QZ(xr2`wyGc z%h?n*?x#~Jyv!G=B6J5*McjapL3exDJW|88e&5<5(d&1Q;Y!HU1O<+xabC|4ibPU2 za4ByS_0>YyxzPhp9o+qU5jMfK6^_vOk7C!-G*YgA^L5|Qp4{gl=l6X4z}T(z2FTT0 z*_K_m6C3POUup9f-H=V%v;>{!Oe2INdnE6B8PO 6H4y zHJ=ar>iu%V0!Bq0r;WITsi15daYxgZfr*yY55k1!G)aH&8o70?#h6a#HtbYmNOJ+; z7Gx}R(og;bdoWtnS6Y@SVLXUwu-&;X3KZYP((0H1u353AJD`K8nAYKgiCyD;D*b@> zQi=O-AuY~yvRhrW56U79jozW9KDjT2@^<+(y4`&Jr }Zi9aq7GSVNP56uuz^nj`xc5H{^4pyUG=%xGLSYYy$})gOv; zI!nSAB!LT|w11DJ%<_W2Imx(AwjCPPEY7zNbAjX^S|nX?Mt$M_7SSFK@n;?2Z(vBv z%*$I7mJNekGbWy*^Kj7vj|8#Xu{38)QfZ6`RJ(b1+X!@YLkOYP$LDwBMU`L#4%p(^ zSu807IuS7-4U6Y2BX$29s+SIi0HxPehCQ$^7>N zYXl*h3sDH847jtmcOGFS+*6JX$4s5D7WXOUke32LU*sVHXxf3|tNKR1!E~MEX0ykZ z 3Dut(?r`cvO+lIr`N%X+bib#>*3#L!jC3cxrKZw5It#xVE=)mmN;bnEOA z*&48oth|$CA7H;4yH__L`lB_VCk|0ZB!YV5G5q!ReCbu;jz-g3R|-kJ8{%ZIBI3FsG)C``PIn9Y?JT*_A8VEM!ydKC^_;GW$hd`%yR^=&I0=ZNb^x zj0a`-;CWyvp_Y@|$74T-#w?hZq$_gch-h`GmvflBLe48CaV$>kPFgDd2)oV2Mh2+v z`{T9Ui{o<>_xIGJO$2{JkNcz7jKpFbU89#2CRhO8ygV{;sc*-^mI;y?8x@{?vof*2 zD;K-Zp(RS`TZj)&eeW{%be{ 422<=L>_ffulmUi1)NtXLx*U zY^7!%7mzbB`cjsmO6*|qgn8?SgT@Jo3}n3n#&8F#-GCph04&EDn0b!@{*hr`2Oh`t zBlB~?3FR;1n$fK4WFWTKAAs5qT!emC;`$kK1LZs2dqLMY15ilzfQ&xGrXv`A6$jgD ze`z+Rn34HLs1m6hcG=SnSi;^%6A+t85*~nCkmy)+DLtPoS7A>Rg#(SK>hmufi@n;L zSx;TM&Asr3HA9L3N@xUX)b2-u4`W4dI}6}^dMs&VL>nw3o_U>sdU{>8_aDF*D-y;= zHhXfDjO0*U_owDqEq3aE5BoBPmtkmsz%9SUQX5Js*+f2Mfkd``W yIy>KZWV($Eh$kto>HB8A{t<>az@Bq^HQ#pNr3dffq5U$As XO!U3#@+h_nCLL&x(Wcg^K}L02lg6H8 literal 0 HcmV?d00001 diff --git a/lab_3/fileOrganization.py b/lab_3/fileOrganization.py new file mode 100644 index 00000000..7f2a859d --- /dev/null +++ b/lab_3/fileOrganization.py @@ -0,0 +1,57 @@ +from config import PUBLIC_PEM, PRIVATE_PEM +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.serialization import load_pem_public_key, load_pem_private_key + + +def serialization_public_key(public_key)->None: + with open(PUBLIC_PEM, 'wb') as public_out: + public_out.write(public_key.public_bytes(encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo)) + +def deserialization_public_key(): + with open(PUBLIC_PEM, 'rb') as pem_in: + public_bytes = pem_in.read() + return load_pem_public_key(public_bytes) + + +def serialization_private_key(private_key)->None: + with open(PRIVATE_PEM, 'wb') as private_out: + private_out.write(private_key.private_bytes(encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption())) + + +def deserialization_private_key(): + with open(PRIVATE_PEM, 'rb') as pem_in: + private_bytes = pem_in.read() + return load_pem_private_key(private_bytes, password=None, ) + + + +def save_bytes_to_file(data: bytes, file_path: str) -> None: + with open(file_path, 'wb') as f: + f.write(data) + + +def load_bytes_from_file(file_path: str) -> bytes: + with open(file_path, 'rb') as f: + return f.read() + +import os + + +def check_file_not_empty(file_path: str) -> bool: + if os.path.getsize(file_path) == 0: + print(f"Ошибка! Файл - {file_path} - пустой") + return False + + return True + +def read_txt_file(file_path: str)->str: + with open(file_path, 'r', encoding='utf-8') as file: + return file.read() + + +def write_file_txt(data: str, file_path: str)->None: + with open(file_path, 'w', encoding='utf-8') as file: + file.write(data) diff --git a/lab_3/main.py b/lab_3/main.py new file mode 100644 index 00000000..a10f964f --- /dev/null +++ b/lab_3/main.py @@ -0,0 +1,132 @@ +import argparse + +from config import INITIAL_FILE, ENCRYPTED_FILE, DECRYPTED_FILE, SYMMETRIC_KEY, PUBLIC_PEM, PRIVATE_PEM +import os +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives import hashes +from symmetric import Symmetric +from asymmetric import Asymmetric +from fileOrganization import ( + serialization_public_key, serialization_private_key, + deserialization_private_key, save_bytes_to_file, + load_bytes_from_file, check_file_not_empty, + read_txt_file, + write_file_txt +) + +def GenerateKeys() -> bool: + print("Запуск генерации ключей") + try: + sym_key = Symmetric.generate_key() + print("Сгенерирован симметричный ключ") + + private_key, public_ley = Asymmetric.generate_keys() + print("Сгенерированы асимметричные ключи") + + serialization_public_key(public_ley) + serialization_private_key(private_key) + print("Сериализованы публичный и приватный ключи") + + cipher_sym_key = Asymmetric.encrypt_bytes(sym_key, public_ley) + save_bytes_to_file(cipher_sym_key, SYMMETRIC_KEY) + print("Зашифрован и сохранен симметричный ключ") + + print("Конец генерации ключей") + + except Exception as ex: + print(f"Ошибка при генерации ключей: {ex}") + return False + return True + + +def EncryptData() -> bool: + print("Запуск шифрования данных") + try: + if (not (check_file_not_empty(SYMMETRIC_KEY) + and check_file_not_empty(PRIVATE_PEM))): + print("При шифровании были найдены пустые файлы ключей") + return False + + private_key = deserialization_private_key() + print("Приватный ключ был десериализован") + + original_sym_key = Asymmetric.decrypt(load_bytes_from_file(SYMMETRIC_KEY), + private_key) + print("Симметричный ключ был извлечен и дешифрован") + + original_data = read_txt_file(INITIAL_FILE) + print("Исходный текст считан") + + c_data_bytes = Symmetric.encrypt(original_data, original_sym_key) + print("Исходный текст зашифрован симметричным алгоритмом ") + + save_bytes_to_file(c_data_bytes, ENCRYPTED_FILE) + print("Исходный текст зашифрован симметричным алгоритмом ") + + print("Конец шифрования данных") + + except Exception as ex: + print(f"Ошибка при шифровании данных: {ex}") + return False + + return True + + +def DecryptData(): + print("Запуск дешифрования данных") + try: + if not check_file_not_empty(ENCRYPTED_FILE): + print("Файл с зашифрованными данными пустой") + return False + + private_key = deserialization_private_key() + print("Приватный ключ был десериализован") + + original_sym_key = Asymmetric.decrypt(load_bytes_from_file(SYMMETRIC_KEY), + private_key) + print("Симметричный ключ был извлечен и дешифрован") + + c_data_bytes = load_bytes_from_file(ENCRYPTED_FILE) + original_data_bytes = Symmetric.decrypt(c_data_bytes, original_sym_key) + original_data = original_data_bytes.decode('utf-8') + print("Зашифрованный байты были извлечены и конвертированы в текст") + + write_file_txt(original_data, DECRYPTED_FILE) + print("Данные были записаны в файл с дешифрованным текстом") + + print("Конец дешифрования данных") + + except Exception as ex: + print(f"Ошибка при дешифровании данных: {ex}") + return False + + return True + +def main(): + parser = argparse.ArgumentParser() + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('-gen', '--generation', help='Запуск режима генерации ключей', action='store_true') + group.add_argument('-enc', '--encryption', help='Запуск режима шифрования', action='store_true') + group.add_argument('-dec', '--decryption', help='Запуск режима дешифрования', action='store_true') + + args = parser.parse_args() + status = False + if args.generation: + status = GenerateKeys() + elif args.encryption : + status = EncryptData() + elif args.decryption : + status = DecryptData() + else: + print("Была выбрана неизвестная операция") + + if status: + print("Операция успешно завершилась") + else: + print("Операция завершилась с ошибкой") + + return 0 + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/lab_3/originalText.txt b/lab_3/originalText.txt new file mode 100644 index 00000000..405c6b0c --- /dev/null +++ b/lab_3/originalText.txt @@ -0,0 +1,167 @@ +Профессор Выбегалло кушал. На столе перед ним дымилась большая фотографическая кювета, +доверху наполненная пареными отрубями. Не обращая ни на кого специального внимания, он зачер- +пывал отруби широкой ладонью, уминал их пальцами, как плов, и образовавшийся комок отправлял +в ротовое отверстие, обильно посыпая крошками бороду. При этом он хрустел, чмокал, хрюкал, +всхрапывал, склонял голову набок и жмурился, словно от огромного наслаждения. Время от времени, +не переставая глотать и давиться, он приходил в волнение, хватал за края чан с отрубями и вёдра с +обратом, стоявшие рядом с ним на полу, и каждый раз придвигал их к себе всё ближе и ближе. На +другом конце стола молоденькая ведьма-практикантка Стелла с чистыми розовыми ушками, бледная +и заплаканная, с дрожащими губками, нарезала хлебные буханки огромными скибками и, отворачи- +ваясь, подносила их Выбегалле на вытянутых руках. Центральный автоклав был раскрыт, опрокинут, +и вокруг него растеклась обширная зеленоватая лужа. +Выбегалло вдруг произнёс неразборчиво: +– Эй, девка… эта… молока давай! Лей, значить, прямо сюда, в отрубя… Силь ву пле, значить… +Стелла торопливо подхватила ведро и плеснула в кювету обрат. +– Эх! – воскликнул профессор Выбегалло. – Посуда мала, значить! Ты, девка, как тебя, эта, +прямо в чан лей. Будем, значить, из чана кушать… +Стелла стала опрокидывать вёдра в чан с отрубями, а профессор, ухвативши кювету, как ложку, +принялся черпать отруби и отправлять в пасть, раскрывшуюся вдруг невероятно широко. +– Да позвоните же ему! – жалобно закричала Стелла. – Он же сейчас всё доест! +– Звонили уже, – сказали в толпе. – Ты лучше от него отойди всё-таки. Ступай сюда. +– Ну, он придёт? Придёт? +– Сказал, что выходит. Галоши, значить, надевает и выходит. Отойди от него, тебе говорят. +Я, наконец, понял, в чём дело. Это не был профессор Выбегалло. Это был новорождённый ка- +давр, модель Человека, неудовлетворённого желудочно. И слава богу, а то я уж было подумал, что +профессора хватил мозговой паралич. Как следствие напряжённых занятий. +Стелла осторожненько отошла. Её схватили за плечи и втянули в толпу. Она спряталась за моей +спиной, вцепившись мне в локоть, и я немедленно расправил плечи, хотя не понимал ещё, в чём дело +и чего она так боится. Кадавр жрал. В лаборатории, полной народа, стояла потрясённая тишина, и +было слышно только, как он сопит и хрустит, словно лошадь, и скребёт кюветой по стенкам чана. +Мы смотрели. Он слез со стула и погрузил голову в чан. Женщины отвернулись. Лилечке Новосме- +ховой стало плохо, и её вывели в коридор. Потом ясный голос Эдика Амперяна произнёс: +– Хорошо. Будем логичны. Сейчас он прикончит отруби, потом доест хлеб. А потом? +В передних рядах возникло движение. Толпа потеснилась к дверям. Я начал понимать. Стелла +сказала тоненьким голоском: +– Ещё селёдочные головы есть… +– Много? +– Две тонны. +– М-да, – сказал Эдик. – И где же они? +– Они должны подаваться по конвейеру, – сказала Стелла. – Но я пробовала, а конвейер сло- +ман… +– Между прочим, – сказал Роман громко, – уже в течение двух минут я пытаюсь его пассивизи- +ровать, и совершенно безрезультатно… +– Я тоже, – сказал Эдик. +– Поэтому, – сказал Роман, – было бы очень хорошо, если бы кто-нибудь из особо брезгливых +занялся починкой конвейера. Как паллиатив. Есть тут кто-нибудь ещё из магистров? Эдика я вижу. +Ещё кто-нибудь есть? Корнеев! Виктор Павлович, ты здесь? +– Нет его. Может быть, за Фёдором Симеоновичем сбегать? +– Я думаю, пока не стоит беспокоить. Справимся как-нибудь. Эдик, давай-ка вместе, сосредо- +точенно. +– В каком режиме? +– В режиме торможения. Вплоть до тетануса. Ребята, помогайте все, кто умеет. +– Одну минутку, – сказал Эдик. – А если мы его повредим? +– Да-да-да, – сказал я. – Вы уж лучше не надо. Пусть уж он лучше меня сожрёт. +– Не беспокойся, не беспокойся. Мы будем осторожны. Эдик, давай на прикосновениях. В одно +касание. +– Начали, – сказал Эдик. +Стало ещё тише. Кадавр ворочался в чане, а за стеной переговаривались и постукивали добро- +вольцы, возившиеся с конвейером. Прошла минута. Кадавр вылез из чана, утёр бороду, сонно по- +смотрел на нас и вдруг ловким движением, неимоверно далеко вытянув руку, сцапал последнюю бу- +ханку хлеба. Затем он рокочуще отрыгнул и откинулся на спинку стула, сложив руки на огромном +вздувшемся животе. По лицу его разлилось блаженство. Он посапывал и бессмысленно улыбался. Он +был несомненно счастлив, как бывает счастлив предельно уставший человек, добравшийся наконец +до желанной постели. +– Подействовало, кажется, – с облегчённым вздохом сказал кто-то в толпе. +Роман с сомнением поджал губы. +– У меня нет такого впечатления, – вежливо сказал Эдик. +– Может быть, у него завод кончился? – сказал я с надеждой. +Стелла жалобно сообщила: +– Это просто релаксация… Пароксизм довольства. Он скоро опять проснётся. +– Слабаки вы, магистры, – сказал мужественный голос. – Пустите-ка меня, пойду Фёдора Си- +меоновича позову. +Все переглядывались, неуверенно улыбаясь. Роман задумчиво играл умклайдетом, катая его на +ладони. Стелла дрожала, шепча: «Что ж это будет? Саша, я боюсь!» Что касается меня, то я выпячи- +вал грудь, хмурил брови и боролся со страстным желанием позвонить Модесту Матвеевичу. Мне +ужасно хотелось снять с себя ответственность. Это была слабость, и я был бессилен перед ней. Мо- +дест Матвеевич представлялся мне сейчас совсем в особом свете, и я с надеждой вспоминал защи- +щённую в прошлом месяце магистерскую диссертацию «О соотношении законов природы и законов +администрации», где в частности доказывалось, что сплошь и рядом административные законы в си- +лу своей специфической непреклонности оказываются действеннее природных и магических законо- +мерностей. Я был убеждён, что стоило бы Модесту Матвеевичу появиться здесь и заорать на упыря: +«Вы это прекратите, товарищ Выбегалло!» – как упырь немедленно бы прекратил. +– Роман, – сказал я небрежно, – я думаю, что в крайнем случае ты способен его дематериализо- +вать? +Роман засмеялся и похлопал меня по плечу. +– Не трусь, – сказал он. – Это всё игрушки. С Выбегаллой только связываться неохота… Этого +ты не бойся, ты вон того бойся! – Он указал на второй автоклав, мирно пощёлкивающий в углу. +Между тем кадавр вдруг беспокойно зашевелился. Стелла тихонько взвизгнула и прижалась ко +мне. Глаза кадавра раскрылись. Сначала он нагнулся и заглянул в чан. Потом погремел пустыми вёд- +рами. Потом замер и некоторое время сидел неподвижно. Выражение довольства на его лице смени- +лось выражением горькой обиды. Он приподнялся, быстро обнюхал, шевеля ноздрями, стол и, вытя- +нув длинный красный язык, слизнул крошки. +– Ну, держись, ребята… – прошептали в толпе. +Кадавр сунул руку в чан, вытащил кювету, осмотрел её со всех сторон и осторожно откусил +край. Брови его страдальчески поднялись. Он откусил ещё кусок и захрустел. Лицо его посинело, +словно бы от сильного раздражения, глаза увлажнились, но он кусал раз за разом, пока не сжевал всю +кювету. С минуту он сидел в задумчивости, пробуя пальцами зубы, затем медленно прошёлся взгля- +дом по замершей толпе. Нехороший у него был взгляд – оценивающий, выбирающий какой-то. Во- +лодя Почкин непроизвольно произнёс: «Но-но, тихо, ты…» И тут пустые прозрачные глаза упёрлись +в Стеллу, и она испустила вопль, тот самый душераздирающий вопль, переходящий в ультразвук, +который мы с Романом уже слышали в приёмной директора четырьмя этажами ниже. Я содрогнулся. +Кадавра это тоже смутило: он опустил глаза и нервно забарабанил пальцами по столу. +В дверях раздался шум, все задвигались, и сквозь толпу, расталкивая зазевавшихся, выдирая +сосульки из бороды, полез Амвросий Амбруазович Выбегалло. Настоящий. От него пахло водкой, +зипуном и морозом. +– Милай! – закричал он. – Что же это, а? Кель сетуасьен!9 Стелла, что же ты, эта, смотришь!.. +Где селёдка? У него же потребности!.. У него же они растут!.. Мои труды читать надо! +Он приблизился к кадавру, и кадавр сейчас же принялся жадно его обнюхивать. Выбегалло от- +дал ему зипун. +– Потребности надо удовлетворять! – говорил он, торопливо щёлкая переключателями на пуль- +те конвейера. – Почему сразу не дала? Ох уж эти ле фам, ле фам!..10 Кто сказал, что сломан? И не +сломан вовсе, а заговорён. Чтоб, значить, не всякому пользоваться, потому что, эта, потребности у +всех, а селёдка – для модели… +В стене открылось окошечко, затарахтел конвейер, и прямо на пол полился поток благоухаю- +щих селёдочных голов. Глаза кадавра сверкнули. Он пал на четвереньки, дробной рысью подскакал к +окошечку и взялся за дело. Выбегалло, стоя рядом, хлопал в ладоши, радостно вскрикивал и время от +времени, переполняясь чувствами, принимался чесать кадавра за ухом. +Толпа облегчённо вздыхала и шевелилась. Выяснилось, что Выбегалло привёл с собой двух +корреспондентов областной газеты. Корреспонденты были знакомые – Г. Проницательный и Б. Питомник. От них тоже пахло водкой. Сверкая блицами, они принялись фотографировать и записывать +в книжечки. Г. Проницательный и Б. Питомник специализировались по науке. Г. Проницательный +был прославлен фразой: «Оорт первый взглянул на звёздное небо и заметил, что Галактика вращает- +ся». Ему же принадлежали: литературная запись повествования Мерлина о путешествии с председа- +телем райсовета и интервью, взятое (по неграмотности) у дубля Ойры-Ойры. Интервью имело назва- +ние «Человек с большой буквы» и начиналось словами: «Как всякий истинный учёный, он был +немногословен…» Б. Питомник паразитировал на Выбегалле. Его боевые очерки о самонадевающей- +ся обуви, о самовыдергивающе-самоукладывающейся в грузовики моркови и о других проектах Вы- +бегаллы были широко известны в области, а статья «Волшебник из Соловца» появилась даже в одном +из центральных журналов. +Когда у кадавра наступил очередной пароксизм довольства и он задремал, подоспевшие лабо- +ранты Выбегаллы, с корнем выдранные из-за новогодних столов и потому очень неприветливые, то- +ропливо нарядили его в чёрную пару и подсунули под него стул. Корреспонденты поставили Выбе- +галлу рядом, положили его руки на плечи кадавра и, нацелясь объективами, попросили продолжать. +– Главное – что? – с готовностью провозгласил Выбегалло. – Главное, чтобы человек был сча- +стлив. Замечаю это в скобках: счастье есть понятие человеческое. А что есть человек, философски +говоря? Человек, товарищи, есть хомо сапиенс, который может и хочет. Может, эта, всё, что хочет, а +хочет всё, что может. Нес па, товарищи? Ежели он, то есть человек, может всё, что хочет, а хочет всё, +что может, то он и есть счастлив. Так мы его и определим. Что мы здесь, товарищи, перед собою +имеем? Мы имеем модель. Но эта модель, товарищи, хочет, и это уже хорошо. Так сказать, экселент, +эксви, шармант11. И ещё, товарищи, вы сами видите, что она может. И это ещё лучше, потому что раз +так, то она… он, значить, счастливый. Имеется метафизический переход от несчастья к счастью, и +это нас не может удивлять, потому что счастливыми не рождаются, а счастливыми, эта, становятся. +Благодаря заботам и правильному к тебе отношению. Вот оно сейчас просыпается… Оно хочет. И +потому оно пока несчастливо. Но оно может, и через это «может» совершается диалектический ска- +чок. Во, во!.. Смотрите! Видали, как оно может? Ух ты, мой милый, ух ты, мой радостный!.. Во, во! +Вот как оно может! Минут десять-пятнадцать оно может… Вы, там, товарищ Питомник, свой фото- +аппаратик отложите, а возьмите вы киноаппаратик, потому как здесь мы имеем процесс… здесь у нас +всё в движении! Покой у нас, как и полагается быть, относителен, движение у нас абсолютно. Вот +так. Теперь оно смогло и диалектически переходит к счастью. К довольству, то есть. Видите, оно гла- +за закрыло. Наслаждается. Ему хорошо. Я вам научно утверждаю, что готов был бы с ним поменять- +ся. В данный, конечно, момент… Вы, товарищ Проницательный, всё, что я говорю, записывайте, а +потом дайте мне. Я приглажу и ссылки вставлю… Вот теперь оно дремлет, но это ещё не всё. По- +требности должны идти у нас как вглубь, так и вширь. Это, значить, будет единственно верный про- +цесс. Он ди ке12, Выбегалло, мол, против духовного мира. Это, товарищи, ярлык. Нам, товарищи, +давно пора забыть такие манеры в научной дискуссии. Все мы знаем, что материальное идёт впереди, +а духовное идёт позади. Сатур вентур, как известно, нон студит либентур13. Что мы, применительно к +данному случаю, переведём так: голодной куме всё хлеб на уме… +– Наоборот, – сказал Ойра-Ойра. +Некоторое время Выбегалло пусто смотрел на него, затем сказал: +– Эту реплику из зала мы, товарищи, сейчас отметём с негодованием. Как неорганизованную. +Не будем отвлекаться от главного – от практики. Оставим теорию лицам, в ней недостаточно подко- +ванным. Я продолжаю и перехожу к следующей ступени эксперимента. Поясняю для прессы. Исходя +из материалистической идеи о том, что временное удовлетворение матпотребностей произошло, +можно переходить к удовлетворению духпотребностей. То есть посмотреть кино, телевизор, послу- +шать народную музыку или попеть самому и даже почитать какую-нибудь книгу, скажем, «Кроко- +дил» или там газету… Мы, товарищи, не забываем, что ко всему этому надо иметь способности, в то +время как удовлетворение матпотребностей особенных способностей не требует, они всегда есть, ибо +природа следует материализму. Пока насчёт духовных способностей данной модели мы сказать ничего не можем, поскольку её рациональное зерно есть желудочная неудовлетворённость. Но эти дух- +способности мы сейчас у неё вычленим. \ No newline at end of file diff --git a/lab_3/privateKey.pem b/lab_3/privateKey.pem new file mode 100644 index 00000000..e69de29b diff --git a/lab_3/privateKey.txt b/lab_3/privateKey.txt new file mode 100644 index 00000000..c780028e --- /dev/null +++ b/lab_3/privateKey.txt @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAwN27ApA2q/PD5+XmFVA+v9Xk+S2Eu8+Ry58YdyghTkep8i7h +ZxXMeT2pl9R/4BLmeKjuxobnyTeEopNZgsauC5+ohrODKB71MHyoBZ4vrDtSMwb8 +wM4bsgUUTUnI5B6hPHr3qy/z5PWctn4Trz33I+2ZfnFEFRJH+UkosW1tf2khdNjR +UYw5NTw5EJzm2KB1PHKiUM5njIROH5gBWk2Vui3VLBZCUCV+2BvDgDwEpqPXGq6x +7BvsxEmhDUg/02zhhxDDiRWIa3uinRIm4wXeiE230gShLA5APQW9eLyAY3zw9MYk +06/X+soIeaKrEPHBXJRdYmeW5rrBRCFYu6u1NQIDAQABAoIBAADAfIuog0YNV7yu +NUTtAKmfzFo6VrOivgdEsgQ46FRVTDDRBPjxaVApsOESJ4N4n2RmCDRG711l9YLq +vyUW4DPUZgRMa9z6resH+WG4OahavNnNE6wuwYXipi8c9AcLpYZGVnPY5oMEgB1o +BWyf+bguo3QOPf01ConKwrlaW1xtS0B7lJIlp4RP3QPHNqErVWEGQFPlNmdAANUK +sQSecwX6xrmVpZbztpWYWlkvQlmKVv2QATYFBdbOC+ZvE8KStVmDJc4xJDS5qDh2 +fDrzZnnuTV6AyDl4SpfXg8MxO609AinmjaquHlwt+jDltI2yr9Q1SmJaiH1sd8PN +GhzTD4ECgYEA/EbrL5SfcQJCe7yR2cBe23x5NQUkrlN7fPurdRmSrV77BeWWRyOl +c5S3URSwtbIDrj/6hA8oep1Nkr96QwMc8g6iSQos3HynWUPLWrRx/me8Nvp8aeYo +SMMdFp80j0ktrDfNVQWWVcAXYTjPyhwPDYpwl8Ieth+f1Q+M1j3LPUECgYEAw7Zc +y5egeBOIsSm7fO42B5tFuUwfp7nhKEP2gewY8vKx2m+1aUpBYqAi4BjGY7JOeBtV +uJwXKC4c6kDNStDYR2vo5l3lxJ1OlGAVghnoHOovAspa5lztaxGY5EM97Z/ip4Wl +lPTj3RvM1XgJCKF7eU/D/heZsTpfXiiwpqLGlvUCgYBavNdSoc5HQ1zOTKDZBUwY +/chKvbLX0McVyR9+Wtha61K2aEGJDNUxoUfeLebxMzoHrpNYdGtGztVb9urut2vt +1D2ir7o7A8rdXHkEAusNahpUW/vryxCqOyN3Q4QQFW4MB3YfIodUCGdgXKLAOx9e +63Bb5UJOl0lRxOoPFOLJAQKBgQC/KIQCp5spLYs539Us9U74pwS3NE9G+BufxKIl +zIGh6sMk5C+BI/IxbtAhqpE8F5DlZ22W338O09uSQrFB/jMBr7jQKa6fGDcEXcUk +SkGY3HQGebfHgcSz64A2lCcMzjoH/lX45CkccmbjqlbIg1Gi7/IzYMHRhX5Y7c19 +NUhbcQKBgQDPOxK6cvYS9/Q3QJMxOGX1rG4oNKqfLusPD+AFoonDchWAkrIrW9r+ +izDPFuNdTvoJLfRZlLDRWd/O40M2RfPvefKG8RjGV1eKRPQabE+XojOsx6uOnGPs +ICIRnBTbUWGHOdvCoMAz2uav2TPU3xXN5PiYKGy/z6+4BVxMyEQG6g== +-----END RSA PRIVATE KEY----- diff --git a/lab_3/publicKey.pem b/lab_3/publicKey.pem new file mode 100644 index 00000000..e69de29b diff --git a/lab_3/publicKey.txt b/lab_3/publicKey.txt new file mode 100644 index 00000000..912e6ccc --- /dev/null +++ b/lab_3/publicKey.txt @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwN27ApA2q/PD5+XmFVA+ +v9Xk+S2Eu8+Ry58YdyghTkep8i7hZxXMeT2pl9R/4BLmeKjuxobnyTeEopNZgsau +C5+ohrODKB71MHyoBZ4vrDtSMwb8wM4bsgUUTUnI5B6hPHr3qy/z5PWctn4Trz33 +I+2ZfnFEFRJH+UkosW1tf2khdNjRUYw5NTw5EJzm2KB1PHKiUM5njIROH5gBWk2V +ui3VLBZCUCV+2BvDgDwEpqPXGq6x7BvsxEmhDUg/02zhhxDDiRWIa3uinRIm4wXe +iE230gShLA5APQW9eLyAY3zw9MYk06/X+soIeaKrEPHBXJRdYmeW5rrBRCFYu6u1 +NQIDAQAB +-----END PUBLIC KEY----- diff --git a/lab_3/settings.json b/lab_3/settings.json new file mode 100644 index 00000000..0df24dcb --- /dev/null +++ b/lab_3/settings.json @@ -0,0 +1,8 @@ +{ + "initial_file":"originalText.txt", + "encrypted_file":"encryptedText.txt", + "decrypted_file":"decryptedText.txt", + "symmetric_key":"encryptedSymmetricKey.txt", + "public_key":"publicKey.txt", + "secret_key":"privateKey.txt" +} \ No newline at end of file diff --git a/lab_3/symmetric.py b/lab_3/symmetric.py new file mode 100644 index 00000000..4632d358 --- /dev/null +++ b/lab_3/symmetric.py @@ -0,0 +1,34 @@ +import os +from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes +from cryptography.hazmat.primitives import padding + + + +class Symmetric: + + @staticmethod + def encrypt(original_text: str, key: bytes) -> bytes: + iv = os.urandom(8) + cipher = Cipher(algorithms.IDEA(key), modes.CBC(iv)) + encryptor = cipher.encryptor() + padder = padding.ANSIX923(16).padder() + text = bytes( original_text, 'UTF-8') + padded_text = padder.update(text) + padder.finalize() + c_text = encryptor.update(padded_text) + encryptor.finalize() + return iv + c_text + + @staticmethod + def decrypt(encrypt_text:bytes, key) -> bytes: + iv = encrypt_text[:8] + cipher = Cipher(algorithms.IDEA(key), modes.CBC(iv)) + decryptor = cipher.decryptor() + dc_text = decryptor.update(encrypt_text[8:]) + decryptor.finalize() + + unpadder = padding.ANSIX923(64).unpadder() + unpadded_dc_text = unpadder.update(dc_text) + unpadder.finalize() + + return unpadded_dc_text + + @staticmethod + def generate_key() ->bytes: + return os.urandom(16) From c604edd74d0ec08bebd3f559bb1fe56dcb5099a4 Mon Sep 17 00:00:00 2001 From: Jupiter0303 <165270679+Jupiter0303@users.noreply.github.com> Date: Tue, 23 Sep 2025 20:28:03 +0400 Subject: [PATCH 26/38] =?UTF-8?q?=D0=BE=D1=82=D1=84=D0=BE=D1=80=D0=BC?= =?UTF-8?q?=D0=B0=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BB=20main?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lab_3/main.py | 82 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/lab_3/main.py b/lab_3/main.py index a10f964f..c5cad0b5 100644 --- a/lab_3/main.py +++ b/lab_3/main.py @@ -1,20 +1,32 @@ import argparse -from config import INITIAL_FILE, ENCRYPTED_FILE, DECRYPTED_FILE, SYMMETRIC_KEY, PUBLIC_PEM, PRIVATE_PEM -import os -from cryptography.hazmat.primitives.asymmetric import padding -from cryptography.hazmat.primitives import hashes -from symmetric import Symmetric from asymmetric import Asymmetric +from config import ( + DECRYPTED_FILE, + ENCRYPTED_FILE, + INITIAL_FILE, + PRIVATE_PEM, + SYMMETRIC_KEY +) from fileOrganization import ( - serialization_public_key, serialization_private_key, - deserialization_private_key, save_bytes_to_file, - load_bytes_from_file, check_file_not_empty, + check_file_not_empty, + deserialization_private_key, + load_bytes_from_file, read_txt_file, + save_bytes_to_file, + serialization_private_key, + serialization_public_key, write_file_txt ) +from symmetric import Symmetric -def GenerateKeys() -> bool: + +def generate_keys() -> bool: + """ + Сюжет генерации ключей гибридной системы и шифрования симметричного ключа + :arg: None + :return: bool - корректность выполнения + """ print("Запуск генерации ключей") try: sym_key = Symmetric.generate_key() @@ -39,19 +51,25 @@ def GenerateKeys() -> bool: return True -def EncryptData() -> bool: +def encrypt_data() -> bool: + """ + Сюжет шифрования данных симметричным шифрованием + :return: bool - корректность выполнения + """ print("Запуск шифрования данных") try: - if (not (check_file_not_empty(SYMMETRIC_KEY) - and check_file_not_empty(PRIVATE_PEM))): - print("При шифровании были найдены пустые файлы ключей") - return False + if not (check_file_not_empty(SYMMETRIC_KEY) + and check_file_not_empty(PRIVATE_PEM)): + print("При шифровании были найдены пустые файлы ключей") + return False private_key = deserialization_private_key() print("Приватный ключ был десериализован") - original_sym_key = Asymmetric.decrypt(load_bytes_from_file(SYMMETRIC_KEY), - private_key) + original_sym_key = Asymmetric.decrypt( + load_bytes_from_file(SYMMETRIC_KEY), + private_key + ) print("Симметричный ключ был извлечен и дешифрован") original_data = read_txt_file(INITIAL_FILE) @@ -72,7 +90,11 @@ def EncryptData() -> bool: return True -def DecryptData(): +def decrypt_data(): + """ + Сюжет дешифрования шифротекста + :return: bool - корректность выполнения + """ print("Запуск дешифрования данных") try: if not check_file_not_empty(ENCRYPTED_FILE): @@ -82,11 +104,13 @@ def DecryptData(): private_key = deserialization_private_key() print("Приватный ключ был десериализован") - original_sym_key = Asymmetric.decrypt(load_bytes_from_file(SYMMETRIC_KEY), - private_key) + original_sym_key = Asymmetric.decrypt( + load_bytes_from_file(SYMMETRIC_KEY), + private_key + ) print("Симметричный ключ был извлечен и дешифрован") - c_data_bytes = load_bytes_from_file(ENCRYPTED_FILE) + c_data_bytes = load_bytes_from_file(ENCRYPTED_FILE) original_data_bytes = Symmetric.decrypt(c_data_bytes, original_sym_key) original_data = original_data_bytes.decode('utf-8') print("Зашифрованный байты были извлечены и конвертированы в текст") @@ -102,7 +126,13 @@ def DecryptData(): return True + def main(): + """ + Главная функция программы. Реализует парсинг аргументов командной строки, + на основе которых запускает тот или иной сюжет гибридного шифрования + :return: + """ parser = argparse.ArgumentParser() group = parser.add_mutually_exclusive_group(required=True) group.add_argument('-gen', '--generation', help='Запуск режима генерации ключей', action='store_true') @@ -112,11 +142,11 @@ def main(): args = parser.parse_args() status = False if args.generation: - status = GenerateKeys() - elif args.encryption : - status = EncryptData() - elif args.decryption : - status = DecryptData() + status = generate_keys() + elif args.encryption: + status = encrypt_data() + elif args.decryption: + status = decrypt_data() else: print("Была выбрана неизвестная операция") @@ -129,4 +159,4 @@ def main(): if __name__ == '__main__': - main() \ No newline at end of file + main() From 862874f2663722190250b8128b87760282eef112 Mon Sep 17 00:00:00 2001 From: Jupiter0303 <165270679+Jupiter0303@users.noreply.github.com> Date: Tue, 23 Sep 2025 20:46:43 +0400 Subject: [PATCH 27/38] =?UTF-8?q?=D0=BE=D1=82=D1=84=D0=BE=D1=80=D0=BC?= =?UTF-8?q?=D0=B0=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BB=20asymmetric.p?= =?UTF-8?q?y?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lab_3/asymmetric.py | 54 ++++++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/lab_3/asymmetric.py b/lab_3/asymmetric.py index 717728d2..9e1871c7 100644 --- a/lab_3/asymmetric.py +++ b/lab_3/asymmetric.py @@ -1,33 +1,51 @@ -from cryptography.hazmat.primitives import padding -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding, rsa class Asymmetric: + """ + Класс для реализации алгоритма асимметричного шифрования + """ + @staticmethod def generate_keys() -> tuple: - keys = rsa.generate_private_key( + """ + генерация приватного и публичного ключа + :return: tuple - кортеж из приватного и публичного ключей + """ + private_key = rsa.generate_private_key( public_exponent=65537, key_size=2048 ) - return keys, keys.public_key() - - @staticmethod - def encrypt_str(original_data: str, public_key) -> bytes: - text = bytes(original_data, 'UTF-8') - return public_key.encrypt(text, - padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), - label=None)) + return private_key, private_key.public_key() @staticmethod - def encrypt_bytes(original_bytes: bytes, public_key): + def encrypt_bytes(original_bytes: bytes, public_key) -> bytes: + """ + шифрование байтов данных через публичный ключ + :param original_bytes: исходные данные в байтах + :param public_key: публичный ключ + :return: зашифрованные байты + """ return public_key.encrypt(original_bytes, - padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), - label=None)) + padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None + ) + ) @staticmethod - def decrypt(encrypt_text: bytes, private_key) -> str: - dc_text = private_key.decrypt(encrypt_text, padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), - algorithm=hashes.SHA256(), label=None)) + def decrypt(encrypt_text: bytes, private_key) -> bytes: + """ + дешифрование байтов данных через приватный ключ + :param encrypt_text: зашифрованные данные в байтах + :param private_key: приватный ключ + :return: расшифрованные байты + """ + dc_text = private_key.decrypt(encrypt_text, + padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None + ) + ) return dc_text From 65406fca3a4d4c494b1969bb1f00a392d38bb286 Mon Sep 17 00:00:00 2001 From: Jupiter0303 <165270679+Jupiter0303@users.noreply.github.com> Date: Tue, 23 Sep 2025 20:56:03 +0400 Subject: [PATCH 28/38] =?UTF-8?q?=D0=BE=D1=82=D1=84=D0=BE=D1=80=D0=BC?= =?UTF-8?q?=D0=B0=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BB=20config.py=20?= =?UTF-8?q?=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20=D0=B8?= =?UTF-8?q?=D1=81=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lab_3/config.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/lab_3/config.py b/lab_3/config.py index 11482071..2d75ff46 100644 --- a/lab_3/config.py +++ b/lab_3/config.py @@ -1,13 +1,17 @@ import json +try: + with open("settings.json", 'r', encoding='utf-8') as js: + config = json.load(js) -with open ("settings.json", 'r') as js: - config = json.load(js) + INITIAL_FILE = config["initial_file"] + ENCRYPTED_FILE = config["encrypted_file"] + DECRYPTED_FILE = config["decrypted_file"] + SYMMETRIC_KEY = config["symmetric_key"] + PUBLIC_PEM = config["public_key"] + PRIVATE_PEM = config["secret_key"] +except FileNotFoundError: + print("Файл settings.json не найден.") - -INITIAL_FILE = config["initial_file"] -ENCRYPTED_FILE = config["encrypted_file"] -DECRYPTED_FILE = config["decrypted_file"] -SYMMETRIC_KEY = config["symmetric_key"] -PUBLIC_PEM = config["public_key"] -PRIVATE_PEM = config["secret_key"] \ No newline at end of file +except Exception as ex: + print(f"Ошибка при загрузке конфигурационных данных: {ex}") From 99ff8387cee850f872e5155fa84f4bbc63fc42b7 Mon Sep 17 00:00:00 2001 From: Jupiter0303 <165270679+Jupiter0303@users.noreply.github.com> Date: Tue, 23 Sep 2025 21:20:14 +0400 Subject: [PATCH 29/38] =?UTF-8?q?=D0=BE=D1=82=D1=84=D0=BE=D1=80=D0=BC?= =?UTF-8?q?=D0=B0=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BB=20symmetric.py?= =?UTF-8?q?=20=D0=B8=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B7=D0=BC=D0=B5=D1=80=20=D0=B1=D0=BB=D0=BE=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=BF=D1=80=D0=B8=20=D0=B4=D0=B5=D0=BF=D0=B0=D0=B4?= =?UTF-8?q?=D0=B4=D0=B8=D0=BD=D0=B3=D0=B5=20=D0=B2=20=D0=BC=D0=B5=D1=82?= =?UTF-8?q?=D0=BE=D0=B4=D0=B5=20decrypt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lab_3/symmetric.py | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/lab_3/symmetric.py b/lab_3/symmetric.py index 4632d358..a30a0ba4 100644 --- a/lab_3/symmetric.py +++ b/lab_3/symmetric.py @@ -3,32 +3,53 @@ from cryptography.hazmat.primitives import padding - class Symmetric: + """ + класс для реализации алгоритма симметричного шифрования IDEA + """ @staticmethod - def encrypt(original_text: str, key: bytes) -> bytes: + def encrypt(original_data: str, key: bytes) -> bytes: + """ + шифрование строки данных симметричным ключом + :param original_data: строка исходных даннных + :param key: симметричный ключ в байтах + :return: зашифрованные данные в байтах + величина iv в виде приставки + """ iv = os.urandom(8) cipher = Cipher(algorithms.IDEA(key), modes.CBC(iv)) encryptor = cipher.encryptor() - padder = padding.ANSIX923(16).padder() - text = bytes( original_text, 'UTF-8') + # паддинг + padder = padding.ANSIX923(64).padder() + text = bytes(original_data, 'UTF-8') padded_text = padder.update(text) + padder.finalize() + # шифрование c_text = encryptor.update(padded_text) + encryptor.finalize() - return iv + c_text + return iv + c_text @staticmethod - def decrypt(encrypt_text:bytes, key) -> bytes: - iv = encrypt_text[:8] + def decrypt(encrypt_text: bytes, key) -> bytes: + """ + дешифрование данных в байтах симметричным ключом + :param encrypt_text: байты - зашифрованные данные + :param key: симметричный ключ в байтах + :return: исходные данные в байтах + """ + iv = encrypt_text[:8] # выделяем iv cipher = Cipher(algorithms.IDEA(key), modes.CBC(iv)) decryptor = cipher.decryptor() + # дешифрование dc_text = decryptor.update(encrypt_text[8:]) + decryptor.finalize() - + # убираем паддинги, которые добавили при шифровании unpadder = padding.ANSIX923(64).unpadder() unpadded_dc_text = unpadder.update(dc_text) + unpadder.finalize() return unpadded_dc_text @staticmethod - def generate_key() ->bytes: + def generate_key() -> bytes: + """ + генерация симметричного ключа + :return: ключ в виде байтов + """ return os.urandom(16) From 5de4f19422e6c00bbdfac2c31c56e3b250d8e5f5 Mon Sep 17 00:00:00 2001 From: Jupiter0303 <165270679+Jupiter0303@users.noreply.github.com> Date: Tue, 23 Sep 2025 21:41:41 +0400 Subject: [PATCH 30/38] =?UTF-8?q?=D0=BE=D1=82=D1=84=D0=BE=D1=80=D0=BC?= =?UTF-8?q?=D0=B0=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BB=20fileOrganisa?= =?UTF-8?q?tion.py=20=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=B8=D1=81=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lab_3/fileOrganization.py | 163 +++++++++++++++++++++++++++++--------- 1 file changed, 125 insertions(+), 38 deletions(-) diff --git a/lab_3/fileOrganization.py b/lab_3/fileOrganization.py index 7f2a859d..ebb77631 100644 --- a/lab_3/fileOrganization.py +++ b/lab_3/fileOrganization.py @@ -1,57 +1,144 @@ -from config import PUBLIC_PEM, PRIVATE_PEM +import os + from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.serialization import load_pem_public_key, load_pem_private_key +from config import PUBLIC_PEM, PRIVATE_PEM -def serialization_public_key(public_key)->None: - with open(PUBLIC_PEM, 'wb') as public_out: - public_out.write(public_key.public_bytes(encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.SubjectPublicKeyInfo)) -def deserialization_public_key(): - with open(PUBLIC_PEM, 'rb') as pem_in: - public_bytes = pem_in.read() - return load_pem_public_key(public_bytes) +def serialization_public_key(public_key) -> None: + """ + сериализация публичного ключа + :param public_key: + :return: None + """ + try: + with open(PUBLIC_PEM, 'wb') as public_out: + public_out.write(public_key.public_bytes(encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo) + ) + except Exception as ex: + print(f"Ошибка при сериализации публичного ключа: {ex}") -def serialization_private_key(private_key)->None: - with open(PRIVATE_PEM, 'wb') as private_out: - private_out.write(private_key.private_bytes(encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption())) +def deserialization_public_key(): + """ + десериализация публичного ключа + :return: публичный ключ в байтах + """ + try: + with open(PUBLIC_PEM, 'rb') as pem_in: + public_bytes = pem_in.read() + return load_pem_public_key(public_bytes) + except Exception as ex: + print(f"Ошибка при десериализации публичного ключа: {ex}") + + +def serialization_private_key(private_key) -> None: + """ + сериализация приватного ключа + :param private_key: приватный ключ + :return: None + """ + try: + with open(PRIVATE_PEM, 'wb') as private_out: + private_out.write(private_key.private_bytes(encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption()) + ) + except Exception as ex: + print(f"Ошибка при сериализации приватного ключа: {ex}") def deserialization_private_key(): - with open(PRIVATE_PEM, 'rb') as pem_in: - private_bytes = pem_in.read() - return load_pem_private_key(private_bytes, password=None, ) - + """ + десериализация приватного ключа + :return: приватный ключ в байтах + """ + try: + with open(PRIVATE_PEM, 'rb') as pem_in: + private_bytes = pem_in.read() + return load_pem_private_key(private_bytes, password=None, ) + except Exception as ex: + print(f"Ошибка при десериализации приватного ключа: {ex}") def save_bytes_to_file(data: bytes, file_path: str) -> None: - with open(file_path, 'wb') as f: - f.write(data) + """ + сохранения байтов в файле + :param data: данные в байтах + :param file_path: путь к файлу для сохранения + :return: None + """ + try: + with open(file_path, 'wb') as f: + f.write(data) + except FileNotFoundError: + print(f"Файл не найден: {file_path}") + except Exception as ex: + print(f"Ошибка при сохранении байтов в файл в функции save_bytes_to_file : {ex}") def load_bytes_from_file(file_path: str) -> bytes: - with open(file_path, 'rb') as f: - return f.read() - -import os + """ + считывание байтов из файла + :param file_path: путь к файлу + :return: байты, содержащиеся в файле + """ + try: + with open(file_path, 'rb') as f: + return f.read() + except FileNotFoundError: + print(f"Файл не найден: {file_path}") + except Exception as ex: + print(f"Ошибка при выводе байтов из файла в функции load_bytes_from_file: {ex}") def check_file_not_empty(file_path: str) -> bool: - if os.path.getsize(file_path) == 0: - print(f"Ошибка! Файл - {file_path} - пустой") - return False - - return True - -def read_txt_file(file_path: str)->str: - with open(file_path, 'r', encoding='utf-8') as file: - return file.read() - - -def write_file_txt(data: str, file_path: str)->None: - with open(file_path, 'w', encoding='utf-8') as file: - file.write(data) + """ + проверка на наличие данных в файле + :param file_path: путь к файлу + :return: bool - результат проверки + """ + try: + if os.path.getsize(file_path) == 0: + print(f"Ошибка! Файл - {file_path} - пустой") + return False + + return True + except FileNotFoundError: + print(f"Файл не найден: {file_path}") + except Exception as ex: + print(f"Ошибка при проверке файла в функции check_file_not_empty: {ex}") + + +def read_txt_file(file_path: str) -> str: + """ + считывание строки из текстового файла + :param file_path: путь к файлу + :return: строка, содержащаяся в нем + """ + try: + with open(file_path, 'r', encoding='utf-8') as file: + return file.read() + except FileNotFoundError: + print(f"Файл не найден: {file_path}") + + except Exception as ex: + print(f"Ошибка при чтении файла в функции read_txt_file: {ex}") + + +def write_file_txt(data: str, file_path: str) -> None: + """ + запись данных в текстовый файл + :param data: строка для записи + :param file_path: путь к файлу + :return: None + """ + try: + with open(file_path, 'w', encoding='utf-8') as file: + file.write(data) + except FileNotFoundError: + print(f"Файл не найден: {file_path}") + except Exception as ex: + print(f"Ошибка при записи данных в файл в функции write_file_txt: {ex}") From 510eadab8ad7d053e54c457097d67da5ae593e66 Mon Sep 17 00:00:00 2001 From: Jupiter0303 <165270679+Jupiter0303@users.noreply.github.com> Date: Tue, 23 Sep 2025 21:42:18 +0400 Subject: [PATCH 31/38] =?UTF-8?q?=D0=BE=D1=82=D1=84=D0=BE=D1=80=D0=BC?= =?UTF-8?q?=D0=B0=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BB=20=D0=B8=D0=BC?= =?UTF-8?q?=D0=BF=D0=BE=D1=80=D1=82=D1=8B=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB?= =?UTF-8?q?=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lab_3/symmetric.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lab_3/symmetric.py b/lab_3/symmetric.py index a30a0ba4..b4d3efe1 100644 --- a/lab_3/symmetric.py +++ b/lab_3/symmetric.py @@ -1,4 +1,5 @@ import os + from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes from cryptography.hazmat.primitives import padding From 8d68328d982b9501865525c93b28fac6cc0d7ece Mon Sep 17 00:00:00 2001 From: Jupiter0303 <165270679+Jupiter0303@users.noreply.github.com> Date: Tue, 23 Sep 2025 21:58:17 +0400 Subject: [PATCH 32/38] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=B8=D0=BC=D0=B5=D0=BD=D0=B0=20=D1=84=D0=B0?= =?UTF-8?q?=D0=B9=D0=BB=D0=BE=D0=B2=20=D0=B2=20json=20=D1=84=D0=B0=D0=B9?= =?UTF-8?q?=D0=BB=D0=B5=20=D0=B8=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BB=D0=B8=D1=88=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lab_3/encryptedSymmetricKey.bin | Bin 0 -> 256 bytes lab_3/encryptedSymmetricKey.txt | 4 ---- lab_3/encryptedText.bin | Bin 0 -> 24032 bytes lab_3/encryptedText.txt | Bin 24032 -> 0 bytes lab_3/privateKey.pem | 27 +++++++++++++++++++++++++++ lab_3/privateKey.txt | 27 --------------------------- lab_3/publicKey.pem | 9 +++++++++ lab_3/publicKey.txt | 9 --------- lab_3/settings.json | 8 ++++---- 9 files changed, 40 insertions(+), 44 deletions(-) delete mode 100644 lab_3/encryptedSymmetricKey.txt create mode 100644 lab_3/encryptedText.bin delete mode 100644 lab_3/encryptedText.txt delete mode 100644 lab_3/privateKey.txt delete mode 100644 lab_3/publicKey.txt diff --git a/lab_3/encryptedSymmetricKey.bin b/lab_3/encryptedSymmetricKey.bin index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..adb2ab4c5db381c4fe539c3ca41a4aa5960d89bd 100644 GIT binary patch literal 256 zcmV+b0ssC{^aiB_!dhwlx+0lU4JtYTof$+v;1TTSlz%}d9T8lc+%@B$I2@3Mo~uW& z63`}vG3PsEN7+rn76t+3iqvymw40&($be1w7O0?>w-2V)&SjmT;Y>7B_oGd9aNr`$ z JgQZsEbA~xStFQv@h>W&uyfWZ_2u?)gN}9N$kkU zRSZK~NjDV({_mq{b`{hWI&1jq47J(v3)aIRMpENMR7p*ftpBIKYr*OHtH;v{7x!Em zy7&5bL}-S-Ry$}nK1MIC@KzP=gPT_xcm(C7T7}jfoE|heDEr09QE@G)-TIgFCYK?F G2d(pG_khp< literal 0 HcmV?d00001 diff --git a/lab_3/encryptedSymmetricKey.txt b/lab_3/encryptedSymmetricKey.txt deleted file mode 100644 index adad13a7..00000000 --- a/lab_3/encryptedSymmetricKey.txt +++ /dev/null @@ -1,4 +0,0 @@ - -/ -qX!%~^'B*hx2Yr' cr) -FU,%ݝٍ*lq[ ?m]Y5zD .] hyz5=,Pu>;mv5[hU=@J~?Gѣx,J!%d1Ў{zSqSM40Q^iW`9cx.Yzj:ʪChF-{z6M)}^Ҍ`F('Q'9 ?2Dvf%-sJ;2XCJ81BYKX)iLl? XF(`XZg_vIMH$*sG2JDtn0(6{h4i{+ANs(~vg$>TSIW2aeU+ zdE08Vx9{$S%Utqhz`w}hA0mai$KBs~TYl7N@r0Xt#tG&!K>G6$oj9$0GJl=z7RCKo zb+WlDEvElw$kso_MFRW?HRvh&?4n==i(WgyEXdgpZ{=I>&L#8`*L=z#&|f4c-8 `K^9q5q>F{ELb7Msf!?cQd3|npH}q=&XH0-9GG)Y8YA4F_6T}01R$? *vnCQkhc}HeR@JJ+#PWe)PevGf_k>r zJ-lYk_CBQ}912#ko9js4RTG+0PvJ1PP3Q*N6NBICQX|TYL=#pr$R sC-s; zcoDplLG=WkpL{DN5oG?d=cLbUHgqVAm#k2UO3ZI0j1{^EWT!-^1U`%sgyLeNS&-dC z;ceMc+to-s)#0DbD(p-75dqqp@oQdDaf`BAND=nZE`Ao|R0n48m&Zh|!SJj9q1=QQ zR{pt$Xp;Bs|91kQKZ?;`Nx61`#_9A(iW{G54COvrP4-G!a)lw~dWo@p-0;#leJ92g z{er17aIaWVf3BSGpB0xp4TE{#M+i)9JO@6hxA6(yRDa>v?FxD1KQd65GcdDAr?#$z z;-yVezgf?s+4g8G#WSUUX=N!5{;PSleIP!{bOB_5d(~)*lJO++!6vZUpx5Z18BN>C zy>^S0?QL}w3U>8YMmD2(tTGXB_Cuq`RrAyvNYrI8wTQESQ+e#vK_r8nLJgq0Qjv$+ zq*w1$$t=?zC;evr!2Y;$4yQYu){AE``(h>-v?8<$&1z7O$nCBz067MLe_aC^3!7h1L;>S21tXY3ra`v+1h zCUMeC=WTOQWw~Oiz|)Fvoc;Q8-4ye9g<%2}@t4E=KIAWWl`kyaPCG@rcssvl%muq; z-g3A^?kuWEJjmzEkJM6hs >NP8(f|VQGT(WJFVHX;MgJNNmW;;VOKxo+W?y*A=)_cPWWtv4MQC(VJau-ff=Y_O z!IKW*cetmL)|~r*QYov0E~_gGHhkP4M?wBbF*=NVl`l(jUX13lV+{Kuw!E_Ew+c=# ztn)TUL%>%}8&EbXMhwV!=0n|(Oii_9P(5V8eiIIm)t@^r=tV8WCV+XJX8kXmu5La) ztn&ZZUn#SQ)DU=-S=;zj!!vyY%9N=lIrnd-B3qc`-c}yrL6!Cdl4QmzwEDVVOn4xK zEswLW`dRn 5@bW - zSYr1rJ4!z1@y;u?huO2Og6cf(a-EC1?esq-^b8(zd6|BPAMHCIq^3PNm6W5%0c_G~ z1RV-vq_^hVX4*8_* `RK=JEjXult(nnc$r3K7n+-98(b|7$S8o@FY-QJ#ZWzT-RUS&xn8_tV?wyTwrp z_%pmA!Z&Kzi+89rF-o&e0V~0*$%j*s+CZuax(D`Ks*E_VALYlzmUUKT$g9D#j>Wz) zF~k6ZKX57eMp~x(4bm(z UuNRtlmak| z`2d(L)iFCFrLWamoIwwsPSQ2Y8Z<|gc_gQqKgT!N-fy_mohrYw|bkS zCWYN%mUex_U;1yv#Lp8>BH{-X#tJl;2#)_Out1m~qg3_lL-af(%s@ISqO1HlAPe)D z{kjrb2y*%g6 MFiPQU*Bg%G*oGc=H zFT8RWU)b@ior1D~mF|ZB%q25U?7Ltqgdf`8Ybp_wUGmb>L+d4knUY|8anXKRck80B zB7 xPOP#Tk1G5WjxLSKDfY*EnwPoPDgc~uSNQGNL z_k(=LWa*;aM5&IFh-bE~&2l=ez_j;#HtKOicXa*K==ikwh36w`=O&!!(rLTV#?utd zG$iD&GE*#ROOZ 0*+O&<_8R<=2aof6Y=6>ex@>RhVhEdP1DvECTgBH zhg@ND9UA9tr_wNMI7nejAUfJ`-?_wX1QumGoeU{n3TuOt{Bu-Rr#>01VVk} z_Ov^ =6TocMACN7nh)(V+PM!^sXL@XKc5+8HOBQ_rq^Ao183L3A)S7aH zxAH@Wauozf!rVd-A@IxoNqJRf)de~=d<4_{o`E|GXdf1V&>Em~XBhC<-3yoolGYg; zFR8l)Zu4y3+?0jcMGBhuy+IUQ&SNvltjq?qXyn8Uih_`Ky-cWk0r7 @WU^nwrk-; n^VL&+&}`I7z`4=CcZJ(lL-2`nxrH?AS{7- zQp$=`M(;nq1seboooIPoHV5KRblx9R-;tUWb+JUBi(Fei;06ZjQM8xYxC2h66CT8l zNGT{i5Z#b^R-;do6v^tz{HU!#&%=KiQM~jbhYlNm<~_QyUWgqFxri4&>Yawa92GC{ zwy~csEA;)5oD^8Z$^wM zANZaj!^nS=8A9a?DE!&pGWX*IDM=UhL-1nHzq`44rXsxr#^@5I*SW-vpj^^ELkZ9X zv)eK5`4c6*B}U#GA$2fydDejEJl- Q5-{>G0PxK5+%=y-!$2MCH6W?ShO* z?)Yj;*u^>+{*?paU~^zUF4~;&pv*&zIt5L %-@$+((3|Pu ztwT%Ei^AycMj5!ewOc$PpEvqf-lmrFoyS#M3d*;Mv_i{FFR(!;ory94|Efx}r1a-8 z1GEKdw3ANg0?m>M+~Xe5fuK ue4Tc3~4ca?{5&V$;vyizAznT>zf$*!Rd`aRWSY{GkPFEk~rj2_HV z7I4cW@bb#{C?22Bu+`#ZIEy$fuX%fn!X*Q9l*&DTi v=+E)y8g6!& z6#EJa|1}r+GXe0|M9Au>z>e1GqE#EPxuv;J`TVwY-t`OU3-s^sf(WZ3MGTx17W&bY ztqSM19?9pW4JD^S@d>UA8Ze*{V?XcUp4j|DN4^dEetkKebKRc9n3YQW7|uoT22t zRiS8Q)#&x5uQW1&i6&k-=KKx($-ZS$fc3JdL2q=W#uy63PcC*hg(a2Bn_2L{O&-NI zHkeAS5qA hTzX z#!+Ts!H|sdQaL@b^||dAW`n>Aj#qHS89&53o~M^8M%m(dW`Y=K8!A?;h4u;oG=i+Q z;H9p0m<@qM>gUqJE;daIG{)1T{r>^I+<*)~W1IC>1@k3cKBJ0luC*OF|rSyi$_9{AW_U z!n|hhTy{=d;y4RU)GpQOrfE)Va!7w`CPfSyQ-q=a?fjshT^*ScCCH)HXtN2@UK4Rf z@1{GvX2t^mDsHoJ1~tMN{#+Lme6&6^MzK4E0AYgY&nwlOfD|1NO7lkb^hre=iuzp0 zVq?l_VqQbbYrEll`y=YOMRc}0A!dyo*ifr9WsY;zvgU439KB#8SJUb<$Vhd4OhEoJ zR&TlUTr^wg+1Xpagu_+RQ1WTQrUlWh^kxBV$9VHu$X12CW#217EGFloJL4br5140~ zG8LAlYtJO5B5iPec{J+SzpoJ)=m!m+k5P21QTN_qaj40D*{rHYM^ggzhXS(e%IP^W zN1KeKvhn{^2|<{#wiIV=r>5z*DTHW_5X`1*<+a~OPh(fMWu(dDHK^xtA}A#|XcZsj zm%9=`smr=z(Y(Jzd{@4c6EXR~dF8U+KIX YE8uOqmxW;N2@H&=p0sPZg5a+ zsCcfgoTTa{h!p6P!b5Fb?}Sd?GSuJ?!XpxkB{xbl7uzg+TAP^#EirL$gHRXKzC6Bx z{ve1 2Ele6WN5`YtT#^D24U)Or zcO=Fa@@dmURHZkQg?&vtFv>P2a3!p!GyGrKtovn1)CMP?QqAN@Q;C1zD_;^mE%(9` zJJPkqrAZG^XrIudUuzuH%bAF39Ei}faYIK+vY)P(*(2JKS2`qg;#1UDOrIby>=&ES zca 8LgOQ#4ke z+wi#urI^5VjZ47Mv}T*uciBs2!3dQlylYg0HobUyW5zF>-Dx1E*NQugOPX!i-L} zB2JlK)w1Qn?Iww^s?ItKS<+XgF>_A^WjDbJ=yrIII%I{zKD5y69Ic$(KA9BVuz0#+ zO>U58Mx%XyMZSB$@if*qSs2!i+FVEblvKzAEi{6w{3M{w{cpUAUMhy^X<^Ft(CxH* z{VRKbsS0XcJD#byQwc_?vY;U|ilNLv;ux@}^OI>+o#m)5Rypz@I4|ah;QMn}(!AKC zg%ktl&zXIF{y>?A*jIm3B168iqn=39GgUf>g_0r;^CA1sX7c_(fRS^jN&I~wX;>0! zr%ifgR@ESGTiC5$o@ZMO&*4`=1Os1%q4?|g+Fxb4508-6J8Pz>I-V;P^GWzrq+@;g z5R%eW#9NMj>XFd&ovoN|3RE2WY6Q2G?hhfrq(kel4?r#qSVGX;QoG2iEl>fMIx1!C zrluS8fQmXFsGvKW`tq4n4Oe?#UrIl=3sptl6(~k4wa0Cj95KT+l}}O^l)k31M?95h zKG%kMn%#e7iPuF<<*iJ$6*h5_DF<*4{n!0*@1AJhcBQ~t8lj&*2np{C1%HmiAsEWH zfQq1Pf=I%yB{>0v*<0ICD&9)0hMmsP$BCX^`cwBg8|ywJgZB*kxQJ559@I-LPD`d* zM#zXNc!K|{+Y0_wP5h@4Sa;0&HXRG$c!_=FoE>Ouq?Qo%Vl$|Ul%D%A(@L&|vr7)7 zkV^MrDu?f0cp*^i5agAD&9Y8n+=N_92=&p?s1FO-Q^@sPD?;gRMSkupNckoYrzCYi zk=wU+I}e3Fl^>^fvGpN|4CI5wXy1tvGBgkTMZb?wwK4|gvwxL&HEse9TBTTD-`YMP zTiCCIMMG^6zvsyy+8)bmz&4$=(EYN%R(cKKPUyqlE7i|v6k%CcgGuo!4F1sFNPHwL zPYdf1Nh($_j~?y$(K0OH;`a_foE*2sf-F@7dH0=K#^y-T3?jWbd0;jQbgGC~Ovwrb zIt~wjzdxI1o)mLjxL_1Oj%N?1;y?Y$kQ$Gms=%g_Wj0OzK32V$b(H;FDaobvGD8Gq zu~qR;`Ghp_->#m0fzU}oTcYO92j8L`xB#T=MK>b!f5|b9Up~r>|J3=%<^ZsBuBh7s zHt7SW+OQ-wqeE=CnXKDPlYgf`--vjf0ibf*>mGBB7eRG{_r7@IRzbJA!tE4}S~<8% z0_dJ>WG=ubCEFV>V@sD -K* z`7q$g#JI)Cef1ymZj~Gj2?3bwI*3R$p~=`$;1epc3jjr((8oxs)iTnLl8*^p;mtes z*N8;L2rL`@{J6Onpx9&Y Z=>%GKpS4Kp(|94;*+fekz%!Cm6m8eD9!@bEaYC`x zdzcyvdu~*A3Z&Y2b|=pyiG_6R)!t`#X0q<0estuJ95&({ncVHWX_Hn6k*QgkO2ho6 z^4z-g7Fs4U+|by_zMBR>3bM;Y(k+=3S)8#*G6Hs- 6$z42-mB1WG zgK;X(VbW7*TB0}qy6DAZO@ldjAW+++Fz}YV*sFirfBsQDj(KjA<-#=m#Tp?o0!C$n z3aFOr(IeD{n&ktx!TQ05 D78&--9cefIeH((wW@SZg YJnhKh@fwWf zdHIYOI2{lc?=o{Ty@{%uwMGVC*$BlqnXhmTfd?>@QM>G2r%q@~9N66FDxrE#9w^8N zbSrOHd%(ZKV5V*r=slQ2d&X6y5x8tgm8}l|t*ySof#M|^rq$+}f;YGvdF&O&Yi DJrP;<>&T+azd|?kBB6Z `eb#a+`dJ`Zw!goesEKKe6X7*^v{u5OFa+$!k@P%fEOP|tNc78<-&tyrL} z$#SC$y{|{N2 *+w@RBbAsJ$JkyGcL;yl7YvPK7P~ zm7BUFl;oNDkqyN^Du~w_xAH=IC;NVAbqUrS<+YI2%*b0D=m|dG6iy!)uoFa{^94B0 zFsjMQAEU_O#%|v0qJ8{pP~KXCME5Yt9x*N|g4a>o@NG&PP6pN&ZI#ORjw>YiAg=D; z`k?-o10?<2gRXEld6?sjRA53k^osP3t?nz`=gCeJ@wiSc!t1E`ew7C0O8(-_TgdG( zw8_Q Cq8R!3^-U(0yH79W7E(K4ON-!d)YlF z6FC>wN~Z2h%@q%bd&bt4#Aw%%5!B~Ij`j8uus%tWdQml&;Y4gR#L!c1)TK_uR(3_s zPb1*W>bCEGDGuG;Dl6+hh}nu_wZ!8L1E8v0h1k(bLT0@Lg6-UYUMVz+FqX~B#?nA$ zijc=x$ k$KjkkY{AS^PnaWR``B0I;g$^zVNy&4U14>?FYizC=nL#k5r zOOe*kGVyNU)roV_9y@uy9tQS1B}D3|l}N)Wh1V*~qTM^2OR@PnL-eCKj^BgdA04a2 zF;==4!6GHy&AnuIuE4de2Hgu|&>WGfZL7oqYv3!B^x~LP^x`ZiE0B)4(QRM{!QF#; zWX}Wt{!uKm>pa8q #iD+WC3$*axV`+Fj{=yHI3i zgtGl_*l-(O k=-Aau+c}&fMP1LE#_L92bKP V zHfCbWFQCnwuMYL0T+`1R{q+psTyT522(!wsqB-^<_JeRye(xMoJe_9%%=cu;Y>Jgc z@*UteA_RxUEHtWB$>-`?Q0HIn?MXj0-Ipl|t?C$6DMOWY%P|~uX_F8CckI37qQidY z)IfmfpQhE^*7L5A=BE5?+fjM-Nb~7?-fM5p%-y+5w%WpQhvg25IZL$4hlW9>_woX% zLwMR5m#1Ay!<0rMwZXPnqD>!h*?8^}HJKWqpb5ERZ|skVv&WLExqsX{GXAd??SN)Y z)E}_%6*YM|fY-8BRrN{kQD->-&Y^|0i+xK6tvs`ptSx63Uz0)fkit#>4*_Ksf21 Lo 3T9!d2q9FwT^sY*ouedz=>7v?)95R)Y>I?NT*@&Z=^bYfZ{`YWK zC-hfgl-0)jX(eKWWY#y|R6$?*3lS3juaH;MtB=7bm|M8cq1VW{($^_^%{1kr#9O}< z7~k5uxLcqrH;sdYER`3E?~$wmq+4z6G0T w` U6K|` zn9mFMI}z?o<^nn}Y!<@du@Okp+SH{0SOgqA>Kdyy%b9b9vwb_>W%+IC$vXIq0Y_Rj z_qTT?aJl^5QyB%y&ue~RO5aYAI1 tTT9kac?7v)e(`o`%#zN`K&HM;GJt-RRMb{g?-cF8^ym|?_aiO-jlaK z*p*k({E u`)!0F}^-B-x)jsGOi zYi0l{LGZ+NPdF(OC;srb)1LD3pj#Xbb+3AC$)#4p%j4(oYm2X14kj`Vq3CZJrc8zw z)-&W}lvL %@sQv|$iOyB~fEye#A z;p74X0hSHPCYlS&4(b=q!EC=Q<>2*iFtY+uApX3~xl=3FwB{#cF$-)Htk?^Xx|9Ia zk{ETTjlIQFlP~M1+K(-Et-=wZoWl&=;LMvXnhXaj;I7TUPH%s_-C?W9X$t8lw-2S@ z_YH=E^5S{Q-nV^&msW-P>ieD-`^!5A{U68QroC;Bf&nPw^LGNzhS~=C#U@3itVF2h z1tijTc{`ZcCZx_+pr)R)JDp%4$4W3nx#`Er`@vY0Sa#A_d9tOoTyv~76^?R|wa-{= zVN&lat%NFGw6gLj$RfuR5!6XK1|E~m*e`_3Io43ancDcDltq5Uz=*52nSX;IPlmcU z1hv;%mOgF)K`=o&?7{IU{p>6l`^z?mGoDt_z9sQ!wYRHW1Y2#aA_l&u(mykQyFr GC z+xam@ 1?1}!ciOUWrx|jA~Q%4Dk?q|@m>{{!^WZi|Bxl&6papqzm zcDv0IT(yozjoMRXcQK!{H|+2E4}CQUI&l}haN_Hp*OTr#Q)b2>@TjLc#@yDRheN2# ztJoQ*z$YI)6M}3y 7!+sUbMD3uWws?WUI9exvSN5cqt@TX?B}B#uG9fa z_Aqq~r}KZGn{_r#bSYQd?O?cVq+wg--SO`~Ed6R`)G&)4JxRK ^=c@>fVHdc?CnR(oz+8d(x!IeOhmwirRE?Fu zj#|3FevXjdXK#4#8S_d1?j+VpnaFLT3izZfR0`?prC7g}+Pz*4&(@0s9E>w7!*O}d z_fk<6q 20R2aqJFhyq^?9@cC8t zsnKQ4giCjyKzb;hgN-h5;AQ$#kHFOLCf5rDjZR&x3G_U&Ut2IhK ~5RuzZ-*KC?h@BeLI-x)dBPu2I`X;5z}~M=FP&8=#EIv?UgxEhzyxHTLAf z)pbCPtIBIrT~w?F(D?kVtFsd%btu9qs1=DQWDSSM3%<>e_o5b=c#gb01m|>y-5%W3 z(<5Fl#RYgE*aY( M(`ksgT+Hw;w@L~3x-ZC+K>pb^l_bTPxwZk_Ly z8ei)#EesiMaZoHmCagJyZ#KpAdwvm)^umVNKOoBz7XiQr_1$dEIr;ufhwIOS)sHB8 zN&1=?M4+hKkA@88USfs>V=CYU7b@`1bN$B6hzOdmA7kYniZ6q{0~0s(1#7gnFVNup zf5i^>6lr^?>R;%b?8kp9WT_j_%|P1$d)COVoPsoK_P_Y;Sx?D1W*@Rx#^f7@q6fl* zC77~6>2qquiJ-h)hzYruI^$>f&Jt?#S vy4xlJ2*{awMmQ)quQ_4F+(exUjDFYx;~&Y@OW zuHEn60eV7ipZ oyAcUbleFP0D5>{J^MYK)rgkR_ z7;hd`#gj#+2JOqo-H!tFJkZkMFkGaDvUMliKPJ<#N+uZ&F2Gj!t@duTA{74O_t;pI zu5_emX?#Q*eV7`HY3KUfPS7-`yn)+ly-|PB-bpR&k^eZ^St7v8uImp1Q6*E1```S1 z7xX0CL95V0*>q->g$P~7AH*z*al5RhbG zwqeP1 ^AA)* !s{4}y4nJzzq}%evoLsj*z6%RhBIpB>%JHcx_;<#ueR<9hs#Wv$N)I{_ zI^(Juoej1?7HKjWfvUg;CFM+CzI^o>`mw2g1r9?fe{I3H?`~B%6Vc?BJUMB9Ct@1z zK^|hBoy?U?6jlv%q}YRh|5|1)Y56au8%h#q(-GQQENeK`)c~6yoZ)XxU0tai2U;&? z`4N926WD2Ke>7Wu%SR+z0ZooabJiQz>+SbY6JD=-8*T*LnpyUt17m&xO4kF%ZLj3B zR6H^A(+^#tvVz5nD^}I3J^&30h &Hg5EkD*MvMi^WnjsTT^w$WDkpeRKvbj!c^89=)Ru;pL$7afzVu1U%j$P;VzW5C zA>7JnR*umE3OO#~$+Eg=ZQ^Bts91?ZzoWnWZ`Aa$73Z;$*^OjHlg#Hk1%)KX89)fo z!EXv!GD1;b^6qVFZR(qTiP+S>N<`U1PlY>DdZX~7ctkD;H9o7WuRxSxh6Xo)6|@{4 zd@YcrtDL(T0rqu8)Xj3Whhs> $^1DX_|YNx?IJY)e0TyM8f6J1vg!VhD~J%Wn%q zm>q9I mm5=39yF%*Mb ~ev9`OET1G5>{EB)q2u zqbHz_ W!RAtO~cb *6SXaITv8# z{3BD`%GkPay}|{q=(6o$8?Ud&@|RyX3FKwz9rv$u=di$opLEPH^x9G$GUZ#WR-?{~uY=v80e@|xsR#tfhKRI YVXgDiCUFrt9MbfNoP zoAz>6#~8M?$tcWab+A-W^thE8F!+LQfk#g`+ o%cI9Fuiarpzi%g$?C z_9$w fOVnoAxFl?^Bh6C^T$o=pBJWX+~bHWCjx3ieAtIO&L6U{*N9sn~DX(@pT*4aY( z_e*UkLo(OULoLG5>hT`JxZww-xr7JksQ?hnQHt2m{upw$nVzx;dPp1W?(T7F_$;{y z<*QE}X@Y!T1SrT$^46uu>52leuBP-rZDpt1xgLMEr)^Y6$>n ~L^i>q$r@axiRe$E^M$8;qU3wK|zqRQl zh^z{adIh_uRV^WF!`|tuho7acDnjVDqNX$qxpPtOi0r1Chy%J!-hpWT0m%S#iORMt zlD_sEr~TqU10Nv|TW~p|C z1FLo >yl?9=)BO z`#=U~u{p$18#t#HRUU3%+A|2sT$)~WDO6CG{`=HU?VzDo9>RusLwjwPbB<*fu&`__ zv@Jku^)g#x?8=*udh6{Of!$B&%uSI!1!s8W$3TsMGka?o9K^4fE2pCgI{JiEteW}P zk-fJHoF#W=3~5Tdt`_WqlPegduxgRAzf9eqp_G?q;&D)s0h(jdhZDOXHbknvw!C?% z02Wh=0NtOePnDEfRe$s)b>%8<=D~AeHu;%I{izt&&&;(dW$ C_PlhZa$j*_ z$}yt#_Am_r%=C4c_KH=H7T^c0-M$SAfz!CGwE3B-omx3)9%%s!|H|f<(HjuA9z*B7 z1O !% zv57MGB>7R`;f*!!t8RAnh0tVy_&F@fYfTk18T3*O#h47wM1a-e4?z$il(difl2Dyn z*!48e&!Ta&MZW{clJ7rfYSTa(^Fl?YEP!X#TT=b9ex0Peca&E&6WYdR3bXsKOx9bP zM|1G(B}=)H5#~Cxbd?QH8*a%-ae*!2(#P^o);Qq}A+{EQ#ku6Z^wQ%jOVW2Tbl!i9 zj?GE!;O3|ofl eOiTs}g2mJ@3 Rudw;cafKTZn9_an zqwbDG>j_ZuWI~KBjNWmRK?}5Id89^M${WQzFEjxRw4cJugHS<4?Vqhg&3G%mB&Mzd z>bn>Q1WPc!#9u6vn-0ej>VpzPWeXBYWA0smv@<`RW$*)O_t>Y-%CtOL$O7@Fb-5Sg z01k~$xTsT8mz1o(LR dgt%xki5}9E#!faVRJX7XxpoX z$;$1vKRAG }ppn8 9g30t`-f~ziuDFD(PYq+I)o0V&~^BtcVRJ2k-WS{JwM0 z=JfQ{S}C4`84I57)rn`r%f)-c%7Y~zY#{&|bwW{itsr*(>h2uRO0zq;* D%T9IW zuQ8Ga1JhrKYJ!gV%-0>>=o6M&Rf!K33TD?7{TJfIYp8lSf`n|WZ~ST~XzMH~R>;-$ z1lVwngxJF)|KQ8_p@DL^kwKv0CO61Vi`1>Gv$Ua* sj zA^S1b61ppOsGi&kJYS*<=0!(t%h2*dGD&Q6ReRg_FH4({1o_&__Zs~{! bBv}=;gN+W z$~y?-%Veadd*Y)Q#>~d4a1cG&Rwm&lX;Ac%u5V#n#?M^Kr^8k{OJN_!3?Nj=cQ2hW zk_=44f1j3VbI*Xw?xJ%1#1Bk>^4$7k!SQ( EZh@#lOY5h@BVgo%@<16W zV}+6 GG+jA=s{b3267$4ld9lTK8bzks*fWR~NJlkSsn%|sh0 z+ofD4Gb_c?1&vDl$;g)mXIJr5{%WM^F|za=^&9CmqTGXI(wvonW}&)5cE}ECDd#|t zk}Ur{h$OYniwITx4;=U4Xe*^)9>QnKZ2ox--H^F{z7^cnP~a-P%SIVHp-~JkJukyS zv~e-W^rWk5Fh=3LaDQZ^gD{H6DxvLRxDN9osILxPR8{CHpA$oxIeTnzeXQB%XQD%G zWS~0pW}UrG1g?$w0RP$??@m|SBnFsT)Ut*w!hGw|#{g `KIT&uRG? zjV259Y+U#ZyhZ%IG=<6b2ZcV{uTWijU*@UBKA#(+lzMl`DDiW9FeJJxrkN_K@su^8 z?=S))bZyP;ofqmz!3Myv@RIUW8o=%^rJ!mTl;3sS|By$YQ&{WE8m+6@41c*0N@lz( z{Z+XW2{+CusE#aLfjzNrE7T#O8WEm07Z`YM@3?vDo2A+i2LXkL4hOk&@}IyHM52Pu z0dn3h9zvSI@VROa((V%y@03yH@gJGrLrV-6!})Pom_D87Yvux_mXR-t>ru2z*pS*+ zHKVJYZup|fQ%+G7=nAGpq-L9^f%LQn&sH|+=V@3)r7JUBBci=v&U(TV77ZHI8YGbO z&t9_8M{SA1uD#@9ge%I;4b8H|{Px0q6Fl5HhBQQvDa4?eY=d3PHN&lD#Ioxn9Ftd_ zcf>y1rn8w(p?7}5{9{lvsL(suTk4GO`8&G4tpDrR5m>)0wJ;&771wbZ^j#-mj;6PT zW54B52y@otRFEt*#HIh%EhO=t%tQm~(Gqetejy$v(2<3D$Qo@CqLawPBspk?fub_n zv%1yuf{~a~e`~?rmwL|P2bWal4-O%^NN#$KUB+<|HGysOa1$f(?t0jjZ$5FC7Y5i; z6~6crS~Qvss^oKU)5|N=OFEX?&YD-YYTQC5kG&E&X^&7N<9wBkRz1P5-inCCX+$hH zJRRJqOb$3CQDbfkK1ED7ZPL(wdP9eq&}PUwhmtw7O5Z8hak$J^iHaOF4B)qFD+ahD z5=Ck^cQYZKI%<`@eDEr3g-18^eDSROp9uRM>g(*P*6a9}ocTsW3k}9=4r5 }4^jTcN=|#K$6xwmNp72u&z|y-!L63JwQg~Mmw)xm#SR*+WIIyO zdN3n+tIV7OQ0SOWc8;ApRYgB1Kq!c(8$xs5WHQ{Ly4OLwpmi02S&bpUxm9Mdh;yT! zgR%nSd^>2@AiQ=&tH{ n)y@A}U; z*ii1FmgcDmsg=2%6C(v8y^55T=QPav5C^5&qU{GpQ#Xb*>Ckw#8iyh9sN?qcia_1` zkNGlwspFHD0ytxLI}D)|301f()67G5Af!ebetuME43@hb2vBKB`Zp8#0WuHxYt{1@ zn!Fh~gRQ*Xz UV1B3F=U26R1yJL r^9T_c6oP1%b~Z(L3+jl&{#rRhJ*;7P+c) zzH^m2JvN^%uWJ6W8GsHPlNO1zEv+bL*U%{5?>!$UCtb`44w8mfM!;?dT wI=>r z#MQn(299Gdl(B@{4M4KJG$Y{dH;mf@f8|Y)?{kL+)PQva|LCdeG|Trx1M{Hzi5kc% z(QEKBEBr>rxslYgA6NYIYtxCZ8Bv(Tr&dbzJv}JBKE!eZGZzZI(bc05I*CIx?Qd6g zg%Zt5njrsNINw_B9?B8h#pLpt!`fh9zR^oec2VGHv`9{i`?7cs{)pau^*jO1u*LS5 zqT=oKC2_}$KW0S8U}}{ZTnJ3~c0|=J0n;5U;*w`d9UF6bGgLfQ2Hpca9M!i{TeY>` zG`!>`B99~UnE<9`BqZo;u2U1a#{+9~gxyy3qM#BdE`13jUp%F2#?ihGcjC7vB2b}J zM%4c1dp^a=RECK({D+EED}r~!XvU!l9-KL2DjofH!_veU_64$p3+&RS#fZPo8c2{2 zyTYYfvONfzNyyA0EaQHHn Pa6cXLW7m>ajtC2|NJbwYjk31l1l?_h9zu1?ZY>C z=`d(r(6s%4VS{{m$RIv0`usTg|GmT)?OaMH+t-PHEHFJEVukY5RkfxphT!w?=uTF* z&_2k?g qgi$q%$K+-a&3z86XK9mEfHAS z>jCSKtX7vrcw4~3uI>=nY?vBo(iPbcNXwi{ZVjI$st8KChPFkD8U0smdXZ@{nCUV& zE&U5d4YaG7r!9oSc;3^H> @&NR{q5#XztvrKzi#MTJI=aj@~i+N=KAChK8!&9gc_Jj}l zmOgPVJNL9^iec$H;+Y)r3f2(P4b8gD-ul0OwWn7cQzY|?*uH|S6<9ix?HE7*M)`}< zVaez!1THgxtMxkBMqbZ%NUz{?jFvVzO|GFoT6k{6i>B+ott0P641o g+;N~%Ch)tj1T4zFG!dk>%c7&xhwO`wavm}<@MBvqlCCg`o_DKH1DB!! zF;|xjjvlPJ7aPIO$+p4kww_Spk6)TWH(+DOb3hzExek-_u}fHQBd>J9BABgwjoPLi zK9#LhS}{S&^fFhm=v=Z&vx0zM1lert`2t4XJNQR95&^<(iA| t|fwOAekNFQgcYc$JC7yRhBTVz?yfV+&Q?jI1k>wwu!f1 zA%5}Wvx((J$Onm{hlD!kvDGkzasA9h4#aUU3=kSg539`4w=z)R_#D!#Zni4j`s%aD z @F)K{bk;gZ2i<%a*k zOMifVe)83d9lX-tsE1;62r*Jc4K>k5e_{=ii8+K&4RKoFdIyZ2LKiQIbiWR{N|0Lx z }C!2TRo(F?6hU0o~9s_W2fqkM3C*XDd@4k~$Ar1!>y!-xdip(@221`xtI z(KZ%>X?Qx>Pr2>8e~gsWcU{aypz8^VD3;;w>0lrD#qE4ytI@V6`75n33C!l5*Vl?C z>aV|gYo*RK)M`f}17T@jg@V_o{xFl!Cgp3LEa?1_!7)L+1rqz2r&(EM6hh`A7K;xY ziOdPjf>hy$)VqlBaDya#KxNjnmV~V|3e?|R2PVY?6 J<_VEN zQMtq@x{8H>f3VI3*L`%etR?gH&u}a&@HnEV$8p2JMwZM#Lkvy$gq-dRS1**{**wQ~ zsG=jDcxbNH#OMG4Sq7QVR-PQJx}Y2eIxIHtF2Ox65e+5u$-MNp?O!+d|2am7Jg{ye z$V=iu1dwgW&!i0b`;{&BBQK!-Q9d%XBgv$?xrRx!u@)2onk-cECej&zh`V~mqn7AV z+N2YD$lj1l0e|jVb=ek80~}zROTpXl6zoegSpDH#zhIqmqD@99>Pd&kLG|#(n*n3W zV8;qz-?sWdW!ems^B36!z81;jJY&PaonJ%Be#k28)P?t|g~mg6T&JEl@@3*9X j<+yr{8qUw2064%~o)GR|y!;6wyAT?|zuWRBs)Tz=eD zll@QfxZol9N+{(G7LT`Cu?9BZWO26A5}Iif0g;gE9}s%P12q6z{hS+Gym{CEo+*{> z4)j>3B`tcZEv+17k^h5$0J-;c84 C0GRcV9x*RfxavH7ZNASM{@o#QpF3Og1wE7~^d` zOO;H#Q t_r0A*Pi4K=I6Rv~p(c+R zgh>CqiE9_WM%(KbugI$zh&kVAZjSq8P3eu^X#q_HbDO-G>XC7A`2(Ck+@zx?)WR@Q zdYl~8uBa)2L^+pbY|T-p=4HlBN!!QP`;+U_);KqvQXPS1(#lT{VxWqT+X$; E?et&20yGh4ukZHP3jlg$@)}bKh(jGZ8{qr7)um;>Wvd8r^ +ec{nUKsn~+@QwoSSbjPv2^o{Gh9h)cc6}>|_hT4xD`Hth zP(44qNb&&3VlPv7rsp_ck|E+f0TkRzx-#o7 _~T7@4sWhfOdfk2}5rr 3J(#FwIHq4|KCj`yB9CpD?$NtJ?Sg-7vOrnvCl1{EDB^k>!TcSIgkwa+Ca z6fu4YKOhq76*?44TP-TX5dnGoWrF=$Zww6Z5Ada%ydb&)^oiwqi $8EyR1Jq&5fof>ow;CUvbW6NvvLpl+|x0Vk#6mSt{;pKC7 zj9UBmt%^5Y3zMvj<;|4KU4bwhY^||02|w!N=q1!sVk_u#%aO#j;36*?8Gzl{F6_ur zKzIE=8-Nu*4c =8~?6NrB^~OS7qBTu k#4MjR2km}hJ+-xOgUKkq#stgJY_)-Za@n6w({m~!;IbHUbHPdAm_ z7`YJQmDOabrG|qUcwKN_BwW2ZY5{yl+L|tB%Tt|vBO)W#0LHesZp!pi0M$8-h+8&X zfpa_DckjXFRbG;Uc(!+@hUa)+lyI3wniTVYlf&t=t&}$s?I1hL|9#_05%-Y#iDMhN zTJ|~Ka@Sa$ZP`WA*8}ON9B43F>$85z^!<%WSXIFL;wk6B&h~9CBX~pITz{NJ+&y|# zNdYX_dHrrp`joT?Es$JO0;7H&oM7oSSm(GpZS!6zk={wB6amun#6k}35cXP!tZBCh zYskJk#sm(Mx(X4QR2*KaUm@hcDkSmGu#;0bUBWB7yrhZ6h`hf&q!#gynwbE+3FP+W zZ722r#rM2DN@NeMCLeqf2)gu{3v?yN?M@G5C3YFASj4RO&B|mtu{PbrkJ$|i2*rCG zGFp3vv2vr;8PZWunK052=ZyN&8H$5&9 z_@0lGRns2M)TMMdvMU#oF8C$}%)9V-FW@$eO0!n +6_M`MX`ziz`9y5Ty*wP1OqYO &KXr%W!1@_O8WLVr0$qI8gKf}poniY &UK`RF-M*VRgx}kR zh|(}=>)?ry=Tcq5J`-t7h%BiZH)3nNCAp@tLLDZS{v5#*7e~(Y#fb~-80JGpW-%TK zdwh>i|0<}%p-E9y%Ocbm!ivePysn@J!(c1KA~HfVRFu*=^44QyeklW9d3!nbjYlCd z#69Hy09I`t@gJE6-Uhv=#fHI?)nyK=hVu{ SV+OfSNI3Y}j{Pjx?t( z@T8WEa@~m0@+VY`oh0F2nPO{+be5FtNTIrj2v9YdQiYL&6V^lEvVi4@v0nzmwPT zLY)D(gt~6qmT`|!tB(vQsf>!?qkCgd-)fX-x|&NRA%8j4lgonI+7v(C3)9X*cLaA^ z*tKCiZt+%aN2d5bzd%Lxq@haU|4o5S9mD@-b0>m#u93FYrF39h1G6|DXiZaqDtr7n z3%fE_qiu^0iUP%f(%`RPh_9xrfdNa$f0K+-Fnr;M!VM+=WV|GxxE&Y)A-nn;nUcrG zbCcvtH;15{y+0L=3mONaXF=PARLZN0+v4V_?wx$33Q%wxL5V4KG+ir3Ts3f;_vOWt z00MZnu2RC>!7KA)I&!{-&nKvG4ES^+Qw5ZD(W2z+-;e)Bxml0vjdZvw_be+?`BL}n zB4RmX));z)cB`#(`a7}Ij3hB;=xpzGCRRc%D0_=#82jDyDX=ub!yG{tCc-?08y>DS z=@xAR$^aPvE1=R^^00!%`rEX4VI|Z6u4O&VLw2=pij{sA Y-*?4DYGnD?+?=5JHcBTqpx-=-PW^gv`#JtuX_B6;7<|SiG$D`+GS{tB`dLE{E z-Es@jl7TNjLSityAaa|qn0IvVhM0h~3X?SbX|U|<86gSS^z-(9p2)zD+7YdaCoZAg zS_oDu!mUAzlMW?!Jj7&GBnv7>MK_t;8O2(>5b~H!&Fd3E3^$@UVm-UtO9W0}(k^_+ zFxXAVf-QaSGcOKQI6BHmbc+%)gD*OA4=BU(?Rtthj6%IAOW&j}KHEZeMMQX6Q}~dx zWSJ1@(MaX}&C5@d#Ne+3$V{slmtTdB_6n1W=(YwgzjT3Qs&AWqEWT8y+ZNuks!p-m z4?56?ExL4KM_EyXEiG+1N?6ah&|#~eph|&Um6>BR*R^MU#*lU7EONHELk`s|shva% zihmCV7&!-4h1EYDJ6+)|!w3)G)HP BIMhfWsJQ_FynWC?zm8Oz&N0%( zeJWIJ{Vp~Z76Z0tWghH!`N%b+yzH*rn|U_oZ j z zeZ^sk&fc2{@SIbJzj0GaIW_K!l|m&dVuJ(`nM+52P9UXEi!oQjUuIendbjNs;#fAw zxw5d#RXcm+-|!w5KY_wBUAvXE^}@PU;o+5(JSJE>d4eL)of$^? d&3Sf-`67e?3L8YKW(fPD*T^L$pE* zuK~Uj27YA%2?|=F9$i3|FbC#u3Q81P9jd#K%}sZBX@|Lm8KB3>8Mr^6Fn((=llIf> zMan4U3==+5_3lZQ+dfj_uTbt=8v7#TKKfl|6ulvQDI#i2YsW2lkc38XDZBF$2!IZY zRpWM50N0M!-96f3am<6*^`4rggYvYs*6(#dJbb(4y7 @O7W}#ax&V} z%;z0F^o8=@A!#=@gjaUUD{>@-Qxr6osu^s9grJ1IzIK;;lXx0b2T~y%0E*DCFM<7H z=o4f1;K)9zugNri V@P{YO~