From d22fa8a286e8abe183de759f7afa0075e0be0ffe Mon Sep 17 00:00:00 2001 From: lusixing <32328454+lusixing@users.noreply.github.com> Date: Sat, 24 Jan 2026 11:02:08 -0800 Subject: [PATCH] backend_v0.1 --- .gitignore | 3 + .vscode/launch.json | 20 ++ app/__pycache__/auth.cpython-311.pyc | Bin 0 -> 6243 bytes app/__pycache__/database.cpython-311.pyc | Bin 0 -> 3483 bytes app/__pycache__/main.cpython-311.pyc | Bin 0 -> 10803 bytes app/__pycache__/models.cpython-311.pyc | Bin 0 -> 2172 bytes app/__pycache__/schemas.cpython-311.pyc | Bin 0 -> 4001 bytes app/auth.py | 108 +++++++++ app/database.py | 50 ++++ app/main.py | 214 ++++++++++++++++++ app/models.py | 37 +++ app/note | 1 + app/schemas.py | 62 +++++ docker-compose.yml | 28 +++ requirements.txt | 13 ++ reset_db.py | 37 +++ .../sp_gateway_rsa.cpython-310.pyc | Bin 0 -> 2804 bytes .../sp_trust_sharding.cpython-310.pyc | Bin 0 -> 3352 bytes .../sp_trust_sharding.cpython-313.pyc | Bin 0 -> 4885 bytes .../__pycache__/sp_vault_aes.cpython-310.pyc | Bin 0 -> 2910 bytes .../__pycache__/sp_vault_aes.cpython-313.pyc | Bin 0 -> 4212 bytes test/core/sp_gateway_rsa.py | 85 +++++++ test/core/sp_trust_sharding.py | 113 +++++++++ test/core/sp_vault_aes.py | 77 +++++++ test/test_scenario.py | 201 ++++++++++++++++ verify_integrity.py | 39 ++++ 26 files changed, 1088 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 app/__pycache__/auth.cpython-311.pyc create mode 100644 app/__pycache__/database.cpython-311.pyc create mode 100644 app/__pycache__/main.cpython-311.pyc create mode 100644 app/__pycache__/models.cpython-311.pyc create mode 100644 app/__pycache__/schemas.cpython-311.pyc create mode 100644 app/auth.py create mode 100644 app/database.py create mode 100644 app/main.py create mode 100644 app/models.py create mode 100644 app/note create mode 100644 app/schemas.py create mode 100644 docker-compose.yml create mode 100644 requirements.txt create mode 100644 reset_db.py create mode 100644 test/core/__pycache__/sp_gateway_rsa.cpython-310.pyc create mode 100644 test/core/__pycache__/sp_trust_sharding.cpython-310.pyc create mode 100644 test/core/__pycache__/sp_trust_sharding.cpython-313.pyc create mode 100644 test/core/__pycache__/sp_vault_aes.cpython-310.pyc create mode 100644 test/core/__pycache__/sp_vault_aes.cpython-313.pyc create mode 100644 test/core/sp_gateway_rsa.py create mode 100644 test/core/sp_trust_sharding.py create mode 100644 test/core/sp_vault_aes.py create mode 100644 test/test_scenario.py create mode 100644 verify_integrity.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6fe2dcf --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +gitignore +# Python virtual environments +venv/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..fe655ac --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Docker Debug", + "type": "python", + "request": "attach", + "connect": { + "host": "localhost", + "port": 5678 + }, + "pathMappings": [ + { + "localRoot": "${workspaceFolder}", + "remoteRoot": "/code" + } + ] + } + ] +} \ No newline at end of file diff --git a/app/__pycache__/auth.cpython-311.pyc b/app/__pycache__/auth.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf66f441eeb294fde1a75c7cfa4703ac6ce94295 GIT binary patch literal 6243 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 literal 0 HcmV?d00001 diff --git a/app/__pycache__/database.cpython-311.pyc b/app/__pycache__/database.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f745474a00d8113655f42ea92520f0c25f068c8e GIT binary patch literal 3483 zcmb^!ZD<=uc6L|VmE@KDQ74Y^g?krQlFfDoSxF$&dW(pBCC5{k<{^5d#Ht_ow|Q6Bk0EaBo&#OPjcM z3O76RG&Aq*d-Fc#z13IIXaqrf^><5sOF-zqbm6t&&gA|&OxBQs6h=cS`parej!m&S zF2yl)&1r#LFcr-4DV{+r!srt4YBVLF1lL)FZo_D_R0t?o98&l@!b6xaV1hV|IVE%_ zyw1T}Fm~RG0FI|8x$SMCRGSj{7fOjr8^EZ-%%X(&3&mbYu;hu1fn^J)W%H_@Nn<^$ z>X?X=bTx}jQ_br@B$|zBPF}(W=}>S+lMUHYFXQyQYy$n%XFV)4Y;5l(WkG`026dMw1J&X~_jOt;}CcFeHF8i}~pU zM*{Pzt`Hu?m}Z)E;N^m9;B;P3XY#q6YGrnHg9#6V1GC+KA13D!13s{rCV}4;V`zm@ z&^z4U(Hi%Dpo~iBO$2ks^$+;SZ|N1bNlPha(FrtyT6fLB6?U1MLsx*q9LR1^C727K zO=qhWXYv_YgH2(_lqNl*^xL6iCa>V6TqqhYME;pT!o~#OMH`7!0!p;AeQQ zp`Y5NYWslGK2S$Y@bLbilC8xKtwtM9o%7Le(bu;R=CQwo$G|>#@L+!`Qv~JK_`h;@ zBjdui%(&olZy1m(Fzzc5r$Zs_eg7{ztrxb6@JN0Sbh%vRjX~v!BVh7gZh!?_gJMx_a>>I zrp%XkbEU*px|$TtnVCZTmP1(@Pb^_EXhSml(W>RSBmts zCuh8YJtcW^b~fNZi9`?KcWPR29z$&-$e?Z}A)~0LX^|s=muLkG<*+5w3``AwBQDB>j4vA|2`;FH zX_3w>)-hB~SA8&wYLFjRotF z7+zLQOVzUtBO1MwtdE`{;u+L*Nxou+-Fm_UUQC)Rb!`}*l9boN3Vx{{I{sM?Sp0R zf1>@dZdLM0b?JYNp^hiL`q~prF?Dn>gfbiIwf(&oI%WHNokvId*WUa1y_(pyI{ne} zpS)NV`yH|Wo;bK64ptVvTyloSzsYV4ovVt|jyS!=u}31?2tXaNU4IN#dEneDeSZAY zNt1c%s%7UmN~-xY{+~bPd?z9{ne=)9!k!D$Y6LoGs4PlMfqr%Qg4* zV;KE$9P?*s+@N^)NRKixPr^<4Vq|iV`+hJonH0VsWdQyVnPR8=xgYx@(+T0nQw+dF zjIeo=9BxS-c-b0uKZ@170apzf^y`DfS}5{H&Y=G(3|d>L#iII-|waptt#JbRQsc@A+?`Gmy!P8lEuDz#h%+=63+#laOIHQTUqs z)XJ8hL76NkBxmZW{1yK!)KlF*=u~Q zTl#3GvT*lp`{JD4ovL=HobHs3IyZ$LTj;3@aYu+>3)BNF1K+f}Akxp!E4Vi_UIZ2X E2Ynwm-T(jq literal 0 HcmV?d00001 diff --git a/app/__pycache__/main.cpython-311.pyc b/app/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eea18b74a500d703a0a842f31a89762c982f8fd5 GIT binary patch literal 10803 zcmeHNYi!(Bb|!}$&QtTyNY-m4kK~s|ilu06%U(C}TDB!Wt9boNJblcLhLk+=JXDhM zLm4%KV>FJTMU1T%F}e<_AKBDFyx1UrHb1sSx@dr)*dL@ozykuo3Mkw*{WY$40VBV9 z&gBe;ltz9OL9su2Mc%o*_uO+2FYh_uIrr)x8XCM5q+<=UnU~rr>fi98n%tGb;{;1l z?^8U*(^)D3PBhb$(9C-UdeXuBuCpJ3NS{>Y zqY@h<2p`?)yKYp&o7{C0c}*rPk_`^_OoD8m8N-HTMG_IiMfhMgndQAvg{5U?e-& zNtzEr?~ZCK9}>7pmJeTdu97Oc?&ytnj?Ww~0^@bA8n0>1cwOs`*9`qPR{Pg|+rV_J z+WtbaIrt60ySv(#ZxQ^{J?0b649B;wk#y6#lD5?u{eLf8JIJ=#l%}3+9cyIUvaW2= z?^L!WM5=5Oz&CH}t%LAjKHDMroJO+j1 z6b+VBwzN;1W&2#b4R-WR<{b4t^Ebq%zD_}zMoS1YM32WEVgUH*u1r27C;5puEjGfl zgxq4^h6mH|euE&&U@n!(56oQE-Pz2fAkC!m;szi=BQNcN>_6def%*eS`68OPUG+va zZ&YFHTqV}CFk1V241GZk!fehnC<;>{|O|1+N>0*X|tbw{meUT^T+@G_{G6 zn`-o%DRG!a34FD>TFE{uc3!fHfD|R0Xb1CzON=Dmai6hc<2Cv+b%i-kU8dvgIB1j` zKXX2n6ZGJPlypJhlQStvx?B)>-8Um}$;3<}l$YAuouPaeyEr ziZ~NGdln=UqgZ58Jg;-Y~pL&;O4KRi2n`b?Y?ak@mDC*50p*UP-vg|&#Tx;rmiPU3t>$Ryt;lpsV7T&aU=)*r%!{5`w-&;8LbyttpwNvdH*1CpOwqxP=t@ek(=EXNl z;pW?&rPiKO)5cPyv+Qw(xJMLZzsGFhNZC(0UGI!t8@sVbWt%j%NnxAH4xiIo3WVOv z+|0ataq*(!Zd-d{`}ED}JFRM)VpgOj`n409@84*|ZX{(oG$Qm2TGCb$ZD{Q{z^@r_HLE z7Um+cUUshbC#x>bJBp4a(<0SIq*hsw`WDHqWmN%=q}-k zke&r}Leorag18k~IWJ!jpf|A$;0UC$DN)j$lNnKx4O0`mB#4>GtERCLHzNadz=@I2 zBWY1Udt)6t?i2@+fS3mk?8|IcHVnp2sFrX}EcU>eElg$d;;T@Ly`+DI%rFKkk>>aJ z{$%e5lRrQA;h(8pLt59+gRb4nUAzC0`?XKob3!?JUOPFlyk|n~N^4zdHNtBVUTIi; zEsT|X;rI64+^70FG+)O9U-z=FTlHym3+Z6MLW;YKb5tX{(DEYm-cc}5AGRRd?Wz*)t8_F<$&i}ZiG<@2UrcPc}NmBOnZMFDqo_P+`XMmT9@4(B%?=_wP3%Y4)P*oEAEwXlv1{8KInfhg=v}>qU z^GP3553PMxb6?eq1r2f_0z2i5oE7vI;*i4Z7I4WYg?t+QSb^8W78p)S7gB)C9`t$( zVluKwt3D~^D~;z7uCM}`--;S&Z9VjMD1iG5i9 z0cJ!m!lqOJ@!(eHW!;0AX)KggI^wTt85!s}Fxd;YbPuwM_ZpzuIjVGQ-@$(HdlaUuBy$ zwi!B?y#Dv%H{p( zd9Y~#u0Wy%TR}Gr-x$^tRIW|m5uRDr-8$HYnn{veGLNrF)#mU3-GGm5a||GYE*D#RFl)jkvjF~-aD+`8|Gp(RN?CnKI! zQjxQ-lb=x^)QKl}YM%RR?y?zIfV8zW!cLp}cSR?(m=O#35NHA2y7jX}r)+~op{MJ%wvdT$J4 zB}4PUP@>t?!xajMnX1+>1i=G1ABLI6SSj8v4g;;lg*l8}T?MicB8jL$I%k-3aW_&P z!R#bv80U-M#|$m2?!q?)lW)X0jg=T@M{?}7QwNS7Iy5@2JEvw-S)l?572yaVhzF1h z20FT@whPlmSnPr@>FD^G(Np6iCz7W}Pn{b*l^i{F>a|ngvdLl%AmT3Z1adLaKzs!s z2}1l3In#oKcuc&cSj>o@(`XzjE0|Lqcs%ZoIOJBPKKfbhBRNYa{ z9sQjrbhG8B(GMo?X2E?jf-2RsL-XwTohNX!83`BPTI$z&hL?lGs%N+6+5O1HHUTC; zR;Jj%`q{!**-JG<-W$I;uKJ^zKl;Gmv+VCt{hKxa=7rG}UkKf&!*s=c8dU;gYG6zY zj4AH1GDo=^uFv1`-wiIiwkfV{)zE0D6ggNoH1bpa@Zu|)uM>oFdM)H<>yvwcC>qnE z+e`ki=8r?Zcmm{lDW9D(u$P>K|eZ_(z72_L!38 z>)_W5dpqVnm3=0j!yhqw*6wLYo!{APb46~AKf*$;rFQ%N7e?^Xs;^#sm7%;?U^F|+mpKCN~ zs?P|{@y2?%DxO9`2&S@#mQP1i@eopP#f^$cmAAu9s*X+67^gS%AEu;v;5zd*nh zm;d@(cV4Ix;r#?#{+lwvb+0h zf8*kD%^zDjrfuG{?BDZR%4^tD)^$X#2v^6<-qq_*fOd4=I6BH883LMiuwyiYs)zc&m8(t!39%#kIB6+;+pc z_{`1Kl~9|~v0VucDDHukaQnjO^<&S9FjxG|O3Q(J$F!IB!!KUGg9>}l2q&8cQ+zIy z9|Q*tcCLi&#A(xAl}8(1qW%-S{4ZE9m57p_hDswroB=pmP4(k7p1$v}M+etvuqO5Q zN3HRszseUP5nR?Jt+LampI>dqU%772TCDg-6*pSuk7pplTQgTYL@i14jbP@?DAM}Q zPqo9G^^Jc<@vaZxyvi~k?6k?Bci&~eu6oAjhF;5Oug1tqffG779w~?thAbGC;IbiG ztr+)Ug#bsNM#x0$@mv6pPHMLG%$$={%yq@!+#@z22qqie$|25(KZ3U+)=+#MO0Ws* z&PYijMeTe=JcmT9!Wv>mNdSYk)DpWOAH$wH#YrsN?WyjQQ{t3hY$~qaW^JszEZK2P z8qp0d|6jr_bz5zyzg6?aR(siF^*mqLF|vjnHUstZtsi1k@%c7w@3G~cW2*bO<~|Nq z3PTIX%9H~`i*+&xEjTLD_Wr<62GqtLt+D4pWAAcfuiDtBHTEqWU-4~N-!h@q>|8hw z`&Z90EkN0Hg1)m`>*!Y^$Bhe)leDJZQaGxGd&({r{dOaBa)!y_24@PAJd}oMGZU`- zx8Z(_U(T>CJUtKp12j(;;WTm%YUdrO6GexK_s;Qe@L1#+2x!9efTbRestgaJa^!%zNkVGl;68}Q8rjUmK;{1j#NcWM!j-gfH0iC7 z0JVtT8##0*$Nf5s-$QqyR=zeX>l`WoKcRcE;vhMkAP0@Q2P;mPXAQcWctY4Egk7u~ z^16?-ReBS3#64HtYgFUbC5gZ*bWjVCnus2W?muMMl*45E02PdUCb58}7R>Mv^jr5U z@Sg#*Sz({}4m^VPQPOAd3Cavj({p5EQ|#9Y4X3LN?Y-lIgG?GvGb`C6hH^A(l~%A9uE4_7tt$!_Q@NPN#TFP) zLWlF6)@!Xd`j@6YEG)FD^sq(`m-Zk2!u6H=w?TFP8`}Oi%G3sA2-%$$t*xJwK8@CD zNeRc=4SmZFB;Hx&+pE$MjgFK?j(pMbRqJmzsw3yMk@IEBMIWGz98JJWM)A|iueBNZ zHUWpSgLG-CGVWV?UE8v&DlxoBckhzx9;@}fs>b$dv3;eMu3MMyT)F#}+A^rM43=GC z8g?2WEBP8VUssud;!0!d?f$Y8TR6(oT%#d`wKqb7-s-@ED&4Np?awjdAPoi#vQoGe z&eh8d6e}ZQi!oxAh7jV29mv~()jVApQh|Srp`pIxImX;ZAA)(sycBBDLNF?pE5njT zW84~@L5)L4aR>)#d80OVOr^Uuy8AiC?!@7GvBh4I#uqdyuyS etM^yOyir*+Wd}>g0Rrea5*mx3T0^RdPX7-ohvwM; literal 0 HcmV?d00001 diff --git a/app/__pycache__/models.cpython-311.pyc b/app/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2328b8d226484c9b37d3a017ba4bf2079b10bf56 GIT binary patch literal 2172 zcmb7FO>ERg6t;K0_IlUeP12BsKm%&4(2YdWLs+T`Rf0lNib|`pZG}_Gb#^AZ2LCK$ zFOakcA9~p1A|bIz9QH(m$bn;zRZqRumV8+G6sad}spz4im%bSXNJ5ax*#0~--@Ng> zH{Tootf~bLO7CCaoXm0DpS1D3K%14PYw)_uArA2tXY(3wXS9r+)v~sr33g7)*`g-e zk|yzVOvcLFvL-`4iv+7+7queKWw=ES<$mIjh>H)h9QO#Gv}g+BB;fL|a4M27bJfBV zIo=A)kLV{TYi$O8_O0>q_eJTVZ*8N zi9)bt_@?W4EwlZEr!(@2aMti}BV9>~FP#1d+IKn3X*}Yz4C1wHhQk8Rp$y7wWcN5B zGEQQgu!qa1+ynUJBl=lZGF6vmcuIT#pj$v&xs;>KV_tPe!QX>LDk}(mX`LhQ{cI0Z_PDagy)QQdk)r&EpN8HN=m>mkf7rI3J|=whU15W&o||A zccLc^>W{}mxfaQ_Sgr+Uhvfs^8@(0QTdqarT3oILpAILE^j7-c+;;9b!55pOmEJ^O zyghYiD)?+TcCa_ypSV4HXEwN)8aD@>uzVsapNPvRz*HIQF7!?f&WFlOq|C(1OmKcv zJ=FcKe?0u)O!$=+s!Nf&6st?Y!m#pg@5jO7FLR;xeONJ~iV;_gP#8~dQhn~yu9Om? zmzJjj$p6h#YD3r~`ZS^BJqeYjP7b79M7iDE9(y=(Xd?fQk!gBKyJyqV)Dp4MKmz9d#T4q)!gh#t{l{Q<$Rg zLfhmhZ8CA^eA5SIQdK3Dh6_ifs7BEQT_>3OD&?$EsMwG4uo z#>oVof0Dxc6xdGB&>HiE!a;bv-vD;X_{eKz%v4t!jEBl}q)f-kbZ~w+^J9Y8{kZtaMNJ#r5X;m2mM&Sk$7T z78kYP9KBNas8g5VC@gr_EyHTGu)Vq?;561Oj4aY5Pn+zFD7_p$>k>OXBJ#+9GYNh+ zM4GV+hn9RdC`n&M{0NBd%JU`^V4Bz}7a>v8n_a-+P?!LD~m0#w! hxc#v^{}*Ha-pRkC@=MHOKkS~P7E3&}*pFxN{SQTJEGGZ} literal 0 HcmV?d00001 diff --git a/app/__pycache__/schemas.cpython-311.pyc b/app/__pycache__/schemas.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ecd12b0e4a782671cfc9fd044d85d1a89440ea33 GIT binary patch literal 4001 zcmbtWOK;mo5MGKDMag>DmJ=s-(#Ea(PzuColRgrpMcbrx;@C=oCKp`@M%>s$B}!dV zEqtSa9()vv0_iVEYa~C$26_;9D$rhfvtgeC6zF_Q$+Af~MiGkq#NBVWJ2N{oyT26* zIYyWHukZY=G4>Z4gDc&C+5cH#>Cu;Wr@or#>`4?jJSM)%So<4Trt6oNv=fP zc!JAIZi2W;o{#BSkX)I#sRUP)+!^A|CdMpDZko7ryc8RATyp1$n@M~(A-P%NE+n`~ z`{K8(^4dReZzUCGzqCBQ(z1CY%-w3akDb-qPTeO$TXsA@H14;3r{!9WdVf9S-(Yq2 zXQ=svaaL1oR!co(T;-ar+@xMRMYE`(afdqsQ-qppHF%PP0Mv#z6A|D)7)bFnEl1nLY?$ou-fefufE2cmUpGSE)1e1AoRTR z1i_JCFINce26s2;3GRxCf$TA>C{pFMU!=dGRklKwq{195xh1&e^J+yEVOY8mXL3u4}b~0HG*Tydl;mHJ(@uP z5D3ZoH&zUmf~5_5f~6>6AUj&6|B-8BQn`MzO|*sc1Jy??uIa5=!VagYX87FoO~-Yq z0CBf2*4sWmAqlU*1^`yryG(Fwm8RkNzV9zy+SYLINTip@D11EHsBeflAq%i+$t>W= z2NjVm-(>t#m8FXMT#vt0!M)(#20g*ONMC&P%KAnLN=iGcuK1Wn46|!=E|5pC(F%V` zLIQA|o}|KuULHgi;ww@ZAB|S{)FPL^&Fc+I@I`C`(N-~XoBo`p5CF`Tta1b8hPyLl ztCccs|LeBp`c7S_)DsldWqFc`CnHg= ztn`-DDz^A9jGNH`_cL*e#={xSvr>%hs*0lY*yZg}caP0&kGgktMJevGBy6tlq4!`& zkz^K`O@yDSNtYkTND7*hF^GP%o=&^^D0-h&}!B!%RM-E2ZKgV~d? z>FlBRU`S0VN(mOl0b^1xEb3EwN}0oSap+bs`yBM_Q$`xICjtJX-h&}kOewP%GY*mM vvlz2ak?rG str: + return pwd_context.hash(password) + + +def verify_password(plain_password, hashed_password): + return pwd_context.verify(plain_password, hashed_password) + +def create_access_token(data: dict): + to_encode = data.copy() + expire = datetime.utcnow() + timedelta(minutes=30) + to_encode.update({"exp": expire}) + return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + +from Crypto.PublicKey import RSA + +def generate_key_pair(): + key = RSA.generate(2048) + private_key = key.export_key().decode('utf-8') + public_key = key.publickey().export_key().decode('utf-8') + return private_key, public_key + +from Crypto.Cipher import PKCS1_OAEP, AES +from Crypto.Hash import SHA256 +from Crypto.Random import get_random_bytes +import base64 + +def encrypt_data(data: str, public_key_pem: str) -> str: + # 1. 准备 RSA 公钥 + recipient_key = RSA.import_key(public_key_pem) + cipher_rsa = PKCS1_OAEP.new(recipient_key) + + # 2. 生成随机 AES 会话密钥 (32 bytes) + session_key = get_random_bytes(32) + + # 3. 用 RSA 加密 AES 密钥 + enc_session_key = cipher_rsa.encrypt(session_key) + + # 4. 用 AES-GCM 加密实际数据 + cipher_aes = AES.new(session_key, AES.MODE_GCM) + ciphertext, tag = cipher_aes.encrypt_and_digest(data.encode('utf-8')) + + # 5. 组合结果: [RSA加密的密钥(256B)] + [Nonce(16B)] + [Tag(16B)] + [密文] + combined = enc_session_key + cipher_aes.nonce + tag + ciphertext + return base64.b64encode(combined).decode('utf-8') + +def decrypt_data(encrypted_data_b64: str, private_key_pem: str) -> str: + # 1. 解码合并的二进制数据 + encrypted_data = base64.b64decode(encrypted_data_b64) + + # 2. 分解组件 (RSA 2048位产生的密文固定为 256 字节) + rsa_key_len = 256 + enc_session_key = encrypted_data[:rsa_key_len] + nonce = encrypted_data[rsa_key_len : rsa_key_len + 16] + tag = encrypted_data[rsa_key_len + 16 : rsa_key_len + 32] + ciphertext = encrypted_data[rsa_key_len + 32 :] + + # 3. 用 RSA 私钥解密 AES 会话密钥 + private_key = RSA.import_key(private_key_pem) + cipher_rsa = PKCS1_OAEP.new(private_key) + session_key = cipher_rsa.decrypt(enc_session_key) + + # 4. 用 AES 会话密钥解密数据 + cipher_aes = AES.new(session_key, AES.MODE_GCM, nonce=nonce) + dec_data = cipher_aes.decrypt_and_verify(ciphertext, tag) + + return dec_data.decode('utf-8') + +from fastapi import Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer +from sqlalchemy.future import select +from sqlalchemy.ext.asyncio import AsyncSession +from jose import JWTError +from . import database, models + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + +async def get_current_user(token: str = Depends(oauth2_scheme), db: AsyncSession = Depends(database.get_db)): + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + username: str = payload.get("sub") + if username is None: + raise credentials_exception + except JWTError: + raise credentials_exception + + result = await db.execute(select(models.User).where(models.User.username == username)) + user = result.scalars().first() + if user is None: + raise credentials_exception + return user \ No newline at end of file diff --git a/app/database.py b/app/database.py new file mode 100644 index 0000000..ce12be0 --- /dev/null +++ b/app/database.py @@ -0,0 +1,50 @@ +# database.py +import os +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession +from sqlalchemy.orm import sessionmaker, declarative_base + +# 优先从环境变量读取(Docker 部署推荐) +SQLALCHEMY_DATABASE_URL = os.getenv("DATABASE_URL", "postgresql+asyncpg://user:password@db:5432/fastapi_db") + +engine = create_async_engine(SQLALCHEMY_DATABASE_URL, echo=True) +AsyncSessionLocal = sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False) + +Base = declarative_base() + +async def get_db(): + async with AsyncSessionLocal() as session: + yield session + # 注意:通常不在 get_db 里统一 commit,建议在 endpoint 里手动 commit + +async def init_db(): + async with engine.begin() as conn: + # 导入模型以确保 metadata 注册了表 + from . import models + # 自动创建表 + await conn.run_sync(Base.metadata.create_all) + + # 创建默认管理员用户 + async with AsyncSessionLocal() as session: + from . import auth + from sqlalchemy.future import select + + # 检查是否已存在 admin 用户 + result = await session.execute( + select(models.User).where(models.User.username == "admin") + ) + existing_admin = result.scalars().first() + + if not existing_admin: + # 创建管理员用户 + private_key, public_key = auth.generate_key_pair() + admin_user = models.User( + username="admin", + hashed_password=auth.hash_password("admin123"), + private_key=private_key, + public_key=public_key, + is_admin=True, + guale=False + ) + session.add(admin_user) + await session.commit() + print("✅ Default admin user created (username: admin, password: admin123)") \ No newline at end of file diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..42de947 --- /dev/null +++ b/app/main.py @@ -0,0 +1,214 @@ +from fastapi import FastAPI, Depends, HTTPException, status +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.future import select +from sqlalchemy.orm import selectinload +from . import models, schemas, auth, database +from passlib.context import CryptContext +from sqlalchemy.exc import IntegrityError +from contextlib import asynccontextmanager + +@asynccontextmanager +async def lifespan(app: FastAPI): + # 启动时执行:创建表 + await database.init_db() + yield + # 关闭时执行(如果需要) + +app = FastAPI(lifespan=lifespan) + + +@app.post("/register", response_model=schemas.UserOut) +async def register(user: schemas.UserCreate, db: AsyncSession = Depends(database.get_db)): + hashed_pwd = auth.hash_password(user.password) + private_key, public_key = auth.generate_key_pair() + + new_user = models.User( + username=user.username, + hashed_password=hashed_pwd, + private_key=private_key, + public_key=public_key + ) + db.add(new_user) + try: + await db.commit() + await db.refresh(new_user) + return new_user + + except IntegrityError: + # 发生唯一约束冲突时回滚并报错 + await db.rollback() + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="用户名已存在" + ) + + + +@app.post("/token") +async def login(form_data: schemas.UserLogin, db: AsyncSession = Depends(database.get_db)): + result = await db.execute(select(models.User).where(models.User.username == form_data.username)) + user = result.scalars().first() + if not user or not auth.verify_password(form_data.password, user.hashed_password): + raise HTTPException(status_code=400, detail="Incorrect username or password") + + access_token = auth.create_access_token(data={"sub": user.username}) + return {"access_token": access_token, "token_type": "bearer"} + + +@app.post("/assets/", response_model=schemas.AssetOut) +async def create_asset( + asset: schemas.AssetCreate, + current_user: models.User = Depends(auth.get_current_user), + db: AsyncSession = Depends(database.get_db) +): + # Encrypt the inner content using user's public key + encrypted_content = auth.encrypt_data(asset.content_inner_encrypted, current_user.public_key) + + new_asset = models.Asset( + title=asset.title, + content_outer_encrypted=encrypted_content, + private_key_shard=asset.private_key_shard, + author_id=current_user.id + ) + db.add(new_asset) + await db.commit() + await db.refresh(new_asset) + return new_asset + + +@app.post("/assets/claim") +async def claim_asset( + asset_claim: schemas.AssetClaim, + current_user: models.User = Depends(auth.get_current_user), + db: AsyncSession = Depends(database.get_db) +): + # Fetch asset with author loaded + result = await db.execute( + select(models.Asset) + .options(selectinload(models.Asset.author)) + .where(models.Asset.id == asset_claim.asset_id) + ) + asset = result.scalars().first() + + if not asset: + raise HTTPException(status_code=404, detail="Asset not found") + + # 1. 验证用户是否是继承人 + if asset.heir_id != current_user.id: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You are not the designated heir for this asset" + ) + + # 2. 验证所有人是否已经挂了 (guale) + if not asset.author.guale: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="The owner of this asset is still alive. You cannot claim it yet." + ) + + # 3. 验证通过后用asset所有人的private_key解密内容 + try: + decrypted_content = auth.decrypt_data( + asset.content_outer_encrypted, + asset.author.private_key + ) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Failed to decrypt asset: {str(e)}" + ) + + return { + "asset_id": asset.id, + "title": asset.title, + "decrypted_content": decrypted_content, + "server_shard_key": asset.private_key_shard + } + + +@app.post("/assets/assign") +async def assign_asset( + assignment: schemas.AssetAssign, + current_user: models.User = Depends(auth.get_current_user), + db: AsyncSession = Depends(database.get_db) +): + # Fetch Asset + result = await db.execute( + select(models.Asset) + .options(selectinload(models.Asset.heir)) + .where(models.Asset.id == assignment.asset_id) + ) + asset = result.scalars().first() + + if not asset: + raise HTTPException(status_code=404, detail="Asset not found") + + if asset.author_id != current_user.id: + raise HTTPException(status_code=403, detail="Not authorized to assign this asset") + + + heir_result = await db.execute( + select(models.User).where( + models.User.username == assignment.heir_name + ) + ) + heir_user = heir_result.scalars().first() + + if not heir_user: + raise HTTPException(status_code=404, detail="Heir not found") + + if heir_user.id == current_user.id: + asset.heir = None + await db.commit() + #raise HTTPException(status_code=403, detail="You cannot assign an asset to yourself") + return {"message": "Asset unassigned"} + + asset.heir = heir_user + await db.commit() + + return {"message": f"Asset assigned to {assignment.heir_name}"} + + + + +@app.post("/admin/declare-guale") +async def declare_user_guale( + declare: schemas.DeclareGuale, + current_user: models.User = Depends(auth.get_current_user), + db: AsyncSession = Depends(database.get_db) +): + # Check if current user is admin + if not current_user.is_admin: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Only administrators can declare users as deceased" + ) + + # Find the target user + result = await db.execute( + select(models.User).where(models.User.username == declare.username) + ) + target_user = result.scalars().first() + + if not target_user: + raise HTTPException(status_code=404, detail="User not found") + + # Set guale to True + target_user.guale = True + await db.commit() + + return { + "message": f"User {declare.username} has been declared as deceased", + "username": target_user.username, + "guale": target_user.guale + } + +# 用于测试热加载 +@app.post("/post1") +async def test1(): + a=2 + b=3 + c = a+b + return {"msg": f"this is a msg {c}"} + diff --git a/app/models.py b/app/models.py new file mode 100644 index 0000000..ac9fa06 --- /dev/null +++ b/app/models.py @@ -0,0 +1,37 @@ +from sqlalchemy import Column, Integer, String, ForeignKey, Text, Table, Boolean +from sqlalchemy.orm import relationship + +from .database import Base + + +class User(Base): + __tablename__ = "users" + + id = Column(Integer, primary_key=True, index=True) + username = Column(String, unique=True, index=True) + hashed_password = Column(String) + # System keys + public_key = Column(String) + private_key = Column(String) # Encrypted or raw? Storing raw for now as per req + is_admin = Column(Boolean, default=False) + guale = Column(Boolean, default=False) + + assets = relationship("Asset", foreign_keys="Asset.author_id", back_populates="author") + inherited_assets = relationship("Asset", foreign_keys="Asset.heir_id", back_populates="heir") + + + +class Asset(Base): + __tablename__ = "assets" + + id = Column(Integer, primary_key=True, index=True) + title = Column(String, index=True) + content_outer_encrypted = Column(Text) + author_id = Column(Integer, ForeignKey("users.id")) + heir_id = Column(Integer, ForeignKey("users.id")) + + # Key shard for this asset + private_key_shard = Column(String) + + author = relationship("User", foreign_keys=[author_id], back_populates="assets") + heir = relationship("User", foreign_keys=[heir_id], back_populates="inherited_assets") \ No newline at end of file diff --git a/app/note b/app/note new file mode 100644 index 0000000..051014c --- /dev/null +++ b/app/note @@ -0,0 +1 @@ +sudo docker compose exec web python reset_db.py \ No newline at end of file diff --git a/app/schemas.py b/app/schemas.py new file mode 100644 index 0000000..394e111 --- /dev/null +++ b/app/schemas.py @@ -0,0 +1,62 @@ +from pydantic import BaseModel, ConfigDict +from typing import List, Optional + +# Heir Schemas +class HeirBase(BaseModel): + name: str + +class HeirCreate(HeirBase): + pass + +class HeirOut(HeirBase): + id: int + user_id: int + model_config = ConfigDict(from_attributes=True) + +# User Schemas +class UserCreate(BaseModel): + username: str + password: str + +class UserLogin(BaseModel): + username: str + password: str + +class UserOut(BaseModel): + id: int + username: str + public_key: Optional[str] = None + is_admin: bool = False + guale: bool = False + #heirs: List[HeirOut] = [] + model_config = ConfigDict(from_attributes=True) + +# Asset Schemas (renamed from Article) +class AssetBase(BaseModel): + title: str + +class AssetCreate(AssetBase): + private_key_shard: str + content_inner_encrypted: str + +class AssetOut(AssetBase): + id: int + author_id: int + private_key_shard: str + content_outer_encrypted: str + model_config = ConfigDict(from_attributes=True) + +class AssetClaim(BaseModel): + asset_id: int + private_key_shard: str + +class AssetClaimOut(AssetClaim): + id: int + result: str + +class AssetAssign(BaseModel): + asset_id: int + heir_name: str + +class DeclareGuale(BaseModel): + username: str \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..fb020bd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,28 @@ +version: '3.8' + +services: + db: + image: postgres:15 + environment: + POSTGRES_USER: user + POSTGRES_PASSWORD: password + POSTGRES_DB: fastapi_db + ports: + - "5432:5432" + + web: + image: python:3.11-slim + command: > + sh -c "pip install -r requirements.txt && python -Xfrozen_modules=off -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload" + # 需要debug时使用 + # sh -c "pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple && python -Xfrozen_modules=off -m debugpy --listen 0.0.0.0:5678 --wait-for-client -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload" + volumes: + - .:/code + working_dir: /code + ports: + - "8000:8000" + - "5678:5678" # 暴露调试端口 + environment: + - DATABASE_URL=postgresql+asyncpg://user:password@db:5432/fastapi_db + depends_on: + - db \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c03768a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,13 @@ +fastapi +uvicorn[standard] +sqlalchemy[asyncio] +asyncpg +python-jose[cryptography] +passlib[bcrypt] +python-multipart +debugpy +watchfiles +argon2_cffi +pycryptodome +cryptography + diff --git a/reset_db.py b/reset_db.py new file mode 100644 index 0000000..2e336de --- /dev/null +++ b/reset_db.py @@ -0,0 +1,37 @@ + +import asyncio +from sqlalchemy.ext.asyncio import create_async_engine +from sqlalchemy import text +import os + +# Use 'db' for docker-compose execution (service name) +# Note: In docker-compose, internal is 'db', but from host it is 'localhost' mapped port 5432 +DATABASE_URL = "postgresql+asyncpg://user:password@db:5432/fastapi_db" + +async def reset_db(): + print(f"Connecting to {DATABASE_URL}...") + engine = create_async_engine(DATABASE_URL, echo=True) + + async with engine.begin() as conn: + print("Dropping all tables...") + # Disable foreign key checks to allow dropping in any order (Postgres specific) + await conn.execute(text("DROP SCHEMA public CASCADE;")) + await conn.execute(text("CREATE SCHEMA public;")) + print("Schema reset complete.") + + await engine.dispose() + +if __name__ == "__main__": + try: + asyncio.run(reset_db()) + print("Database reset successfully.") + except Exception as e: + print(f"Error resetting database: {e}") + # Try 'db' host if localhost fails (in case running inside container) + if "Connection refused" in str(e) or "gaierror" in str(e): + print("Retrying with host 'db'...") + DATABASE_URL = "postgresql+asyncpg://user:password@db:5432/fastapi_db" + try: + asyncio.run(reset_db()) + except Exception as e2: + print(f"Failed inside container too: {e2}") diff --git a/test/core/__pycache__/sp_gateway_rsa.cpython-310.pyc b/test/core/__pycache__/sp_gateway_rsa.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d09b809950e704d7ce2945caf9f9ee49e74fc1a GIT binary patch literal 2804 zcmaJ@>u(!H5Z}E!+ZV@n(pcn~IPEdyysHzH~zQRMOAyR}82I+M1ZsK!&KC^dj zVk?ybZJ_ks0+k@NwMDD6Q2GL?A^|G#FYxWYCbpALd`F@%dwX$>XgF)=#?B6tRVE1JK@5nAAj_J?zK!EpL8LIe? z&I@wDjnU|8ToO+wV1I%pX3z!6mshK6XeUk7q;wZ>*3ho%+8X+=-bxRgWgU&uE|Ba7 z$u8R6(h(zTxvqC$-!M`T?J1wdDBX)ug%Crh0Smu`P#SnPt~yarN+@S_35jP^S7E`9TCT_PhW4&ez<(@3Y^@;2qP19 z`OaQbERE#Nj7}$tb^$K$sRw9TcgzaaWKX4qjLoCqg-2~>8Lrm@Us$=w1ECxFF`Jog z*3u-_0frgJy%uK@yu6+d)ffa-D(7=8# zJ|ynD2&>g;&rZ= zhI25;VD#Wiky@=&!u%Ry8-V}kp8l+D(f+(M(ZA2mlq~R+)1Ngm2Pkv1wq5Ao%gqUZ3lG1(d)rIfHHEF`%P28i zLl+400~yVj#ViDcPPl&L1lh=CHw2pXaR99z_)A!R0{Bi3kf^Lc6cn6*2*CR<<-Vd; zwyxs))%C3&ft_H4C;lXOnn;ko508S}iUQ`T--<%$KT!~A-5!Od*>9jM4Qkj^i-zNj z+l;Ws!7SsqT=9L+1K+phXM|8cv3%z<9&q+FC*1Yy8NOX%kMVgQCyxU0I*NJ2EIiEL z8v|wk_P4OM1-AMQPdv=w+g5S-|0o>PR8Q6Qf?-iz_oBLP*`$=`^O&xGP%`oXC!*`b z&hXEU3;iTxQOIAyHu3vtoTND6Ip0P&d{`I=PbLQnCu9`hM2ceRQ5f+A_PP837C1xK zOV5GK!qk}{QEq>-!){N*vyBq@`a!t508P zd~vIB@`it^62CSuL~0BBB9ZGiPCzF~_VFfn{u50|C)=e1@cDq_P3PDpA><07`k7PB z`R^OwUs?Y4vhcvETlKH63gh*=cj_}2>%W{6&9Z*<0`%6PrA~eL$ImC~r*AIKU2e{Q zDFhlv&qI@K&d)YJy|*|wClKHGdJ2pP#B(<$z_3`=FW+AJ@ygDm_K2tXn6}55QOuT8 zS)&4-ECnsVGF|f^by9{?wg3mr%=o=aRG!WMzsav)A}=^FSU(WJRM~-kC;l`DO!?X3 zm#Pq0g#H=^5Zi=$-6bBapoBoQcp>u<=&d6*G%>2@ZD=oo;mKJ#;X~8w2?|%K(9Z7| zn8;ASEqj=@A=d}8U;WgVMMk^=|4EU<@!5sCa6(eIhm{ysrOkLVR^$YZDVwkaUlRUr L6IQ3Bzm>*6ATp+6 literal 0 HcmV?d00001 diff --git a/test/core/__pycache__/sp_trust_sharding.cpython-310.pyc b/test/core/__pycache__/sp_trust_sharding.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..001e99af23278697e0269e2380e1e7bb8fbae417 GIT binary patch literal 3352 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 literal 0 HcmV?d00001 diff --git a/test/core/__pycache__/sp_trust_sharding.cpython-313.pyc b/test/core/__pycache__/sp_trust_sharding.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea66fc1f41c3c1ea43399117ddd762fde994e1ff GIT binary patch literal 4885 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 literal 0 HcmV?d00001 diff --git a/test/core/__pycache__/sp_vault_aes.cpython-310.pyc b/test/core/__pycache__/sp_vault_aes.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11401e986bb77d8a59447186a0f47044f659b834 GIT binary patch literal 2910 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 a=gZi(p!4(FXu=0gy4u^eiT->e1@u4V`e<+f literal 0 HcmV?d00001 diff --git a/test/core/sp_gateway_rsa.py b/test/core/sp_gateway_rsa.py new file mode 100644 index 0000000..46ac90b --- /dev/null +++ b/test/core/sp_gateway_rsa.py @@ -0,0 +1,85 @@ +from cryptography.hazmat.primitives.asymmetric import rsa, padding +from cryptography.hazmat.primitives import serialization, hashes + +class SentinelSystemProvider: + """系统级非对称加密提供者 (独立于用户)""" + + @staticmethod + def generate_system_keys(): + """生成全新的系统公私钥对""" + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=4096 + ) + public_key = private_key.public_key() + + # 序列化私钥 (用于保存到安全服务器) + private_pem = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption() + ) + + # 序列化公钥 (用于下发或在线加密) + public_pem = public_key.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo + ) + + return private_pem, public_pem + + @staticmethod + def encrypt_with_system_public(public_pem, data_bytes): + """使用系统公钥进行二次加密""" + public_key = serialization.load_pem_public_key(public_pem) + ciphertext = public_key.encrypt( + data_bytes, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None + ) + ) + return ciphertext + + @staticmethod + def decrypt_with_system_private(private_pem, ciphertext): + """使用系统私钥进行二次解密""" + private_key = serialization.load_pem_private_key(private_pem, password=None) + plaintext = private_key.decrypt( + ciphertext, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None + ) + ) + return plaintext +if __name__ == "__main__": + # --- 演示流程 --- + + # 1. 初始化系统密钥 (这一步通常只在系统上线时执行一次) + sys_provider = SentinelSystemProvider() + private_pem, public_pem = sys_provider.generate_system_keys() + + print("【系统层】: 独立公私钥已生成。") + print(f" - 公钥 (PEM): {public_pem.decode('utf-8')[:50]}...") + print(f" - 私钥 (PEM): {private_pem.decode('utf-8')[:50]}...") + + # 2. 模拟用户已经加密过的数据 (这已经是用户那一层加密后的二进制数据) + user_encrypted_data = b"User_Encrypted_Blob_v1.0_Data" + print(f"【输入数据】: {user_encrypted_data}") + + # 3. 系统二次加密 (外层锁) + # 这一步发生在数据上传服务器时,或者存入信托池时 + double_locked_data = sys_provider.encrypt_with_system_public(public_pem, user_encrypted_data) + print(f"【使用公钥加密完成 (密文)】: {double_locked_data.hex()[:50]}...") + + # 4. 系统二次解密 (判定传承触发后) + # 只有在满足触发条件(如订阅失败)后,系统才调取私钥进行这第一层解密 + try: + system_unlocked_data = sys_provider.decrypt_with_system_private(private_pem, double_locked_data) + print(f"【使用私钥解密成功】: {system_unlocked_data}") + print("【后续步骤】: 现在数据已回归用户初级加密态,可交给用户或者继承人进行最后解密。") + except Exception as e: + print(f"解密失败: {e}") \ No newline at end of file diff --git a/test/core/sp_trust_sharding.py b/test/core/sp_trust_sharding.py new file mode 100644 index 0000000..64592d5 --- /dev/null +++ b/test/core/sp_trust_sharding.py @@ -0,0 +1,113 @@ +import hashlib +import secrets +from mnemonic import Mnemonic # 仅用于标准的助记词转换 + +class SentinelKeyEngine: + # 使用第 13 个梅森素数 (2^521 - 1),远大于 128-bit 熵,确保有限域安全 + PRIME = 2**521 - 1 + + def __init__(self): + self.mnemo = Mnemonic("english") + + def generate_vault_keys(self): + """ + 1. 生成原始 12 助记词 (Master Key) + """ + words = self.mnemo.generate(strength=128) + entropy = self.mnemo.to_entropy(words) + return words, entropy + + def split_to_shares(self, entropy): + """ + 2. SSS (3,2) 门限分片逻辑 + 公式: f(x) = S + a*x (直线方程,S为秘密,a为随机斜率) + 我们将秘密 S 分成 3 份,任选 2 份即可恢复。 + 注意:必须在有限域 GF(PRIME) 下进行运算以保证完善保密性。 + """ + # 将熵转换为大整数 + secret_int = int.from_bytes(entropy, 'big') + + # 生成一个随机系数 a (安全性需与秘密强度一致) + # a 必须在 [0, PRIME-1] 范围内 + a = secrets.randbelow(self.PRIME) + + # 定义 3 个点: x=1, x=2, x=3 + # Share = (x, f(x)) + def f(x): return (secret_int + a * x) % self.PRIME + + share1 = (1, f(1)) # 手机分片 + share2 = (2, f(2)) # 云端分片 + share3 = (3, f(3)) # 传承卡分片 + + return [share1, share2, share3] + + def recover_from_shares(self, share_a, share_b): + """ + 3. 恢复逻辑:拉格朗日插值还原 + 已知 (x1, y1) 和 (x2, y2),求 f(0) 即秘密 S + 公式: S = (x2*y1 - x1*y2) / (x2 - x1) + 在有限域下,除法变为乘以模逆: S = (x2*y1 - x1*y2) * (x2 - x1)^-1 mod P + """ + x1, y1 = share_a + x2, y2 = share_b + + # 计算分子 + numerator = (x2 * y1 - x1 * y2) % self.PRIME + # 计算分母的模逆 (x2 - x1) + denominator = (x2 - x1) % self.PRIME + inv_denominator = pow(denominator, -1, self.PRIME) + + # 还原常数项 S + secret_int = (numerator * inv_denominator) % self.PRIME + + # 转回字节并生成助记词 + # 注意:secret_int 可能略小于 16 字节(高位为0),需要补齐 + # 但由于 entropy 原始就是 16 字节,这里直接转换即可 + try: + recovered_entropy = secret_int.to_bytes(16, 'big') + except OverflowError: + # 理论上不应发生,除非计算出的 secret_int 大于 128 bit (即原始 entropy 大于 128 bit) + # 这里为了健壮性,如果原始 entropy 是 16 字节,这里应该也是。 + # 如果 PRIME 很大,secret_int 还是原来的值。 + recovered_entropy = secret_int.to_bytes((secret_int.bit_length() + 7) // 8, 'big') + + return self.mnemo.to_mnemonic(recovered_entropy) +if __name__ == "__main__": + # --- Sentinel 协议业务流程模拟 --- + + engine = SentinelKeyEngine() + + # [生前]:初始化金库 + master_words, entropy = engine.generate_vault_keys() + print(f"【1. 生成原始助记词】: {master_words}") + + shares = engine.split_to_shares(entropy) + print(f"【2. SSS 分片完成】:") + print(f" - 分片1 (手机安全区): {shares[0]}") + print(f" - 分片2 (Sentinel云): {shares[1]}") + print(f" - 分片3 (传承卡单词): {shares[2]}") + + print("-" * 50) + + # [死后/传承]:模拟用户失联,触发被动验证 + # 假设继承人拿着卡片 (Share 3),向服务器请求分片 (Share 2) + successor_share = shares[2] + server_share = shares[1] + + # 执行恢复 + recovered_words = engine.recover_from_shares(shares[0], shares[1]) + print(f"【1. 手机+云 : {recovered_words}") + + recovered_words = engine.recover_from_shares(shares[0], shares[2]) + print(f"【2. 手机+传承卡 : {recovered_words}") + + recovered_words = engine.recover_from_shares(shares[1], shares[2]) + print(f"【3. 云+传承卡 : {recovered_words}") + + # 校验一致性 + assert recovered_words == master_words + print("\n结果:恢复出的助记词与原始完全一致。") + + + with open("words.txt", "w") as f: + f.write("%s\n"%master_words) \ No newline at end of file diff --git a/test/core/sp_vault_aes.py b/test/core/sp_vault_aes.py new file mode 100644 index 0000000..2d7d206 --- /dev/null +++ b/test/core/sp_vault_aes.py @@ -0,0 +1,77 @@ +import os +from mnemonic import Mnemonic +from Crypto.Cipher import AES +from Crypto.Protocol.KDF import PBKDF2 +from Crypto.Util.Padding import pad, unpad + +class SentinelVault: + def __init__(self, salt=None): + self.mnemo = Mnemonic("english") + # 默认盐值仅用于演示,生产环境建议每个用户随机生成并存储 + self.salt = salt if salt else b'Sentinel_Salt_2026' + + def derive_key(self, mnemonic_phrase): + """ + 使用 PBKDF2 将助记词转换为 AES-256 密钥 (32 bytes) + """ + # 种子生成遵循 BIP-39 逻辑 + seed = self.mnemo.to_seed(mnemonic_phrase, passphrase="") + # 派生出一个 32 字节的强密钥 + key = PBKDF2(seed, self.salt, dkLen=32, count=100000) + return key + + def encrypt_data(self, key, plaintext): + """ + 使用 AES-256 GCM 模式进行加密 (具备完整性校验) + """ + cipher = AES.new(key, AES.MODE_GCM) + nonce = cipher.nonce + ciphertext, tag = cipher.encrypt_and_digest(plaintext.encode('utf-8')) + # 返回:随机数 + 校验位 + 密文 + return nonce + tag + ciphertext + + def decrypt_data(self, key, encrypted_blob): + """ + AES-256 GCM 解密 + """ + nonce = encrypted_blob[:16] + tag = encrypted_blob[16:32] + ciphertext = encrypted_blob[32:] + + cipher = AES.new(key, AES.MODE_GCM, nonce=nonce) + try: + plaintext = cipher.decrypt_and_verify(ciphertext, tag) + return plaintext.decode('utf-8') + except ValueError: + return "【解密失败】:密钥错误或数据被篡改" + +if __name__ == "__main__": + # --- 模拟 Sentinel 协议完整业务流 --- + + # 1. 假设这是通过之前 SSS 算法恢复出来的 12 词 + recovered_mnemonic = "apple banana cherry dog elephant fish goat horse ice jacket kangaroo lion" + try: + with open("words.txt", "r") as f: + recovered_mnemonic = f.read().strip() + except FileNotFoundError: + print("words.txt 文件未找到,使用默认助记词进行演示。") + + print(f"Demo助记词:{recovered_mnemonic}") + vault = SentinelVault() + + # 2. 生成加密密钥 + aes_key = vault.derive_key(recovered_mnemonic) + aes_key_hex = aes_key.hex() + print(f"【密钥派生完成】:len:{len(aes_key_hex)} -> {aes_key_hex[:20]}...") + + # 3. 用户生前加密资产(如:银行账户、数字遗产) + my_legacy = "我的瑞士银行账号是:CH123456789,密码是:Sentinel2026" + print(f"【Demo资产信息】:{my_legacy}") + encrypted_asset = vault.encrypt_data(aes_key, my_legacy) + encrypted_asset_hex = encrypted_asset.hex() + print(f"【数据已加密】:len:{len(encrypted_asset_hex)} -> {encrypted_asset_hex[:40]}...") + + # 4. 模拟继承人通过分片拼凑后进行解密 + print("-" * 50) + decrypted_content = vault.decrypt_data(aes_key, encrypted_asset) + print(f"【继承人解密成功】:{decrypted_content}") \ No newline at end of file diff --git a/test/test_scenario.py b/test/test_scenario.py new file mode 100644 index 0000000..0f7268c --- /dev/null +++ b/test/test_scenario.py @@ -0,0 +1,201 @@ +import requests +import json +from core.sp_trust_sharding import SentinelKeyEngine +from core.sp_vault_aes import SentinelVault +import ast + +BASE_URL = "http://localhost:8000" + +def register_user(username, password): + url = f"{BASE_URL}/register" + data = { + "username": username, + "password": password + } + response = requests.post(url, json=data) + if response.status_code == 200: + print(f"User {username} registered successfully.") + return response.json() + else: + print(f"Failed to register {username}: {response.text}") + return None + +def login_user(username, password): + url = f"{BASE_URL}/token" + data = { + "username": username, + "password": password + } + response = requests.post(url, json=data) + if response.status_code == 200: + print(f"User {username} logged in successfully.") + return response.json()["access_token"] + else: + print(f"Failed to login {username}: {response.text}") + return None + +def create_asset(token, title, private_key_shard, content_inner_encrypted): + url = f"{BASE_URL}/assets/" + headers = {"Authorization": f"Bearer {token}"} + data = { + "title": title, + "private_key_shard": str(private_key_shard), + "content_inner_encrypted": str(content_inner_encrypted) + } + response = requests.post(url, json=data, headers=headers) + if response.status_code == 200: + print(f"Asset '{title}' created successfully.") + return response.json() + else: + print(f"Failed to create asset: {response.text}") + return None + +def assign_heir(token, asset_id, heir_name): + url = f"{BASE_URL}/assets/assign" + headers = {"Authorization": f"Bearer {token}"} + data = { + "asset_id": asset_id, + "heir_name": heir_name + } + response = requests.post(url, json=data, headers=headers) + if response.status_code == 200: + print(f"Asset {asset_id} assigned to heir {heir_name} successfully.") + return response.json() + else: + print(f"Failed to assign heir: {response.text}") + return None + +def declare_user_guale(token, username): + url = f"{BASE_URL}/admin/declare-guale" + headers = {"Authorization": f"Bearer {token}"} + data = {"username": username} + response = requests.post(url, json=data, headers=headers) + if response.status_code == 200: + print(f"User {username} declared as 'guale' successfully.") + return response.json() + else: + print(f"Failed to declare guale: {response.text}") + return None + +def claim_asset(token, asset_id, private_key_shard): + url = f"{BASE_URL}/assets/claim" + headers = {"Authorization": f"Bearer {token}"} + data = { + "asset_id": asset_id, + "private_key_shard": private_key_shard + } + response = requests.post(url, json=data, headers=headers) + if response.status_code == 200: + print(f"Asset {asset_id} claimed successfully.") + return response.json() + else: + print(f"Failed to claim asset: {response.text}") + return None + +def main(): + # 1. 创建三个用户 + users = [ + ("user1", "pass123"), + ("user2", "pass123"), + ("user3", "pass123") + ] + + for username, password in users: + register_user(username, password) + + # 1.1 用户一信息生成 + key_engine = SentinelKeyEngine() + # 1.1 生成助记词 (BIP-39) + master_words, entropy = key_engine.generate_vault_keys() + print(f" [生成] 原始助记词: {master_words}") + + # 1.2 SSS 分片 (3-of-2) + shares = key_engine.split_to_shares(entropy) + share_a = shares[0] # Device (手机) + share_b = shares[1] # Cloud (云端) + share_c = shares[2] # Physical (传承卡) + + print("\n## 2. 用户内层加密流 (Vault Layer)") + user_data = "我的瑞士银行账号是:CH123456789,密码是:Sentinel2027" + print(f" [输入] 用户隐私数据: {user_data}") + + vault = SentinelVault() + # 2.1 派生 AES 密钥 + aes_key = vault.derive_key(master_words) + # 2.2 加密数据 + ciphertext_1 = vault.encrypt_data(aes_key, user_data) + + # 2. 用户一登录 + token1 = login_user("user1", "pass123") + if not token1: + return + + # 3. 创建一个 asset + asset = create_asset( + token1, + "My Secret Asset", + share_a, + ciphertext_1 + ) + + if not asset: + return + + asset_id = asset["id"] + print(f" [输出] Asset ID: {asset_id}") + + # 4. 指定用户 2 为继承人 + print("用户 1 指定用户 2 为继承人") + assign_heir(token1, asset_id, "user2") + + print("\n## 3. 继承流 (Inheritance Layer)") + # 5. Admin 宣布用户 1 挂了 + print("Admin 宣布用户 1 挂了") + admin_token = login_user("admin", "admin123") + if not admin_token: + print("Failed to login as admin. Make sure the database is initialized with an admin user.") + return + + declare_user_guale(admin_token, "user1") + + # 6. 用户 2 登录 + print("用户 2 登录") + token2 = login_user("user2", "pass123") + if not token2: + return + + # 7. 用户 2 申领资产,并带上自己的分片 (share_c) + print("用户 2 申领资产,并带上自己的分片 (share_c)") + claim_res = claim_asset(token2, asset_id, json.dumps(share_c)) + if not claim_res: + return + + print(f" [输出] Claim Result (私钥分片与加密内容已获取):") + print(f" - Server Shard Key: {claim_res['server_shard_key']}") + print(f" - Decrypted Content (Outer Layer): {claim_res['decrypted_content'][:50]}...") + + print("\n## 4. 客户端恢复流 (Client Recovery)") + # 8. 恢复助记词 + # 继承人有自己的 share_c,从服务器拿到了存储在 asset 里的 share_a (server_shard_key) + #server_shard = tuple(claim_res['server_shard_key']) + server_shard = ast.literal_eval(claim_res['server_shard_key']) + + recovered_mnemonic = key_engine.recover_from_shares(server_shard, share_c) + print(f" [恢复] 助记词: {recovered_mnemonic}") + + # 9. 派生密钥 + recovered_aes_key = vault.derive_key(recovered_mnemonic) + + # 10. 解密内容 (Inner Layer) + #inner_ciphertext = bytes.fromhex(claim_res["decrypted_content"]) + inner_ciphertext = ast.literal_eval(claim_res["decrypted_content"]) + decrypted_final = vault.decrypt_data(recovered_aes_key, inner_ciphertext) + print(f" [完成] 解密后的原始数据: {decrypted_final}") + + if decrypted_final == user_data: + print("\n✅ 测试成功!数据完整恢复。") + else: + print("\n❌ 测试失败!解密数据不匹配。") + +if __name__ == "__main__": + main() diff --git a/verify_integrity.py b/verify_integrity.py new file mode 100644 index 0000000..5bb82b3 --- /dev/null +++ b/verify_integrity.py @@ -0,0 +1,39 @@ + +import sys +import os + +# Ensure we can import app +sys.path.append(os.getcwd()) + +print("Importing app modules...") +try: + from app import models, schemas, auth, main + print("Imports successful.") +except Exception as e: + print(f"Import failed: {e}") + sys.exit(1) + +print("Testing Key Generation...") +try: + priv, pub = auth.generate_key_pair() + if priv and pub: + print("Key generation successful.") + print(f"Public Key sample: {pub[:30]}...") + else: + print("Key generation returned empty values.") + sys.exit(1) +except Exception as e: + print(f"Key generation failed: {e}") + sys.exit(1) + +print("Testing Model Instantiation...") +try: + user = models.User(username="test", public_key=pub, private_key=priv) + heir = models.Heir(name="heir1") + asset = models.Asset(title="test asset", content="content", private_key_shard="shard") + print("Model instantiation successful.") +except Exception as e: + print(f"Model instantiation failed: {e}") + sys.exit(1) + +print("Verification passed!")