From 1fbdfc21a2a70c3ae4aec1346cc2cd5ba8cb6cf6 Mon Sep 17 00:00:00 2001 From: Insoleet <insomniak.fr@gmail.com> Date: Sat, 3 Jan 2015 16:14:25 +0100 Subject: [PATCH] Added fixed pylibscrypt to load libsodium.dll --- lib/pylibscrypt/__init__.py | 63 +++ .../__pycache__/__init__.cpython-34.pyc | Bin 0 -> 712 bytes .../__pycache__/common.cpython-34.pyc | Bin 0 -> 1485 bytes .../__pycache__/mcf.cpython-34.pyc | Bin 0 -> 7075 bytes .../__pycache__/pbkdf2.cpython-34.pyc | Bin 0 -> 1440 bytes .../__pycache__/pylibscrypt.cpython-34.pyc | Bin 0 -> 3969 bytes .../__pycache__/pylibsodium.cpython-34.pyc | Bin 0 -> 7001 bytes .../pylibsodium_salsa.cpython-34.pyc | Bin 0 -> 6502 bytes .../pypyscrypt_inline.cpython-34.pyc | Bin 0 -> 6336 bytes .../__pycache__/pyscrypt.cpython-34.pyc | Bin 0 -> 2531 bytes .../__pycache__/tests.cpython-34.pyc | Bin 0 -> 16784 bytes lib/pylibscrypt/bench.py | 55 +++ lib/pylibscrypt/common.py | 61 +++ lib/pylibscrypt/fuzz.py | 324 ++++++++++++++ lib/pylibscrypt/inline.py | 89 ++++ lib/pylibscrypt/mcf.py | 246 ++++++++++ lib/pylibscrypt/pbkdf2.py | 62 +++ lib/pylibscrypt/pylibscrypt.py | 161 +++++++ lib/pylibscrypt/pylibsodium.py | 252 +++++++++++ lib/pylibscrypt/pylibsodium_salsa.py | 244 ++++++++++ lib/pylibscrypt/pypyscrypt.py | 197 ++++++++ lib/pylibscrypt/pypyscrypt_inline.py | 244 ++++++++++ lib/pylibscrypt/pyscrypt.py | 85 ++++ lib/pylibscrypt/tests.py | 419 ++++++++++++++++++ setup.py | 4 +- 25 files changed, 2504 insertions(+), 2 deletions(-) create mode 100644 lib/pylibscrypt/__init__.py create mode 100644 lib/pylibscrypt/__pycache__/__init__.cpython-34.pyc create mode 100644 lib/pylibscrypt/__pycache__/common.cpython-34.pyc create mode 100644 lib/pylibscrypt/__pycache__/mcf.cpython-34.pyc create mode 100644 lib/pylibscrypt/__pycache__/pbkdf2.cpython-34.pyc create mode 100644 lib/pylibscrypt/__pycache__/pylibscrypt.cpython-34.pyc create mode 100644 lib/pylibscrypt/__pycache__/pylibsodium.cpython-34.pyc create mode 100644 lib/pylibscrypt/__pycache__/pylibsodium_salsa.cpython-34.pyc create mode 100644 lib/pylibscrypt/__pycache__/pypyscrypt_inline.cpython-34.pyc create mode 100644 lib/pylibscrypt/__pycache__/pyscrypt.cpython-34.pyc create mode 100644 lib/pylibscrypt/__pycache__/tests.cpython-34.pyc create mode 100644 lib/pylibscrypt/bench.py create mode 100644 lib/pylibscrypt/common.py create mode 100644 lib/pylibscrypt/fuzz.py create mode 100644 lib/pylibscrypt/inline.py create mode 100644 lib/pylibscrypt/mcf.py create mode 100644 lib/pylibscrypt/pbkdf2.py create mode 100644 lib/pylibscrypt/pylibscrypt.py create mode 100644 lib/pylibscrypt/pylibsodium.py create mode 100644 lib/pylibscrypt/pylibsodium_salsa.py create mode 100644 lib/pylibscrypt/pypyscrypt.py create mode 100644 lib/pylibscrypt/pypyscrypt_inline.py create mode 100644 lib/pylibscrypt/pyscrypt.py create mode 100644 lib/pylibscrypt/tests.py diff --git a/lib/pylibscrypt/__init__.py b/lib/pylibscrypt/__init__.py new file mode 100644 index 00000000..764f925c --- /dev/null +++ b/lib/pylibscrypt/__init__.py @@ -0,0 +1,63 @@ +# Copyright (c) 2014, Jan Varho +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +"""Scrypt for Python""" + +__version__ = '1.4.0-git' + +# First, try loading libscrypt +_done = False +try: + from .pylibscrypt import * +except ImportError: + pass +else: + _done = True + +# If that didn't work, try the scrypt module +if not _done: + try: + from .pyscrypt import * + except ImportError: + pass + else: + _done = True + +# Next: libsodium +if not _done: + try: + from .pylibsodium import * + except ImportError: + pass + else: + _done = True + +# Unless we are on pypy, we want to try libsodium_salsa as well +if not _done: + import platform + if platform.python_implementation() != 'PyPy': + try: + from .pylibsodium_salsa import * + except ImportError: + pass + else: + _done = True + +# If that didn't work either, the inlined Python version +if not _done: + from .pypyscrypt_inline import * + +__all__ = ['scrypt', 'scrypt_mcf', 'scrypt_mcf_check'] + + diff --git a/lib/pylibscrypt/__pycache__/__init__.cpython-34.pyc b/lib/pylibscrypt/__pycache__/__init__.cpython-34.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2a0a1fdebc7e9c5d9c915f308d52d6fb05536ad GIT binary patch literal 712 zcmZ8d&ui2`7=4p$er%Ghwjg*c=w$_0g`P?gp;hoA3tdo$g)wHPZQRL^PSQep?cd<J zc=TV|t9a<8!X7<2Np>}HV7__t@!tF1%v~_@9)J2ayR(5G=&VN?ztX#SNB|B{T?r@w zH$V+kTi_=60Mf+leY%5z7+~Q+?ST;TF?H~PTQyM_D3T7{>eHi!ex7XTDH_F1M`8CB zUt7h^e~J~j4emfXpbmtCFD|ZiW)}++YSr-@_Wgdge!n(;=PRs3SY-V~4>R53LvRn= z$EgYG*Rzht!PWM0q^i8!J;~JWv?|ZC^lJEGvNw7D^fWHt-0IK6m@LVY*{w!+M+eht zT2Z@bRMcxvmP9A?{y)s3GZ8Ju!O~%jXAxu6XY5j_BF<8Mv2-&P)Xys!e{NMv|80_I zs(h_frl^}&>j7&{#^Z}*>E<#lb<l)9$eXAvPI4&{k(Obpt?72_*sxg<$|9^=8#5NC zGEPMuDwLA3?@+FOQ&WCFJKCQgiOcz6#>Ko7tcRJlh)c#Md9`#8l8j$S@$#PbC=PV7 b4coMdNw&y{SUo)K^lKcDoxi+5-$%wDW&E$v literal 0 HcmV?d00001 diff --git a/lib/pylibscrypt/__pycache__/common.cpython-34.pyc b/lib/pylibscrypt/__pycache__/common.cpython-34.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b069b1cdba0dd48a228008131151d7649bc75c5a GIT binary patch literal 1485 zcmZ`(OK%f96h8CFqiI5+gtsVOL`aPUq9B2Ucoe0rgai@Lg6OCyOlC|vWa1gwPD@4= zi_85Vh~@nk_ji~jt8Cb@Va2iAkSQ%Q^5^68eaAlI<MH=ut<re=W#h{Xz&FS}4%*Au z#CwDgz(cekv>~>@L&E{jhR_AiG3aW_D?nHUuLxlYyb^>3@X8RDfzH4ufPNA9bD$Mq z4$xWfDiF><h&T)X;+$1I!}?+^yWdI%gM^VCpGST-rh@n^B=3eSkWs<}844Pb?udwh zk5WmZK^oHmWzskF7}JHe(CIhsG=!^rvD6q_XpB#2C?lxi2kx^T9WUb`C?e=b_d2kJ zO?<!z39yG~!6zHU0!aK~v@Q6@0=Cau6ZW2A?Zd1M(t-D`34db*&S8O*6MQrR6zAu6 zbAr!CP&h0o<OKg4L2)YB!-VId?*jh{5{rX%m`sk8W-D-hS^O|&3HkuswX|?HC&U6* zp>F}dVyIe<!m966cMWwSM>(+PLOnbU{Q~e!L!Zpi=A`IJrJ+tuDLiZIi}iN4korRG zCOjmAp^&6YiH}v6lnBWq*2^vmKbC*t>g_4PNW>)VQJ&TLvEKAJ?}eDMp4>i$8ryg} z#t#v)%WV?)j3p+!*yp$ik=s5aH=E7dx40H&S97V5G}%Q-@{Y*egyaL3xIUlR{4~Qf z5s{2`Xf{91Z{iy-Xz>+Jnq*4ik0K6MC(S0gbt60X$2UyE;{t|Ho-#yeE-w~UB@z)6 z(q{ox1-;Wk&1{TPy2^RNRf!D;UCM=5eyq2X`?0FL^5fw?jp5}<y_BmfqpY^FxUy2^ zCYZ;6z|Y`1Kd-Ia-CChLTdgFdTj?l{y8FNJAv-V^!1Z*bD#11l`W@U_(ZFzVL#J)c z9lZ0{=UWxW7qRJHx09yl=P)MHm)T-Xl{%d;2|68KL#KCoz4c=A*+%DSYpwI_#p>GQ z*PT16e$*+J)P<womDROJFQ06vGe@aM?M+oaaJPS3{I`{=QZG)rek`~yhANob$Yg5v hDF<gYU%od;!eLAw%;8AX5sG%zDO%>W4VEt1zW~?WZd?EW literal 0 HcmV?d00001 diff --git a/lib/pylibscrypt/__pycache__/mcf.cpython-34.pyc b/lib/pylibscrypt/__pycache__/mcf.cpython-34.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0a4bbee9fa25919f142a01f2522c3403ecc345aa GIT binary patch literal 7075 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 literal 0 HcmV?d00001 diff --git a/lib/pylibscrypt/__pycache__/pbkdf2.cpython-34.pyc b/lib/pylibscrypt/__pycache__/pbkdf2.cpython-34.pyc new file mode 100644 index 0000000000000000000000000000000000000000..262909f5bd77a50500b01079d8110bb6a0662e93 GIT binary patch literal 1440 zcmY*Z&u`;I6n-<Y9XoNhX%TG8s=`7@D2UCWyAl#A6_jW%i>iv<RVAxk%kfM%P8>U( z8MjU4TqrC^oVml1KZomFk&yPng&QZ{IN2&#nKy6VeDBBi<|%*soz_<V+jH9m_!HK? z2I^nplCM!PfDk1CMj;~*qO#N}g!HZ6fUp4!e->mGJVz+AfjRI!I@rJ+U`?1%5Z}FZ zXhPVm9bCv<n0gR;K)46v7VsvF+s0?-mH^2yH-%?C?`Cj(_;m1OFG%xXRtg>*&()~N zKMj&%I*V00%y@8>s?o~+sQ)+^#d4IT!~SeIVds0%Xc{MPkP#fcAt-x9lTQ-c81UP~ zg?#|SUH*di3SeAwt!lbR?ni)g)Gfp9HGx~83mYmM#uRl}I8Zq-Za{@cEL>T&wFwnE zI52TRysQy6<Os50VSz#)Yh}Epa7tm}!oq{fh03dYX2EA@y8a6kwhOJUk@pchIQs(@ zt=e~)z718|q*pGC(HDcX0CB5Q%pIP=oEJV+_*5=TED(%TJ}f$LEli4~eH%f8vol!S zTcfuD)kaOPa~BiOcQG+Sua1B{unl5xwhfCeVDzdB_D<C?`_55g|KclhaslK7U$&~B z8X>02i(lacS&N?unwL%cfx;IEQy8OZFwc6eo5OFpDn%{>HR6HbvdmOaoY$x0SjwwH zu!o1S<Scl~=fQxB^b)6|$b%<kp47^oZcO-Gdn|p$rJ5G(AAEC2%uJZi>}Cqy!6myW zmXq)kmW%B(vq+A5R5w@Cr5icFGPA@`Sb03<zr()|2cMq~_~q%>1>>i)IS!#r#C)dy zuh(PQpUt(qpT*N5iw|~9PF!%ayEe4_{j5mhOdg=J#2pD!rJx2$55DPjv}LASm<iTa zQeaG{oUvGO?G5LO$3nz&-Ci<|WO~E3!_rISuAOTU=dbWAl_(RXty!E*%&lpTcKEg8 zIn%a@B72*nWiq(4Rj0Kj;|x)vD03!5n@uvFYcdMyNQTZ3%bv)vUGGtX7!`V1ELB}W z=r8SgIURC=9z#uDg!DzKNvi1xx3OqrwjQaI3g2XIueId?8e|^@(2t2vI@BW%$TsQH z_wlqtwx~}Wa^KvLE%F}OrJK|tT_Qd-f!rvX#%Uf!_-Zy%Q?lplW)!g^i6YTNh`49O zy9Vswf6TPV?>x;bN0DyGxzr#&GLWt)^JrC^EK_WJ6T4exT-GGkM9st9U)~;={~fu7 S!fE(es!v;ujSr00CI11i)K=mE literal 0 HcmV?d00001 diff --git a/lib/pylibscrypt/__pycache__/pylibscrypt.cpython-34.pyc b/lib/pylibscrypt/__pycache__/pylibscrypt.cpython-34.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df0cf565d6fd9d4eb91e4cee1b23f01c29f0582c GIT binary patch literal 3969 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 literal 0 HcmV?d00001 diff --git a/lib/pylibscrypt/__pycache__/pylibsodium.cpython-34.pyc b/lib/pylibscrypt/__pycache__/pylibsodium.cpython-34.pyc new file mode 100644 index 0000000000000000000000000000000000000000..826de49d1d2925b78d0a0bd07b44bbaa1a9b383a GIT binary patch literal 7001 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 literal 0 HcmV?d00001 diff --git a/lib/pylibscrypt/__pycache__/pylibsodium_salsa.cpython-34.pyc b/lib/pylibscrypt/__pycache__/pylibsodium_salsa.cpython-34.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fded72646b6f1543f4cc4ebf11fae7d3780657bc GIT binary patch literal 6502 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 U1w7SRlh$m6u`%EhXePyf0Mp9?2mk;8 literal 0 HcmV?d00001 diff --git a/lib/pylibscrypt/__pycache__/pypyscrypt_inline.cpython-34.pyc b/lib/pylibscrypt/__pycache__/pypyscrypt_inline.cpython-34.pyc new file mode 100644 index 0000000000000000000000000000000000000000..97c3dd5027ae493e1042b7e1ed043010eb99b4a3 GIT binary patch literal 6336 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 IFYs~xZ!mN#H~;_u literal 0 HcmV?d00001 diff --git a/lib/pylibscrypt/__pycache__/pyscrypt.cpython-34.pyc b/lib/pylibscrypt/__pycache__/pyscrypt.cpython-34.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0bc0e240c509da046749fa23e729c0db48d17019 GIT binary patch literal 2531 zcmbtWT~8ZF6un~`V~imV<O@Y=N9sec0|b+XMo|={g&;*uPy}w1SgBUy-7##~ug>g3 z9HBmx^oR7ZKc;_RU;D&Me?a@vGqc7eY1BT{@{VVA?%uigo^$T{_r^?pF8aCq=QPo8 zbnbHz^M|-}jS+c7J&cUJ5(SKUnBZHcphRJrdKF?`g}ez0CMc{@Z?afdDX39**dmF| zN%CssO_5h8Z<>sG!;UlL&C*G!!k$N;P(@DBL7mi3#2AyXyg(<6eq{7AV)Q~zlWdU0 z6HW3ik~c^1D%2~PpvGHrmb`iLF44OQ>Rll3GI>|%9l~#t#3NVfphT*|MybqEZ_Y+> z&4xZty?J7Co?c%fIY+NAn`agf0gI4*iFR8HZy)ZuYM7+l50gNKGD=12#}QBWMao?f z1e*I%8e=W_4Q)+t@MM_o!zPTqERd(B$lEQJSN4V8KSgy3i*o1=@Wa}=h8u~{`Y2YO zt{7Xf5#((9w1j!iRL+vT+Rp}oj85CQ^>(#5X5^k9N~KLiS){WhiB&4SCBC|{ev>Qt zeda6aoj$~4ZWV+1nBRF^8@Tj$7*e8Rd>I{==(tSB6*``vlPa;{ElMr&*hV#A0Sdqf zV8RaY!_=!<YpU)(k7f;uX=#ifII!EQiC>Y(j!ZKZX)gGI9CA-8|4@27(|)vPOE*dh zo6B}nD3j74i(FIfdY$95&BIKmyf5J;iI1e>@qnjCF%N{=!wQf@yu<rh%A@yhuJLx8 zuixg2exyYtTh^W8BJy}LcACW6PZ1W6vapYua-F&R1<s1MVR)ZgKetzlaJoR;lLL_j zDL)iJCXt<z_eP;EFY~o_yYo3;{GqdcaCRc@-n9k^$Fx&8dMd+M4N(lSC%LbAqGAIh z@)iLHwYJ-8g>M!o=`&Ed=*LL3v(BITU#(c5T|bmO6$di<xR>Od7h}fncUD(JZK^2X z)&;luo4e~Pf2S7+)H{xlr#~2&k_=QF^1$zp;2Nc`mu%5Pk-Gb+f|UY9A(a4nhw_Gv z4)i|Y1Kf#K;l`N7%HQ)%nu$mxvfRRE5F1WN6><$*Q<I~8sU%lfl$s3Vf%P}$QK3iF zEs9ga3>Ehw)FnQQGw#M&U}z>yp@fm@x#)3LU#2+8`Ws4UJaUy3u-@kWfQJPx1`%j4 z^#@r{6gsb+={2ut5v0mUN*NuiDKpeDY`4mJ9p!Kj9I#X8lTJ~yy#B4>$|I#>)vBr) zV?S$#_kHA*7-#-M$|&`ZTbuV@ZOX$}8?h%}CBrcQAIysrR+3>}EvBF0VQr$J`Gr!M z-C|{?E@SmxFEDX$;L@LBKreXB2paxKM=)w;0^77{^=s7p)U-IE_=fF~fDa_#rqBIT z+h1>P70h}jln5p2&UGSOOL-lllb%U<0APyv7h7j?`d`Yw-LVM&r{KYcBMJ3%Y-d=z zbH_fm3xvl_Lh$}Dm0+eaovX79TA3T#vUa4L#{<)U3^9xj_n?4i38hkKH&Bng56N}b z?%w2mKLshZVW_vl7k8u^hj_<$SQRY;ESkid!ojN~uNOseu(l}v8-1#Yk^}#ZrB5+j z0=nudh6e^~^KPMOyLRmyOY>{qKb8!JcOQ`9q-+TA_KUM#-c=dELkotG&ji8GHvyYE zf4Z?{8PO`MD{z`O|4zfP0x6OyDRXuq2y^(ks28w&f~$zSq|7?6IZi12$Z_x%od5!< zZe4-O94~eqC!ZWOaqC-|%v94nh5rI(ZUd>BV#iTF)g2~<AoJ<Rp=rHVoq26{W9Q|w zuCqO!sqrky7e>2C&v~}<XzTF{XY<k4m)|_;+NzF&jKFmSRMtbSuH)=p6OC-t3>=;L zsRY9IkAgmjmaTw+EU(=!b|08OXqscFmK&vJsmbb<GHbCJcCmczdV^h<X-qU&gEiS- Dt0<&z literal 0 HcmV?d00001 diff --git a/lib/pylibscrypt/__pycache__/tests.cpython-34.pyc b/lib/pylibscrypt/__pycache__/tests.cpython-34.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c7edcc5d7371d01da9b17fc9369e61d12562c5f2 GIT binary patch literal 16784 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 M0c)S<d(QU!KWCU*@Bjb+ literal 0 HcmV?d00001 diff --git a/lib/pylibscrypt/bench.py b/lib/pylibscrypt/bench.py new file mode 100644 index 00000000..488dfcee --- /dev/null +++ b/lib/pylibscrypt/bench.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + +# Copyright (c) 2014, Jan Varho +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +"""Simple benchmark of python vs c scrypt""" + +import time + +from .common import * +from .pylibscrypt import scrypt +from .pypyscrypt_inline import scrypt as pyscrypt +from .pylibsodium_salsa import scrypt as pcscrypt + + +# Benchmark time in seconds +tmin = 5 +Nmax = 20 + +t1 = time.time() +for i in xrange(1, Nmax+1): + pyscrypt(b'password', b'NaCl', N=2**i) + if time.time() - t1 > tmin: + Nmax = i + break +t1 = time.time() - t1 +print('Using N = 2,4,..., 2**%d' % Nmax) +print('Python scrypt took %.2fs' % t1) + +t2 = time.time() +for i in xrange(1, Nmax+1): + pcscrypt(b'password', b'NaCl', N=2**i) +t2 = time.time() - t2 +print('Py + C scrypt took %.2fs' % t2) + +t3 = time.time() +for i in xrange(1, Nmax+1): + scrypt(b'password', b'NaCl', N=2**i) +t3 = time.time() - t3 +print('C scrypt took %.2fs' % t3) + +print('Python scrypt took %.2f times as long as C' % (t1 / t3)) +print('Py + C scrypt took %.2f times as long as C' % (t2 / t3)) + diff --git a/lib/pylibscrypt/common.py b/lib/pylibscrypt/common.py new file mode 100644 index 00000000..24142697 --- /dev/null +++ b/lib/pylibscrypt/common.py @@ -0,0 +1,61 @@ +# Copyright (c) 2014, Jan Varho +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +"""Common variables and functions used by scrypt implementations""" + +import numbers + + +SCRYPT_MCF_PREFIX_7 = b'$7$' +SCRYPT_MCF_PREFIX_s1 = b'$s1$' +SCRYPT_MCF_PREFIX_DEFAULT = b'$s1$' +SCRYPT_MCF_PREFIX_ANY = None + +SCRYPT_N = 1<<14 +SCRYPT_r = 8 +SCRYPT_p = 1 + +# The last one differs from libscrypt defaults, but matches the 'interactive' +# work factor from the original paper. For long term storage where runtime of +# key derivation is not a problem, you could use 16 as in libscrypt or better +# yet increase N if memory is plentiful. + +xrange = xrange if 'xrange' in globals() else range + +def check_args(password, salt, N, r, p, olen=64): + if not isinstance(password, bytes): + raise TypeError('password must be a byte string') + if not isinstance(salt, bytes): + raise TypeError('salt must be a byte string') + if not isinstance(N, numbers.Integral): + raise TypeError('N must be an integer') + if not isinstance(r, numbers.Integral): + raise TypeError('r must be an integer') + if not isinstance(p, numbers.Integral): + raise TypeError('p must be an integer') + if not isinstance(olen, numbers.Integral): + raise TypeError('length must be an integer') + if N > 2**63: + raise ValueError('N cannot be larger than 2**63') + if (N & (N - 1)) or N < 2: + raise ValueError('N must be a power of two larger than 1') + if r <= 0: + raise ValueError('r must be positive') + if p <= 0: + raise ValueError('p must be positive') + if r * p >= 2**30: + raise ValueError('r * p must be less than 2 ** 30') + if olen <= 0: + raise ValueError('length must be positive') + diff --git a/lib/pylibscrypt/fuzz.py b/lib/pylibscrypt/fuzz.py new file mode 100644 index 00000000..91cd4e2b --- /dev/null +++ b/lib/pylibscrypt/fuzz.py @@ -0,0 +1,324 @@ +#!/usr/bin/env python + +# Copyright (c) 2014, Jan Varho +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +"""Fuzzes scrypt function input, comparing two implementations""" + +import itertools +import random +from random import randrange as rr +import unittest + + +class Skip(Exception): + pass + + +class Fuzzer(object): + """Fuzzes function input""" + def __init__(self, f, args, g=None, pass_good=None, pass_bad=None): + self.f = f + self.g = g + self.args = args + self.pass_good = pass_good + self.pass_bad = pass_bad + + @staticmethod + def get_random_int(): + return int((1<<rr(66)) * 1.3) + + @staticmethod + def get_random_bytes(lrange=None, skip=None): + if lrange is None: + v = bytearray(rr(2**rr(10))) + else: + v = bytearray(rr(*lrange)) + for i in range(len(v)): + v[i] = rr(256) + while v[i] == skip: + v[i] = rr(256) + return bytes(v) + + def get_good_args(self): + kwargs = {} + for a in self.args: + assert isinstance(a, dict) + if 'opt' in a and a['opt'] and random.randrange(2): + continue + if 'val' in a: + kwargs[a['name']] = a['val'] + elif 'vals' in a: + kwargs[a['name']] = random.choice(a['vals']) + elif 'valf' in a: + kwargs[a['name']] = a['valf']() + elif 'type' in a and a['type'] == 'int': + kwargs[a['name']] = self.get_random_int() + elif 'type' in a and a['type'] == 'bytes': + kwargs[a['name']] = self.get_random_bytes() + else: + raise ValueError + if 'none' in a and not random.randrange(10): + kwargs[a['name']] = None + if 'skip' in a and a['skip'](kwargs[a['name']]): + if 'opt' in a and a['opt']: + del kwargs[a['name']] + else: + raise Skip + return kwargs + + def get_bad_args(self, kwargs=None): + kwargs = kwargs or self.get_good_args() + a = random.choice(self.args) + if not 'opt' in a: + if not random.randrange(10): + del kwargs[a['name']] + return kwargs + if not 'type' in a: + return self.get_bad_args(kwargs) + + if not random.randrange(10): + wrongtype = [ + self.get_random_int(), self.get_random_bytes(), None, + 1.1*self.get_random_int(), 1.0*self.get_random_int() + ] + if a['type'] == 'int': + del wrongtype[0] + elif a['type'] == 'bytes': + del wrongtype[1] + v = random.choice(wrongtype) + try: + if 'valf' in a: + if a['valf'](v): + return self.get_bad_args(kwargs) + if 'skip' in a and a['skip'](v): + return self.get_bad_args(kwargs) + except TypeError: + pass # Surely bad enough + kwargs[a['name']] = v + return kwargs + + if a['type'] == 'int': + v = self.get_random_int() + if 'valf' in a: + if a['valf'](v): + return self.get_bad_args(kwargs) + if 'skip' in a and a['skip'](v): + return self.get_bad_args(kwargs) + kwargs[a['name']] = v + return kwargs + + if a['type'] == 'bytes' and 'valf' in a: + v = self.get_random_bytes() + if not a['valf'](v): + kwargs[a['name']] = v + return kwargs + + return self.get_bad_args(kwargs) + + def fuzz_good_run(self, tc): + try: + kwargs = self.get_good_args() + r1 = self.f(**kwargs) + r2 = self.g(**kwargs) if self.g is not None else None + if self.g is not None: + r2 = self.g(**kwargs) + except Skip: + tc.skipTest('slow') + except Exception as e: + assert False, ('unexpected exception', kwargs, e) + + try: + if self.pass_good: + tc.assertTrue(self.pass_good(r1, r2, kwargs), + msg=('unexpected output', r1, r2, kwargs)) + else: + if self.g is not None: + assert r1 == r2, ('f and g mismatch', kwargs, r1, r2) + tc.assertTrue(r1) + except Exception as e: + print ('unexpected exception', kwargs, r1, r2, e) + raise + + def fuzz_bad(self, f=None, kwargs=None): + f = f or self.f + kwargs = kwargs or self.get_bad_args() + return f(**kwargs) + + def fuzz_bad_run(self, tc): + try: + kwargs = self.get_bad_args() + except Skip: + tc.skipTest('slow') + for f in ((self.f,) if not self.g else (self.f, self.g)): + try: + r = self.fuzz_bad(f, kwargs) + assert False, ('no exception', kwargs, r) + except Skip: + tc.skipTest('slow') + except AssertionError: + raise + except Exception: + pass + + def testcase_good(self, tests=1, name='FuzzTestGood'): + testfs = {} + for i in range(tests): + testfs['test_fuzz_good_%d' % i] = lambda s: self.fuzz_good_run(s) + t = type(name, (unittest.TestCase,), testfs) + return t + + def testcase_bad(self, tests=1, name='FuzzTestBad'): + testfs = {} + for i in range(tests): + testfs['test_fuzz_bad_%d' % i] = lambda s: self.fuzz_bad_run(s) + t = type(name, (unittest.TestCase,), testfs) + return t + + def generate_tests(self, suite, count, name): + loader = unittest.defaultTestLoader + suite.addTest( + loader.loadTestsFromTestCase( + self.testcase_good(count, name + 'Good') + ) + ) + suite.addTest( + loader.loadTestsFromTestCase( + self.testcase_bad(count, name + 'Bad') + ) + ) + + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description='Fuzz testing') + parser.add_argument('-c', '--count', type=int, default=100) + parser.add_argument('-f', '--failfast', action='store_true') + clargs = parser.parse_args() + + modules = [] + try: + from . import pylibscrypt + modules.append((pylibscrypt, 'pylibscrypt')) + except ImportError: + pass + + try: + from . import pyscrypt + modules.append((pyscrypt, 'pyscrypt')) + except ImportError: + pass + + try: + from . import pylibsodium_salsa + modules.append((pylibsodium_salsa, 'pylibsodium_salsa')) + except ImportError: + pass + + try: + from . import pylibsodium + modules.append((pylibsodium, 'pylibsodium')) + except ImportError: + pass + + try: + from . import pypyscrypt_inline as pypyscrypt + modules.append((pypyscrypt, 'pypyscrypt')) + except ImportError: + pass + + scrypt_args = ( + {'name':'password', 'type':'bytes'}, + {'name':'salt', 'type':'bytes'}, + { + 'name':'N', 'type':'int', 'opt':False, + 'valf':(lambda N=None: 2**rr(1,6) if N is None else + 1 < N < 2**64 and not (N & (N - 1))), + 'skip':(lambda N: (N & (N - 1)) == 0 and N > 32 and N < 2**64) + }, + { + 'name':'r', 'type':'int', 'opt':True, + 'valf':(lambda r=None: rr(1, 16) if r is None else 0<r<2**30), + 'skip':(lambda r: r > 16 and r < 2**30) + }, + { + 'name':'p', 'type':'int', 'opt':True, + 'valf':(lambda p=None: rr(1, 16) if p is None else 0<p<2**30), + 'skip':(lambda p: p > 16 and p < 2**30) + }, + { + 'name':'olen', 'type':'int', 'opt':True, + 'valf':(lambda l=None: rr(1, 1000) if l is None else l >= 0), + 'skip':(lambda l: l < 0 or l > 1024) + }, + ) + + scrypt_mcf_args = ( + { + 'name':'password', 'type':'bytes', + 'valf':(lambda p=None: Fuzzer.get_random_bytes(skip=0) if p is None + else not b'\0' in p) + }, + { + 'name':'salt', 'type':'bytes', 'opt':False, 'none':True, + 'valf':(lambda s=None: Fuzzer.get_random_bytes((1,17)) if s is None + else 1 <= len(s) <= 16) + }, + { + 'name':'N', 'type':'int', 'opt':False, + 'valf':(lambda N=None: 2**rr(1,6) if N is None else + 1 < N < 2**64 and not (N & (N - 1))), + 'skip':(lambda N: (N & (N - 1)) == 0 and N > 32 and N < 2**64) + }, + { + 'name':'r', 'type':'int', 'opt':True, + 'valf':(lambda r=None: rr(1, 16) if r is None else 0<r<2**30), + 'skip':(lambda r: r > 16 and r < 2**30) + }, + { + 'name':'p', 'type':'int', 'opt':True, + 'valf':(lambda p=None: rr(1, 16) if p is None else 0<p<2**30), + 'skip':(lambda p: p > 16 and p < 2**30) + }, + { + 'name':'prefix', 'type':'bytes', 'opt':True, + 'vals':(b'$s1$', b'$7$'), + 'skip':(lambda p: p is None) + } + ) + + random.shuffle(modules) + suite = unittest.TestSuite() + loader = unittest.defaultTestLoader + for m1, m2 in itertools.permutations(modules, 2): + Fuzzer( + m1[0].scrypt, scrypt_args, m2[0].scrypt, + pass_good=lambda r1, r2, a: ( + isinstance(r1, bytes) and + (r2 is None or r1 == r2) and + (len(r1) == 64 if 'olen' not in a else len(r1) == a['olen']) + ) + ).generate_tests(suite, clargs.count, m1[1]) + Fuzzer( + m1[0].scrypt_mcf, scrypt_mcf_args, m2[0].scrypt_mcf, + pass_good=lambda r1, r2, a: ( + m2[0].scrypt_mcf_check(r1, a['password']) and + (r2 is None or m1[0].scrypt_mcf_check(r2, a['password'])) and + (r2 is None or 'salt' not in a or a['salt'] is None or r1 == r2) + ) + ).generate_tests(suite, clargs.count, m1[1]) + unittest.TextTestRunner(failfast=clargs.failfast).run(suite) + diff --git a/lib/pylibscrypt/inline.py b/lib/pylibscrypt/inline.py new file mode 100644 index 00000000..54e8f5c0 --- /dev/null +++ b/lib/pylibscrypt/inline.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python + +# Copyright (c) 2014, Jan Varho +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +"""Inlines the salsa20 core lines into salsa20_8""" + +of = open('pylibscrypt/pypyscrypt_inline.py', 'w') +assert of + +def indent(line): + i = 0 + while i < len(line) and line[i] == ' ': + i += 1 + return i + +with open('pylibscrypt/pypyscrypt.py', 'r') as f: + in_loop = False + loop_indent = 0 + lc = 0 + rl = [] + skipping = False + for line in f: + lc += 1 + i = indent(line) + if line[i:].startswith('def R('): + skipping = True + elif line[i:].startswith('def array_overwrite('): + skipping = True + elif skipping: + if line[i:].startswith('def'): + of.write(line) + skipping = False + + elif line[i:].startswith('R('): + parts = line.split(';') + rl += parts + if len(rl) == 32: + # Interleave to reduce dependencies for pypy + rl1 = rl[:16] + rl2 = rl[16:] + rl1 = rl1[0::4] + rl1[1::4] + rl1[2::4] + rl1[3::4] + rl2 = rl2[0::4] + rl2[1::4] + rl2[2::4] + rl2[3::4] + rl = rl1 + rl2 + for p, q in zip(rl[::2], rl[1::2]): + pvals = p.split(',')[1:] + pvals = [int(v.strip(' )\n')) for v in pvals] + qvals = q.split(',')[1:] + qvals = [int(v.strip(' )\n')) for v in qvals] + of.write(' '*i) + of.write('a = (x[%d]+x[%d]) & 0xffffffff\n' % + (pvals[1], pvals[2])) + of.write(' '*i) + of.write('b = (x[%d]+x[%d]) & 0xffffffff\n' % + (qvals[1], qvals[2])) + of.write(' '*i) + of.write('x[%d] ^= (a << %d) | (a >> %d)\n' % + (pvals[0], pvals[3], 32 - pvals[3])) + of.write(' '*i) + of.write('x[%d] ^= (b << %d) | (b >> %d)\n' % + (qvals[0], qvals[3], 32 - qvals[3])) + + elif line[i:].startswith('array_overwrite('): + vals = line.split(',') + vals[0] = vals[0].split('(')[1] + vals[-1] = vals[-1].split(')')[0] + vals = [v.strip() for v in vals] + assert len(vals) == 5 + of.write(' '*i) + of.write(vals[2] + '[' + vals[3] + ':(' + vals[3] + ')+(' + + vals[4] + ')] = ' + vals[0] + '[' + vals[1] + ':(' + + vals[1] + ')+(' + vals[4] + ')]\n') + + else: + of.write(line) + if lc == 1: + of.write('\n# Automatically generated file, see inline.py\n') + diff --git a/lib/pylibscrypt/mcf.py b/lib/pylibscrypt/mcf.py new file mode 100644 index 00000000..493ac942 --- /dev/null +++ b/lib/pylibscrypt/mcf.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python + +# Copyright (c) 2014, Jan Varho +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +"""Modular Crypt Format support for scrypt + +Compatible with libscrypt scrypt_mcf_check also supports the $7$ format. + +libscrypt format: +$s1$NNrrpp$salt$hash +NN - hex encoded N log2 (two hex digits) +rr - hex encoded r in 1-255 +pp - hex encoded p in 1-255 +salt - base64 encoded salt 1-16 bytes decoded +hash - base64 encoded 64-byte scrypt hash + +$7$ format: +$7$Nrrrrrpppppsalt$hash +N - crypt base64 N log2 +rrrrr - crypt base64 r (little-endian 30 bits) +ppppp - crypt base64 p (little-endian 30 bits) +salt - raw salt (0-43 bytes that should be limited to crypt base64) +hash - crypt base64 encoded 32-byte scrypt hash (43 bytes) + +(crypt base64 is base64 with the alphabet: ./0-9A-Za-z) + +When reading, we are more lax, allowing salts and hashes to be longer and +incorrectly encoded, since the worst that can happen is that the password does +not verify. +""" + + +import base64, binascii +import os +import struct + +from .common import * + + +def _scrypt_mcf_encode_s1(N, r, p, salt, hash): + h64 = base64.b64encode(hash) + s64 = base64.b64encode(salt) + + t = 1 + while 2**t < N: + t += 1 + params = p + (r << 8) + (t << 16) + + return ( + b'$s1' + + ('$%06x' % params).encode() + + b'$' + s64 + + b'$' + h64 + ) + + +def _b64decode(b64): + for b in (b64, b64 + b'=', b64 + b'=='): + try: + return base64.b64decode(b) + except (TypeError, binascii.Error): + pass + raise ValueError('Incorrect base64 in MCF') + + +def _scrypt_mcf_decode_s1(mcf): + s = mcf.split(b'$') + if not (mcf.startswith(b'$s1$') and len(s) == 5): + return None + + params, s64, h64 = s[2:] + params = base64.b16decode(params, True) + salt = _b64decode(s64) + hash = _b64decode(h64) + + if len(params) != 3: + raise ValueError('Unrecognized MCF parameters') + t, r, p = struct.unpack('3B', params) + N = 2 ** t + return N, r, p, salt, hash, len(hash) + + +# Crypt base 64 +_cb64 = b'./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' +_cb64a = bytearray(_cb64) +_icb64 = ( + [None] * 46 + + [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, None, None, None, None, None, + None, None, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, None, None, None, + None, None, None, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63 + ] + + [None] * 133 +) + + +def _cb64enc(arr): + arr = bytearray(arr) + out = bytearray() + val = bits = pos = 0 + for b in arr: + val += b << bits + bits += 8 + while bits >= 0: + out.append(_cb64a[val & 0x3f]) + bits -= 6 + val = val >> 6 + return bytes(out) + + +def _scrypt_mcf_encode_7(N, r, p, salt, hash): + t = 1 + while 2**t < N: + t += 1 + return ( + b'$7$' + + # N + _cb64[t::64] + + # r + _cb64[r & 0x3f::64] + _cb64[(r >> 6) & 0x3f::64] + + _cb64[(r >> 12) & 0x3f::64] + _cb64[(r >> 18) & 0x3f::64] + + _cb64[(r >> 24) & 0x3f::64] + + # p + _cb64[p & 0x3f::64] + _cb64[(p >> 6) & 0x3f::64] + + _cb64[(p >> 12) & 0x3f::64] + _cb64[(p >> 18) & 0x3f::64] + + _cb64[(p >> 24) & 0x3f::64] + + # rest + salt + + b'$' + _cb64enc(hash) + ) + + +def _cb64dec(arr): + out = bytearray() + val = bits = pos = 0 + for b in arr: + val += _icb64[b] << bits + bits += 6 + if bits >= 8: + out.append(val & 0xff) + bits -= 8 + val >>= 8 + return out + + +def _scrypt_mcf_decode_7(mcf): + s = mcf.split(b'$') + if not (mcf.startswith(b'$7$') and len(s) == 4): + return None + + s64 = bytearray(s[2]) + h64 = bytearray(s[3]) + try: + N = 2 ** _icb64[s64[0]] + r = (_icb64[s64[1]] + (_icb64[s64[2]] << 6) + (_icb64[s64[3]] << 12) + + (_icb64[s64[4]] << 18) + (_icb64[s64[5]] << 24)) + p = (_icb64[s64[6]] + (_icb64[s64[7]] << 6) + (_icb64[s64[8]] << 12) + + (_icb64[s64[9]] << 18) + (_icb64[s64[10]] << 24)) + salt = bytes(s64[11:]) + hash = bytes(_cb64dec(h64)) + except (IndexError, TypeError): + raise ValueError('Unrecognized MCF format') + + return N, r, p, salt, hash, len(hash) + + +def _scrypt_mcf_7_is_standard(mcf): + params = _scrypt_mcf_decode_7(mcf) + if params is None: + return False + N, r, p, salt, hash, hlen = params + return len(salt) == 43 and hlen == 32 + + +def _scrypt_mcf_decode(mcf): + params = _scrypt_mcf_decode_s1(mcf) + if params is None: + params = _scrypt_mcf_decode_7(mcf) + if params is None: + raise ValueError('Unrecognized MCF hash') + return params + + +def scrypt_mcf(scrypt, password, salt=None, N=SCRYPT_N, r=SCRYPT_r, p=SCRYPT_p, + prefix=SCRYPT_MCF_PREFIX_DEFAULT): + """Derives a Modular Crypt Format hash using the scrypt KDF given + + Expects the signature: + scrypt(password, salt, N=SCRYPT_N, r=SCRYPT_r, p=SCRYPT_p, olen=64) + + If no salt is given, a random salt of 128+ bits is used. (Recommended.) + """ + if salt is not None and not (1 <= len(salt) <= 16): + raise ValueError('salt must be 1-16 bytes') + if r > 255: + raise ValueError('scrypt_mcf r out of range [1,255]') + if p > 255: + raise ValueError('scrypt_mcf p out of range [1,255]') + if N > 2**31: + raise ValueError('scrypt_mcf N out of range [2,2**31]') + if b'\0' in password: + raise ValueError('scrypt_mcf password must not contain zero bytes') + + if prefix == SCRYPT_MCF_PREFIX_s1: + if salt is None: + salt = os.urandom(16) + hash = scrypt(password, salt, N, r, p) + return _scrypt_mcf_encode_s1(N, r, p, salt, hash) + elif prefix == SCRYPT_MCF_PREFIX_7 or prefix == SCRYPT_MCF_PREFIX_ANY: + if salt is None: + salt = os.urandom(32) + salt = _cb64enc(salt) + hash = scrypt(password, salt, N, r, p, 32) + return _scrypt_mcf_encode_7(N, r, p, salt, hash) + else: + raise ValueError("Unrecognized MCF format") + + +def scrypt_mcf_check(scrypt, mcf, password): + """Returns True if the password matches the given MCF hash + + Supports both the libscrypt $s1$ format and the $7$ format. + """ + if not isinstance(mcf, bytes): + raise TypeError + if not isinstance(password, bytes): + raise TypeError + + N, r, p, salt, hash, hlen = _scrypt_mcf_decode(mcf) + h = scrypt(password, salt, N=N, r=r, p=p, olen=hlen) + return hash == h + diff --git a/lib/pylibscrypt/pbkdf2.py b/lib/pylibscrypt/pbkdf2.py new file mode 100644 index 00000000..bedd5d62 --- /dev/null +++ b/lib/pylibscrypt/pbkdf2.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python + +# Copyright (c) 2014, Jan Varho +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +"""PBKDF2 in pure Python, compatible with Python3.4 hashlib.pbkdf2_hmac""" + + +import hashlib +import hmac +import struct + +from .common import * + + +def pbkdf2_hmac(name, password, salt, rounds, dklen=None): + """Returns the result of the Password-Based Key Derivation Function 2""" + h = hmac.new(key=password, digestmod=lambda d=b'': hashlib.new(name, d)) + hs = h.copy() + hs.update(salt) + + blocks = bytearray() + dklen = hs.digest_size if dklen is None else dklen + block_count, last_size = divmod(dklen, hs.digest_size) + block_count += last_size > 0 + + for block_number in xrange(1, block_count + 1): + hb = hs.copy() + hb.update(struct.pack('>L', block_number)) + U = bytearray(hb.digest()) + + if rounds > 1: + Ui = U + for i in xrange(rounds - 1): + hi = h.copy() + hi.update(Ui) + Ui = bytearray(hi.digest()) + for j in xrange(hs.digest_size): + U[j] ^= Ui[j] + + blocks.extend(U) + + if last_size: + del blocks[dklen:] + return bytes(blocks) + +if __name__ == "__main__": + import sys + from . import tests + tests.run_pbkdf2_suite(sys.modules[__name__]) + diff --git a/lib/pylibscrypt/pylibscrypt.py b/lib/pylibscrypt/pylibscrypt.py new file mode 100644 index 00000000..3bbc3fb1 --- /dev/null +++ b/lib/pylibscrypt/pylibscrypt.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python + +# Copyright (c) 2014, Jan Varho +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +"""Scrypt implementation that calls into system libscrypt""" + + +import base64 +import ctypes, ctypes.util +from ctypes import c_char_p, c_size_t, c_uint64, c_uint32 +import os + +from .common import * +from . import mcf as mcf_mod + + +_libscrypt_soname = ctypes.util.find_library('scrypt') +if _libscrypt_soname is None: + raise ImportError('Unable to find libscrypt') + +try: + _libscrypt = ctypes.CDLL(_libscrypt_soname) + _libscrypt_scrypt = _libscrypt.libscrypt_scrypt + _libscrypt_mcf = _libscrypt.libscrypt_mcf + _libscrypt_check = _libscrypt.libscrypt_check +except OSError: + raise ImportError('Unable to load libscrypt: ' + _libscrypt_soname) +except AttributeError: + raise ImportError('Incompatible libscrypt: ' + _libscrypt_soname) + +_libscrypt_scrypt.argtypes = [ + c_char_p, # password + c_size_t, # password length + c_char_p, # salt + c_size_t, # salt length + c_uint64, # N + c_uint32, # r + c_uint32, # p + c_char_p, # out + c_size_t, # out length +] + +_libscrypt_mcf.argtypes = [ + c_uint64, # N + c_uint32, # r + c_uint32, # p + c_char_p, # salt + c_char_p, # hash + c_char_p, # out (125+ bytes) +] + +_libscrypt_check.argtypes = [ + c_char_p, # mcf (modified) + c_char_p, # hash +] + + +def scrypt(password, salt, N=SCRYPT_N, r=SCRYPT_r, p=SCRYPT_p, olen=64): + """Returns a key derived using the scrypt key-derivarion function + + N must be a power of two larger than 1 but no larger than 2 ** 63 (insane) + r and p must be positive numbers such that r * p < 2 ** 30 + + The default values are: + N -- 2**14 (~16k) + r -- 8 + p -- 1 + + Memory usage is proportional to N*r. Defaults require about 16 MiB. + Time taken is proportional to N*p. Defaults take <100ms of a recent x86. + + The last one differs from libscrypt defaults, but matches the 'interactive' + work factor from the original paper. For long term storage where runtime of + key derivation is not a problem, you could use 16 as in libscrypt or better + yet increase N if memory is plentiful. + """ + check_args(password, salt, N, r, p, olen) + + out = ctypes.create_string_buffer(olen) + ret = _libscrypt_scrypt(password, len(password), salt, len(salt), + N, r, p, out, len(out)) + if ret: + raise ValueError + + return out.raw + + +def scrypt_mcf(password, salt=None, N=SCRYPT_N, r=SCRYPT_r, p=SCRYPT_p, + prefix=SCRYPT_MCF_PREFIX_DEFAULT): + """Derives a Modular Crypt Format hash using the scrypt KDF + + Parameter space is smaller than for scrypt(): + N must be a power of two larger than 1 but no larger than 2 ** 31 + r and p must be positive numbers between 1 and 255 + Salt must be a byte string 1-16 bytes long. + + If no salt is given, a random salt of 128+ bits is used. (Recommended.) + """ + if (prefix != SCRYPT_MCF_PREFIX_s1 and prefix != SCRYPT_MCF_PREFIX_ANY): + return mcf_mod.scrypt_mcf(scrypt, password, salt, N, r, p, prefix) + if salt is None: + salt = os.urandom(16) + elif not (1 <= len(salt) <= 16): + raise ValueError('salt must be 1-16 bytes') + if N > 2**31: + raise ValueError('N > 2**31 not supported') + if b'\0' in password: + raise ValueError('scrypt_mcf password must not contain zero bytes') + + hash = scrypt(password, salt, N, r, p) + + h64 = base64.b64encode(hash) + s64 = base64.b64encode(salt) + + out = ctypes.create_string_buffer(125) + ret = _libscrypt_mcf(N, r, p, s64, h64, out) + if not ret: + raise ValueError + + out = out.raw.strip(b'\0') + # XXX: Hack to support old libscrypt (like in Ubuntu 14.04) + if len(out) == 123: + out = out + b'=' + + return out + + +def scrypt_mcf_check(mcf, password): + """Returns True if the password matches the given MCF hash""" + if not isinstance(mcf, bytes): + raise TypeError + if not isinstance(password, bytes): + raise TypeError + if len(mcf) != 124 or b'\0' in password: + return mcf_mod.scrypt_mcf_check(scrypt, mcf, password) + + mcfbuf = ctypes.create_string_buffer(mcf) + ret = _libscrypt_check(mcfbuf, password) + if ret < 0: + return mcf_mod.scrypt_mcf_check(scrypt, mcf, password) + + return bool(ret) + + +if __name__ == "__main__": + import sys + from . import tests + tests.run_scrypt_suite(sys.modules[__name__]) + diff --git a/lib/pylibscrypt/pylibsodium.py b/lib/pylibscrypt/pylibsodium.py new file mode 100644 index 00000000..bf6949e8 --- /dev/null +++ b/lib/pylibscrypt/pylibsodium.py @@ -0,0 +1,252 @@ +#!/usr/bin/env python + +# Copyright (c) 2014, Jan Varho +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +"""Scrypt implementation that calls into system libsodium""" + + +import base64 +import ctypes, ctypes.util +from ctypes import c_char_p, c_size_t, c_uint64, c_uint32, c_void_p +import hashlib, hmac +import numbers +import platform +import struct +import sys + +from . import mcf as mcf_mod +from .common import * + +if platform.python_implementation() == 'PyPy': + from . import pypyscrypt_inline as scr_mod +else: + from . import pylibsodium_salsa as scr_mod + + +def _get_libsodium(): + ''' + Locate the nacl c libs to use + ''' + + __SONAMES = (13, 10, 5, 4) + # Import libsodium from system + sys_sodium = ctypes.util.find_library('sodium') + if sys_sodium is None: + sys_sodium = ctypes.util.find_library('libsodium') + + if sys_sodium: + return ctypes.CDLL(sys_sodium) + + # Import from local path + if sys.platform.startswith('win'): + + try: + return ctypes.cdll.LoadLibrary('libsodium') + except OSError: + pass + for soname_ver in __SONAMES: + try: + return ctypes.cdll.LoadLibrary( + 'libsodium-{0}'.format(soname_ver) + ) + except OSError: + pass + elif sys.platform.startswith('darwin'): + try: + return ctypes.cdll.LoadLibrary('libsodium.dylib') + except OSError: + pass + else: + try: + return ctypes.cdll.LoadLibrary('libsodium.so') + except OSError: + pass + + for soname_ver in __SONAMES: + try: + return ctypes.cdll.LoadLibrary( + 'libsodium.so.{0}'.format(soname_ver) + ) + except OSError: + pass + + +_lib = _get_libsodium() +if _lib is None: + raise ImportError('Unable to load libsodium') + +try: + _scrypt_ll = _lib.crypto_pwhash_scryptsalsa208sha256_ll + _scrypt_ll.argtypes = [ + c_void_p, # passwd + c_size_t, # passwdlen + c_void_p, # salt + c_size_t, # saltlen + c_uint64, # N + c_uint32, # r + c_uint32, # p + c_void_p, # buf + c_size_t, # buflen + ] +except AttributeError: + _scrypt_ll = None + +try: + _scrypt = _lib.crypto_pwhash_scryptsalsa208sha256 + _scrypt_str = _lib.crypto_pwhash_scryptsalsa208sha256_str + _scrypt_str_chk = _lib.crypto_pwhash_scryptsalsa208sha256_str_verify + _scrypt_str_bytes = _lib.crypto_pwhash_scryptsalsa208sha256_strbytes() + _scrypt_salt = _lib.crypto_pwhash_scryptsalsa208sha256_saltbytes() + if _scrypt_str_bytes != 102 and not _scrypt_ll: + raise ImportError('Incompatible libsodium: ' + _lib_soname) +except AttributeError: + try: + _scrypt = _lib.crypto_pwhash_scryptxsalsa208sha256 + _scrypt_str = _lib.crypto_pwhash_scryptxsalsa208sha256_str + _scrypt_str_chk = _lib.crypto_pwhash_scryptxsalsa208sha256_str_verify + _scrypt_str_bytes = _lib.crypto_pwhash_scryptxsalsa208sha256_strbytes() + _scrypt_salt = _lib.crypto_pwhash_scryptxsalsa208sha256_saltbytes + _scrypt_salt = _scrypt_salt() + if _scrypt_str_bytes != 102 and not _scrypt_ll: + raise ImportError('Incompatible libsodium: ' + _lib_soname) + except AttributeError: + if not _scrypt_ll: + raise ImportError('Incompatible libsodium: ' + _lib_soname) + +_scrypt.argtypes = [ + c_void_p, # out + c_uint64, # outlen + c_void_p, # passwd + c_uint64, # passwdlen + c_void_p, # salt + c_uint64, # opslimit + c_size_t, # memlimit +] + +_scrypt_str.argtypes = [ + c_void_p, # out (102 bytes) + c_void_p, # passwd + c_uint64, # passwdlen + c_uint64, # opslimit + c_size_t, # memlimit +] + +_scrypt_str_chk.argtypes = [ + c_char_p, # str (102 bytes) + c_void_p, # passwd + c_uint64, # passwdlen +] + + +def scrypt(password, salt, N=SCRYPT_N, r=SCRYPT_r, p=SCRYPT_p, olen=64): + """Returns a key derived using the scrypt key-derivarion function + + N must be a power of two larger than 1 but no larger than 2 ** 63 (insane) + r and p must be positive numbers such that r * p < 2 ** 30 + + The default values are: + N -- 2**14 (~16k) + r -- 8 + p -- 1 + + Memory usage is proportional to N*r. Defaults require about 16 MiB. + Time taken is proportional to N*p. Defaults take <100ms of a recent x86. + + The last one differs from libscrypt defaults, but matches the 'interactive' + work factor from the original paper. For long term storage where runtime of + key derivation is not a problem, you could use 16 as in libscrypt or better + yet increase N if memory is plentiful. + """ + check_args(password, salt, N, r, p, olen) + + if _scrypt_ll: + out = ctypes.create_string_buffer(olen) + if _scrypt_ll(password, len(password), salt, len(salt), + N, r, p, out, olen): + raise ValueError + return out.raw + + if len(salt) != _scrypt_salt or r != 8 or (p & (p - 1)) or (N*p <= 512): + return scr_mod.scrypt(password, salt, N, r, p, olen) + + s = next(i for i in range(1, 64) if 2**i == N) + t = next(i for i in range(0, 30) if 2**i == p) + m = 2**(10 + s) + o = 2**(5 + t + s) + if s > 53 or t + s > 58: + raise ValueError + out = ctypes.create_string_buffer(olen) + if _scrypt(out, olen, password, len(password), salt, o, m) != 0: + raise ValueError + return out.raw + + +def scrypt_mcf(password, salt=None, N=SCRYPT_N, r=SCRYPT_r, p=SCRYPT_p, + prefix=SCRYPT_MCF_PREFIX_DEFAULT): + """Derives a Modular Crypt Format hash using the scrypt KDF + + Parameter space is smaller than for scrypt(): + N must be a power of two larger than 1 but no larger than 2 ** 31 + r and p must be positive numbers between 1 and 255 + Salt must be a byte string 1-16 bytes long. + + If no salt is given, a random salt of 128+ bits is used. (Recommended.) + """ + if N < 2 or (N & (N - 1)): + raise ValueError('scrypt N must be a power of 2 greater than 1') + if p > 255 or p < 1: + raise ValueError('scrypt_mcf p out of range [1,255]') + if N > 2**31: + raise ValueError('scrypt_mcf N out of range [2,2**31]') + + if (salt is not None or r != 8 or (p & (p - 1)) or (N*p <= 512) or + prefix not in (SCRYPT_MCF_PREFIX_7, SCRYPT_MCF_PREFIX_s1, + SCRYPT_MCF_PREFIX_ANY) or + _scrypt_ll): + return mcf_mod.scrypt_mcf(scrypt, password, salt, N, r, p, prefix) + + s = next(i for i in range(1, 32) if 2**i == N) + t = next(i for i in range(0, 8) if 2**i == p) + m = 2**(10 + s) + o = 2**(5 + t + s) + mcf = ctypes.create_string_buffer(102) + if _scrypt_str(mcf, password, len(password), o, m) != 0: + return mcf_mod.scrypt_mcf(scrypt, password, salt, N, r, p, prefix) + + if prefix in (SCRYPT_MCF_PREFIX_7, SCRYPT_MCF_PREFIX_ANY): + return mcf.raw.strip(b'\0') + + _N, _r, _p, salt, hash, olen = mcf_mod._scrypt_mcf_decode_7(mcf.raw[:-1]) + assert _N == N and _r == r and _p == p, (_N, _r, _p, N, r, p, o, m) + return mcf_mod._scrypt_mcf_encode_s1(N, r, p, salt, hash) + + +def scrypt_mcf_check(mcf, password): + """Returns True if the password matches the given MCF hash""" + if mcf_mod._scrypt_mcf_7_is_standard(mcf) and not _scrypt_ll: + return _scrypt_str_chk(mcf, password, len(password)) == 0 + return mcf_mod.scrypt_mcf_check(scrypt, mcf, password) + + +if __name__ == "__main__": + import sys + from . import tests + try: + from . import pylibscrypt + scr_mod = pylibscrypt + except ImportError: + pass + tests.run_scrypt_suite(sys.modules[__name__]) + diff --git a/lib/pylibscrypt/pylibsodium_salsa.py b/lib/pylibscrypt/pylibsodium_salsa.py new file mode 100644 index 00000000..34057e63 --- /dev/null +++ b/lib/pylibscrypt/pylibsodium_salsa.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python + +# Copyright (c) 2014 Richard Moore +# Copyright (c) 2014 Jan Varho +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +"""Scrypt implementation that calls into system libsodium""" + + +import base64 +import ctypes, ctypes.util +from ctypes import c_char_p, c_size_t, c_uint64, c_uint32, c_void_p +import hashlib, hmac +import numbers +import struct +import sys + +from . import mcf as mcf_mod +from .common import * + + +def _get_libsodium(): + ''' + Locate the nacl c libs to use + ''' + + __SONAMES = (13, 10, 5, 4) + # Import libsodium from system + sys_sodium = ctypes.util.find_library('sodium') + if sys_sodium is None: + sys_sodium = ctypes.util.find_library('libsodium') + + if sys_sodium: + return ctypes.CDLL(sys_sodium) + + # Import from local path + if sys.platform.startswith('win'): + try: + return ctypes.cdll.LoadLibrary('libsodium') + except OSError: + pass + for soname_ver in __SONAMES: + try: + return ctypes.cdll.LoadLibrary( + 'libsodium-{0}'.format(soname_ver) + ) + except OSError: + pass + elif sys.platform.startswith('darwin'): + try: + return ctypes.cdll.LoadLibrary('libsodium.dylib') + except OSError: + pass + else: + try: + return ctypes.cdll.LoadLibrary('libsodium.so') + except OSError: + pass + + for soname_ver in __SONAMES: + try: + return ctypes.cdll.LoadLibrary( + 'libsodium.so.{0}'.format(soname_ver) + ) + except OSError: + pass + + +_libsodium = _get_libsodium() +if _libsodium is None: + raise ImportError('Unable to load libsodium') + +try: + _libsodium_salsa20_8 = _libsodium.crypto_core_salsa208 +except AttributeError: + raise ImportError('Incompatible libsodium: ') + +_libsodium_salsa20_8.argtypes = [ + c_void_p, # out (16*4 bytes) + c_void_p, # in (4*4 bytes) + c_void_p, # k (8*4 bytes) + c_void_p, # c (4*4 bytes) +] + + +# Python 3.4+ have PBKDF2 in hashlib, so use it... +if 'pbkdf2_hmac' in dir(hashlib): + _pbkdf2 = hashlib.pbkdf2_hmac +else: + # but fall back to Python implementation in < 3.4 + from pbkdf2 import pbkdf2_hmac as _pbkdf2 + + +def scrypt(password, salt, N=SCRYPT_N, r=SCRYPT_r, p=SCRYPT_p, olen=64): + """Returns a key derived using the scrypt key-derivarion function + + N must be a power of two larger than 1 but no larger than 2 ** 63 (insane) + r and p must be positive numbers such that r * p < 2 ** 30 + + The default values are: + N -- 2**14 (~16k) + r -- 8 + p -- 1 + + Memory usage is proportional to N*r. Defaults require about 16 MiB. + Time taken is proportional to N*p. Defaults take <100ms of a recent x86. + + The last one differs from libscrypt defaults, but matches the 'interactive' + work factor from the original paper. For long term storage where runtime of + key derivation is not a problem, you could use 16 as in libscrypt or better + yet increase N if memory is plentiful. + """ + def array_overwrite(source, s_start, dest, d_start, length): + dest[d_start:d_start + length] = source[s_start:s_start + length] + + + def blockxor(source, s_start, dest, d_start, length): + for i in xrange(length): + dest[d_start + i] ^= source[s_start + i] + + + def integerify(B, r): + """A bijection from ({0, 1} ** k) to {0, ..., (2 ** k) - 1""" + + Bi = (2 * r - 1) * 8 + return B[Bi] & 0xffffffff + + + def salsa20_8(B, x): + """Salsa 20/8 using libsodium + + NaCL/libsodium includes crypto_core_salsa208, but unfortunately it + expects the data in a different order, so we need to mix it up a bit. + """ + hi = 0xffffffff00000000 + lo = 0x00000000ffffffff + struct.pack_into('<9Q', x, 0, + (B[0] & lo) + (B[2] & hi), (B[5] & lo) + (B[7] & hi), # c + B[3], B[4], # in + B[0], B[1], (B[2] & lo) + (B[5] & hi), # pad k pad + B[6], B[7], + ) + + c = ctypes.addressof(x) + i = c + 4*4 + k = c + 9*4 + + _libsodium_salsa20_8(c, i, k, c) + + B[:] = struct.unpack('<8Q8x', x) + + + def blockmix_salsa8(BY, Yi, r): + """Blockmix; Used by SMix""" + + start = (2 * r - 1) * 8 + X = BY[start:start+8] # BlockMix - 1 + x = ctypes.create_string_buffer(8*9) + + for i in xrange(2 * r): # BlockMix - 2 + blockxor(BY, i * 8, X, 0, 8) # BlockMix - 3(inner) + salsa20_8(X, x) # BlockMix - 3(outer) + array_overwrite(X, 0, BY, Yi + (i * 8), 8) # BlockMix - 4 + + for i in xrange(r): # BlockMix - 6 + array_overwrite(BY, Yi + (i * 2) * 8, BY, i * 8, 8) + array_overwrite(BY, Yi + (i*2 + 1) * 8, BY, (i + r) * 8, 8) + + + def smix(B, Bi, r, N, V, X): + """SMix; a specific case of ROMix based on Salsa20/8""" + + array_overwrite(B, Bi, X, 0, 16 * r) # ROMix - 1 + + for i in xrange(N): # ROMix - 2 + array_overwrite(X, 0, V, i * (16 * r), 16 * r) # ROMix - 3 + blockmix_salsa8(X, 16 * r, r) # ROMix - 4 + + for i in xrange(N): # ROMix - 6 + j = integerify(X, r) & (N - 1) # ROMix - 7 + blockxor(V, j * (16 * r), X, 0, 16 * r) # ROMix - 8(inner) + blockmix_salsa8(X, 16 * r, r) # ROMix - 9(outer) + + array_overwrite(X, 0, B, Bi, 16 * r) # ROMix - 10 + + check_args(password, salt, N, r, p, olen) + + # Everything is lists of 64-bit uints for all but pbkdf2 + try: + B = _pbkdf2('sha256', password, salt, 1, p * 128 * r) + B = list(struct.unpack('<%dQ' % (len(B) // 8), B)) + XY = [0] * (32 * r) + V = [0] * (16 * r * N) + except (MemoryError, OverflowError): + raise ValueError("scrypt parameters don't fit in memory") + + for i in xrange(p): + smix(B, i * 16 * r, r, N, V, XY) + + B = struct.pack('<%dQ' % len(B), *B) + return _pbkdf2('sha256', password, B, 1, olen) + + +def scrypt_mcf(password, salt=None, N=SCRYPT_N, r=SCRYPT_r, p=SCRYPT_p, + prefix=b'$s1$'): + """Derives a Modular Crypt Format hash using the scrypt KDF + + Parameter space is smaller than for scrypt(): + N must be a power of two larger than 1 but no larger than 2 ** 31 + r and p must be positive numbers between 1 and 255 + Salt must be a byte string 1-16 bytes long. + + If no salt is given, a random salt of 128+ bits is used. (Recommended.) + """ + return mcf_mod.scrypt_mcf(scrypt, password, salt, N, r, p, prefix) + + +def scrypt_mcf_check(mcf, password): + """Returns True if the password matches the given MCF hash""" + return mcf_mod.scrypt_mcf_check(scrypt, mcf, password) + + +if __name__ == "__main__": + import sys + from . import tests + tests.run_scrypt_suite(sys.modules[__name__]) + diff --git a/lib/pylibscrypt/pypyscrypt.py b/lib/pylibscrypt/pypyscrypt.py new file mode 100644 index 00000000..4b088e12 --- /dev/null +++ b/lib/pylibscrypt/pypyscrypt.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python + +# Copyright (c) 2014 Richard Moore +# Copyright (c) 2014 Jan Varho +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +"""Python implementation of Scrypt password-based key derivation function""" + +# Scrypt definition: +# http://www.tarsnap.com/scrypt/scrypt.pdf + +# It was originally written for a pure-Python Litecoin CPU miner: +# https://github.com/ricmoo/nightminer +# Imported to this project from: +# https://github.com/ricmoo/pyscrypt +# And owes thanks to: +# https://github.com/wg/scrypt + + +import hashlib, hmac +import struct + +from . import mcf as mcf_mod +from .common import * + + +# Python 3.4+ have PBKDF2 in hashlib, so use it... +if 'pbkdf2_hmac' in dir(hashlib): + _pbkdf2 = hashlib.pbkdf2_hmac +else: + # but fall back to Python implementation in < 3.4 + from pbkdf2 import pbkdf2_hmac as _pbkdf2 + + +def array_overwrite(source, s_start, dest, d_start, length): + dest[d_start:d_start + length] = source[s_start:s_start + length] + + +def blockxor(source, s_start, dest, d_start, length): + for i in xrange(length): + dest[d_start + i] ^= source[s_start + i] + + +def integerify(B, r): + """A bijection from ({0, 1} ** k) to {0, ..., (2 ** k) - 1""" + + Bi = (2 * r - 1) * 16 + return B[Bi] + + +def R(X, destination, a1, a2, b): + """A single Salsa20 row operation""" + + a = (X[a1] + X[a2]) & 0xffffffff + X[destination] ^= ((a << b) | (a >> (32 - b))) + + +def salsa20_8(B, x, src, s_start, dest, d_start): + """Salsa20/8 http://en.wikipedia.org/wiki/Salsa20""" + + # Merged blockxor for speed + for i in xrange(16): + x[i] = B[i] = B[i] ^ src[s_start + i] + + # This is the actual Salsa 20/8: four identical double rounds + for i in xrange(4): + R(x, 4, 0,12, 7);R(x, 8, 4, 0, 9);R(x,12, 8, 4,13);R(x, 0,12, 8,18) + R(x, 9, 5, 1, 7);R(x,13, 9, 5, 9);R(x, 1,13, 9,13);R(x, 5, 1,13,18) + R(x,14,10, 6, 7);R(x, 2,14,10, 9);R(x, 6, 2,14,13);R(x,10, 6, 2,18) + R(x, 3,15,11, 7);R(x, 7, 3,15, 9);R(x,11, 7, 3,13);R(x,15,11, 7,18) + R(x, 1, 0, 3, 7);R(x, 2, 1, 0, 9);R(x, 3, 2, 1,13);R(x, 0, 3, 2,18) + R(x, 6, 5, 4, 7);R(x, 7, 6, 5, 9);R(x, 4, 7, 6,13);R(x, 5, 4, 7,18) + R(x,11,10, 9, 7);R(x, 8,11,10, 9);R(x, 9, 8,11,13);R(x,10, 9, 8,18) + R(x,12,15,14, 7);R(x,13,12,15, 9);R(x,14,13,12,13);R(x,15,14,13,18) + + # While we are handling the data, write it to the correct dest. + # The latter half is still part of salsa20 + for i in xrange(16): + dest[d_start + i] = B[i] = (x[i] + B[i]) & 0xffffffff + + +def blockmix_salsa8(BY, Yi, r): + """Blockmix; Used by SMix""" + + start = (2 * r - 1) * 16 + X = BY[start:start+16] # BlockMix - 1 + tmp = [0]*16 + + for i in xrange(2 * r): # BlockMix - 2 + #blockxor(BY, i * 16, X, 0, 16) # BlockMix - 3(inner) + salsa20_8(X, tmp, BY, i * 16, BY, Yi + i*16) # BlockMix - 3(outer) + #array_overwrite(X, 0, BY, Yi + (i * 16), 16) # BlockMix - 4 + + for i in xrange(r): # BlockMix - 6 + array_overwrite(BY, Yi + (i * 2) * 16, BY, i * 16, 16) + array_overwrite(BY, Yi + (i*2 + 1) * 16, BY, (i + r) * 16, 16) + + +def smix(B, Bi, r, N, V, X): + """SMix; a specific case of ROMix based on Salsa20/8""" + + array_overwrite(B, Bi, X, 0, 32 * r) # ROMix - 1 + + for i in xrange(N): # ROMix - 2 + array_overwrite(X, 0, V, i * (32 * r), 32 * r) # ROMix - 3 + blockmix_salsa8(X, 32 * r, r) # ROMix - 4 + + for i in xrange(N): # ROMix - 6 + j = integerify(X, r) & (N - 1) # ROMix - 7 + blockxor(V, j * (32 * r), X, 0, 32 * r) # ROMix - 8(inner) + blockmix_salsa8(X, 32 * r, r) # ROMix - 9(outer) + + array_overwrite(X, 0, B, Bi, 32 * r) # ROMix - 10 + + +def scrypt(password, salt, N=SCRYPT_N, r=SCRYPT_r, p=SCRYPT_p, olen=64): + """Returns a key derived using the scrypt key-derivarion function + + N must be a power of two larger than 1 but no larger than 2 ** 63 (insane) + r and p must be positive numbers such that r * p < 2 ** 30 + + The default values are: + N -- 2**14 (~16k) + r -- 8 + p -- 1 + + Memory usage is proportional to N*r. Defaults require about 16 MiB. + Time taken is proportional to N*p. Defaults take <100ms of a recent x86. + + The last one differs from libscrypt defaults, but matches the 'interactive' + work factor from the original paper. For long term storage where runtime of + key derivation is not a problem, you could use 16 as in libscrypt or better + yet increase N if memory is plentiful. + """ + + check_args(password, salt, N, r, p, olen) + + # Everything is lists of 32-bit uints for all but pbkdf2 + try: + B = _pbkdf2('sha256', password, salt, 1, p * 128 * r) + B = list(struct.unpack('<%dI' % (len(B) // 4), B)) + XY = [0] * (64 * r) + V = [0] * (32 * r * N) + except (MemoryError, OverflowError): + raise ValueError("scrypt parameters don't fit in memory") + + for i in xrange(p): + smix(B, i * 32 * r, r, N, V, XY) + + B = struct.pack('<%dI' % len(B), *B) + return _pbkdf2('sha256', password, B, 1, olen) + + +def scrypt_mcf(password, salt=None, N=SCRYPT_N, r=SCRYPT_r, p=SCRYPT_p, + prefix=SCRYPT_MCF_PREFIX_DEFAULT): + """Derives a Modular Crypt Format hash using the scrypt KDF + + Parameter space is smaller than for scrypt(): + N must be a power of two larger than 1 but no larger than 2 ** 31 + r and p must be positive numbers between 1 and 255 + Salt must be a byte string 1-16 bytes long. + + If no salt is given, a random salt of 128+ bits is used. (Recommended.) + """ + return mcf_mod.scrypt_mcf(scrypt, password, salt, N, r, p, prefix) + + +def scrypt_mcf_check(mcf, password): + """Returns True if the password matches the given MCF hash""" + return mcf_mod.scrypt_mcf_check(scrypt, mcf, password) + + +__all__ = ['scrypt', 'scrypt_mcf', 'scrypt_mcf_check'] + + +if __name__ == "__main__": + import sys + from . import tests + tests.run_scrypt_suite(sys.modules[__name__]) + diff --git a/lib/pylibscrypt/pypyscrypt_inline.py b/lib/pylibscrypt/pypyscrypt_inline.py new file mode 100644 index 00000000..75f497e9 --- /dev/null +++ b/lib/pylibscrypt/pypyscrypt_inline.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python + +# Automatically generated file, see inline.py + +# Copyright (c) 2014 Richard Moore +# Copyright (c) 2014 Jan Varho +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +"""Python implementation of Scrypt password-based key derivation function""" + +# Scrypt definition: +# http://www.tarsnap.com/scrypt/scrypt.pdf + +# It was originally written for a pure-Python Litecoin CPU miner: +# https://github.com/ricmoo/nightminer +# Imported to this project from: +# https://github.com/ricmoo/pyscrypt +# And owes thanks to: +# https://github.com/wg/scrypt + + +import hashlib, hmac +import struct + +from . import mcf as mcf_mod +from .common import * + + +# Python 3.4+ have PBKDF2 in hashlib, so use it... +if 'pbkdf2_hmac' in dir(hashlib): + _pbkdf2 = hashlib.pbkdf2_hmac +else: + # but fall back to Python implementation in < 3.4 + from pbkdf2 import pbkdf2_hmac as _pbkdf2 + + +def blockxor(source, s_start, dest, d_start, length): + for i in xrange(length): + dest[d_start + i] ^= source[s_start + i] + + +def integerify(B, r): + """A bijection from ({0, 1} ** k) to {0, ..., (2 ** k) - 1""" + + Bi = (2 * r - 1) * 16 + return B[Bi] + + +def salsa20_8(B, x, src, s_start, dest, d_start): + """Salsa20/8 http://en.wikipedia.org/wiki/Salsa20""" + + # Merged blockxor for speed + for i in xrange(16): + x[i] = B[i] = B[i] ^ src[s_start + i] + + # This is the actual Salsa 20/8: four identical double rounds + for i in xrange(4): + a = (x[0]+x[12]) & 0xffffffff + b = (x[5]+x[1]) & 0xffffffff + x[4] ^= (a << 7) | (a >> 25) + x[9] ^= (b << 7) | (b >> 25) + a = (x[10]+x[6]) & 0xffffffff + b = (x[15]+x[11]) & 0xffffffff + x[14] ^= (a << 7) | (a >> 25) + x[3] ^= (b << 7) | (b >> 25) + a = (x[4]+x[0]) & 0xffffffff + b = (x[9]+x[5]) & 0xffffffff + x[8] ^= (a << 9) | (a >> 23) + x[13] ^= (b << 9) | (b >> 23) + a = (x[14]+x[10]) & 0xffffffff + b = (x[3]+x[15]) & 0xffffffff + x[2] ^= (a << 9) | (a >> 23) + x[7] ^= (b << 9) | (b >> 23) + a = (x[8]+x[4]) & 0xffffffff + b = (x[13]+x[9]) & 0xffffffff + x[12] ^= (a << 13) | (a >> 19) + x[1] ^= (b << 13) | (b >> 19) + a = (x[2]+x[14]) & 0xffffffff + b = (x[7]+x[3]) & 0xffffffff + x[6] ^= (a << 13) | (a >> 19) + x[11] ^= (b << 13) | (b >> 19) + a = (x[12]+x[8]) & 0xffffffff + b = (x[1]+x[13]) & 0xffffffff + x[0] ^= (a << 18) | (a >> 14) + x[5] ^= (b << 18) | (b >> 14) + a = (x[6]+x[2]) & 0xffffffff + b = (x[11]+x[7]) & 0xffffffff + x[10] ^= (a << 18) | (a >> 14) + x[15] ^= (b << 18) | (b >> 14) + a = (x[0]+x[3]) & 0xffffffff + b = (x[5]+x[4]) & 0xffffffff + x[1] ^= (a << 7) | (a >> 25) + x[6] ^= (b << 7) | (b >> 25) + a = (x[10]+x[9]) & 0xffffffff + b = (x[15]+x[14]) & 0xffffffff + x[11] ^= (a << 7) | (a >> 25) + x[12] ^= (b << 7) | (b >> 25) + a = (x[1]+x[0]) & 0xffffffff + b = (x[6]+x[5]) & 0xffffffff + x[2] ^= (a << 9) | (a >> 23) + x[7] ^= (b << 9) | (b >> 23) + a = (x[11]+x[10]) & 0xffffffff + b = (x[12]+x[15]) & 0xffffffff + x[8] ^= (a << 9) | (a >> 23) + x[13] ^= (b << 9) | (b >> 23) + a = (x[2]+x[1]) & 0xffffffff + b = (x[7]+x[6]) & 0xffffffff + x[3] ^= (a << 13) | (a >> 19) + x[4] ^= (b << 13) | (b >> 19) + a = (x[8]+x[11]) & 0xffffffff + b = (x[13]+x[12]) & 0xffffffff + x[9] ^= (a << 13) | (a >> 19) + x[14] ^= (b << 13) | (b >> 19) + a = (x[3]+x[2]) & 0xffffffff + b = (x[4]+x[7]) & 0xffffffff + x[0] ^= (a << 18) | (a >> 14) + x[5] ^= (b << 18) | (b >> 14) + a = (x[9]+x[8]) & 0xffffffff + b = (x[14]+x[13]) & 0xffffffff + x[10] ^= (a << 18) | (a >> 14) + x[15] ^= (b << 18) | (b >> 14) + + # While we are handling the data, write it to the correct dest. + # The latter half is still part of salsa20 + for i in xrange(16): + dest[d_start + i] = B[i] = (x[i] + B[i]) & 0xffffffff + + +def blockmix_salsa8(BY, Yi, r): + """Blockmix; Used by SMix""" + + start = (2 * r - 1) * 16 + X = BY[start:start+16] # BlockMix - 1 + tmp = [0]*16 + + for i in xrange(2 * r): # BlockMix - 2 + #blockxor(BY, i * 16, X, 0, 16) # BlockMix - 3(inner) + salsa20_8(X, tmp, BY, i * 16, BY, Yi + i*16) # BlockMix - 3(outer) + #array_overwrite(X, 0, BY, Yi + (i * 16), 16) # BlockMix - 4 + + for i in xrange(r): # BlockMix - 6 + BY[i * 16:(i * 16)+(16)] = BY[Yi + (i * 2) * 16:(Yi + (i * 2) * 16)+(16)] + BY[(i + r) * 16:((i + r) * 16)+(16)] = BY[Yi + (i*2 + 1) * 16:(Yi + (i*2 + 1) * 16)+(16)] + + +def smix(B, Bi, r, N, V, X): + """SMix; a specific case of ROMix based on Salsa20/8""" + + X[0:(0)+(32 * r)] = B[Bi:(Bi)+(32 * r)] + + for i in xrange(N): # ROMix - 2 + V[i * (32 * r):(i * (32 * r))+(32 * r)] = X[0:(0)+(32 * r)] + blockmix_salsa8(X, 32 * r, r) # ROMix - 4 + + for i in xrange(N): # ROMix - 6 + j = integerify(X, r) & (N - 1) # ROMix - 7 + blockxor(V, j * (32 * r), X, 0, 32 * r) # ROMix - 8(inner) + blockmix_salsa8(X, 32 * r, r) # ROMix - 9(outer) + + B[Bi:(Bi)+(32 * r)] = X[0:(0)+(32 * r)] + + +def scrypt(password, salt, N=SCRYPT_N, r=SCRYPT_r, p=SCRYPT_p, olen=64): + """Returns a key derived using the scrypt key-derivarion function + + N must be a power of two larger than 1 but no larger than 2 ** 63 (insane) + r and p must be positive numbers such that r * p < 2 ** 30 + + The default values are: + N -- 2**14 (~16k) + r -- 8 + p -- 1 + + Memory usage is proportional to N*r. Defaults require about 16 MiB. + Time taken is proportional to N*p. Defaults take <100ms of a recent x86. + + The last one differs from libscrypt defaults, but matches the 'interactive' + work factor from the original paper. For long term storage where runtime of + key derivation is not a problem, you could use 16 as in libscrypt or better + yet increase N if memory is plentiful. + """ + + check_args(password, salt, N, r, p, olen) + + # Everything is lists of 32-bit uints for all but pbkdf2 + try: + B = _pbkdf2('sha256', password, salt, 1, p * 128 * r) + B = list(struct.unpack('<%dI' % (len(B) // 4), B)) + XY = [0] * (64 * r) + V = [0] * (32 * r * N) + except (MemoryError, OverflowError): + raise ValueError("scrypt parameters don't fit in memory") + + for i in xrange(p): + smix(B, i * 32 * r, r, N, V, XY) + + B = struct.pack('<%dI' % len(B), *B) + return _pbkdf2('sha256', password, B, 1, olen) + + +def scrypt_mcf(password, salt=None, N=SCRYPT_N, r=SCRYPT_r, p=SCRYPT_p, + prefix=SCRYPT_MCF_PREFIX_DEFAULT): + """Derives a Modular Crypt Format hash using the scrypt KDF + + Parameter space is smaller than for scrypt(): + N must be a power of two larger than 1 but no larger than 2 ** 31 + r and p must be positive numbers between 1 and 255 + Salt must be a byte string 1-16 bytes long. + + If no salt is given, a random salt of 128+ bits is used. (Recommended.) + """ + return mcf_mod.scrypt_mcf(scrypt, password, salt, N, r, p, prefix) + + +def scrypt_mcf_check(mcf, password): + """Returns True if the password matches the given MCF hash""" + return mcf_mod.scrypt_mcf_check(scrypt, mcf, password) + + +__all__ = ['scrypt', 'scrypt_mcf', 'scrypt_mcf_check'] + + +if __name__ == "__main__": + import sys + from . import tests + tests.run_scrypt_suite(sys.modules[__name__]) + diff --git a/lib/pylibscrypt/pyscrypt.py b/lib/pylibscrypt/pyscrypt.py new file mode 100644 index 00000000..5cfe1bbd --- /dev/null +++ b/lib/pylibscrypt/pyscrypt.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python + +# Copyright (c) 2014, Jan Varho +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +"""Scrypt implementation that calls into the 'scrypt' python module""" + + +import numbers + +from scrypt import hash as _scrypt + +from . import mcf as mcf_mod +from .common import * + + +# scrypt < 0.6 doesn't support hash length +try: + _scrypt(b'password', b'NaCl', N=2, r=1, p=1, buflen=42) +except TypeError: + raise ImportError('scrypt module version unsupported, 0.6+ required') + + +def scrypt(password, salt, N=SCRYPT_N, r=SCRYPT_r, p=SCRYPT_p, olen=64): + """Returns a key derived using the scrypt key-derivarion function + + N must be a power of two larger than 1 but no larger than 2 ** 63 (insane) + r and p must be positive numbers such that r * p < 2 ** 30 + + The default values are: + N -- 2**14 (~16k) + r -- 8 + p -- 1 + + Memory usage is proportional to N*r. Defaults require about 16 MiB. + Time taken is proportional to N*p. Defaults take <100ms of a recent x86. + + The last one differs from libscrypt defaults, but matches the 'interactive' + work factor from the original paper. For long term storage where runtime of + key derivation is not a problem, you could use 16 as in libscrypt or better + yet increase N if memory is plentiful. + """ + check_args(password, salt, N, r, p, olen) + + try: + return _scrypt(password=password, salt=salt, N=N, r=r, p=p, buflen=olen) + except: + raise ValueError + + +def scrypt_mcf(password, salt=None, N=SCRYPT_N, r=SCRYPT_r, p=SCRYPT_p, + prefix=SCRYPT_MCF_PREFIX_DEFAULT): + """Derives a Modular Crypt Format hash using the scrypt KDF + + Parameter space is smaller than for scrypt(): + N must be a power of two larger than 1 but no larger than 2 ** 31 + r and p must be positive numbers between 1 and 255 + Salt must be a byte string 1-16 bytes long. + + If no salt is given, a random salt of 128+ bits is used. (Recommended.) + """ + return mcf_mod.scrypt_mcf(scrypt, password, salt, N, r, p, prefix) + + +def scrypt_mcf_check(mcf, password): + """Returns True if the password matches the given MCF hash""" + return mcf_mod.scrypt_mcf_check(scrypt, mcf, password) + + +if __name__ == "__main__": + import sys + from . import tests + tests.run_scrypt_suite(sys.modules[__name__]) + diff --git a/lib/pylibscrypt/tests.py b/lib/pylibscrypt/tests.py new file mode 100644 index 00000000..95e1b186 --- /dev/null +++ b/lib/pylibscrypt/tests.py @@ -0,0 +1,419 @@ +#!/usr/bin/env python + +# Copyright (c) 2014, Jan Varho +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +"""Tests scrypt and PBKDF2 implementations""" + + +import base64 +import hashlib +import unittest + + +class ScryptTests(unittest.TestCase): + """Tests an scrypt implementation from module""" + set_up_lambda = None + tear_down_lambda = None + + def setUp(self): + if not self.module: + self.skipTest('module not tested') + if self.set_up_lambda: + self.set_up_lambda() + + def tearDown(self): + if self.tear_down_lambda: + self.tear_down_lambda() + + def _test_vector(self, vector): + pw, s, N, r, p, h, m = vector + self.assertEqual( + self.module.scrypt(pw, s, N, r, p), + base64.b16decode(h, True) + ) + if m is not None: + self.assertEqual( + self.module.scrypt_mcf(pw, s, N, r, p), + m + ) + self.assertTrue(self.module.scrypt_mcf_check(m, pw)) + self.assertFalse(self.module.scrypt_mcf_check(m, b'x' + pw)) + + def test_vector0(self): + self._test_vector(( + b'', b'', 16, 1, 1, + b'77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442' + b'fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906', + None + )) + + def test_vector1(self): + if self.fast: + self.skipTest('slow testcase') + self._test_vector(( + b'password', b'NaCl', 1024, 8, 16, + b'fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162' + b'2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640', + b'$s1$0a0810$TmFDbA==$/bq+HJ00cgB4VucZDQHp/nxq18vII3gw53N2Y0s3MWIu' + b'rzDZLiKjiG/xCSedmDDaxyevuUqD7m2DYMvfoswGQA==' + )) + + def test_vector2(self): + if self.fast: + self.skipTest('slow testcase') + self._test_vector(( + b'pleaseletmein', b'SodiumChloride', 16384, 8, 1, + b'7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2' + b'd5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887', + b'$s1$0e0801$U29kaXVtQ2hsb3JpZGU=$cCO9yzr9c0hGHAbNgf046/2o+7qQT44+' + b'qbVD9lRdofLVQylVYT8Pz2LUlwUkKpr55h6F3A1lHkDfzwF7RVdYhw==' + )) + + def test_vector3(self): + self._test_vector(( + b'password', b'NaCl', 2, 8, 1, + b'e5ed8edc019edfef2d3ced0896faf9eec6921dcc68125ce81c10d53474ce' + b'1be545979159700d324e77c68d34c553636a8429c4f3c99b9566466877f9' + b'dca2b92b', + b'$s1$010801$TmFDbA==$5e2O3AGe3+8tPO0Ilvr57saSHcxoElzoHBDVNHTO' + b'G+VFl5FZcA0yTnfGjTTFU2NqhCnE88mblWZGaHf53KK5Kw==' + )) + + def test_vector4(self): + self._test_vector(( + b'pleaseletmein', b'SodiumChloride', 4, 1, 1, + b'BB1D77016C543A99FE632C9C43C60180FD05E0CAC8B29374DBD1854569CB' + b'534F487240CFC069D6A59A35F2FA5C7428B21D9BE9F84315446D5371119E' + b'016FEDF7', + b'$s1$020101$U29kaXVtQ2hsb3JpZGU=$ux13AWxUOpn+YyycQ8YBgP0F4MrI' + b'spN029GFRWnLU09IckDPwGnWpZo18vpcdCiyHZvp+EMVRG1TcRGeAW/t9w==' + )) + + def test_vector5(self): + if self.fast: + self.skipTest('slow testcase') + self._test_vector(( + b'pleaseletmein', b'X'*32, 2**10, 8, 1, + b'cd81f46bd79125651e017a1bf5a28295f68d4b68d397815514bfdc2f3684' + b'f034ae2a5df332a48e915f7567306df2d401387b70d8f02f83bd6f4c69ff' + b'89d2663c', + None + )) + + def test_vector6(self): + self._test_vector(( + b'pa\0ss', b'salt'*4, 32, 2, 2, + b'76c5260f1dc6339512ae87143d799089f5b508c823c870a3d55f641efa84' + b'63a813221050c93a44255ac8027804c49a87c1ecc9911356b9fc17e06eda' + b'85f23ff5', + None + )) + + def test_vector7(self): + if self.fast: + self.skipTest('slow testcase') + self._test_vector(( + b'pleaseletmein', b'X'*32, 2**10, 8, 2, + b'1693cc02b680b18b3d0a874d459d23a5f63ff4f9de0fb5917ef899226af6' + b'bd33d3a3dfe569d3b6f4f762f0cb64f5f406d485aca501a54645d7389fe6' + b'e28e261e', + None + )) + + def test_bytes_enforced(self): + self.assertRaises(TypeError, self.module.scrypt, u'pass', b'salt') + self.assertRaises(TypeError, self.module.scrypt, 42, b'salt') + self.assertRaises(TypeError, self.module.scrypt, b'pass', None) + self.assertRaises(TypeError, self.module.scrypt_mcf, u'mcf', b'pass') + self.assertRaises(TypeError, self.module.scrypt_mcf, object, b'pass') + + def test_salt_length_mcf(self): + pw = b'pass' + self.assertRaises(ValueError, self.module.scrypt_mcf, pw, b'') + self.assertRaises(ValueError, self.module.scrypt_mcf, pw, b'a'*17) + + def test_salt_generation(self): + pw, N = b'pass', 2 + m1 = self.module.scrypt_mcf(pw, N=N) + m2 = self.module.scrypt_mcf(pw, N=N) + self.assertNotEqual(m1, m2) + self.assertTrue(self.module.scrypt_mcf_check(m1, pw)) + self.assertTrue(self.module.scrypt_mcf_check(m2, pw)) + + def test_invalid_N(self): + pw, s = b'password', b'salt'*8 + self.assertRaises(TypeError, self.module.scrypt, pw, s, 7.5) + self.assertRaises(ValueError, self.module.scrypt, pw, s, -1) + self.assertRaises(ValueError, self.module.scrypt, pw, s, 1) + self.assertRaises(ValueError, self.module.scrypt, pw, s, 42) + self.assertRaises(ValueError, self.module.scrypt, pw, s, 2**66) + self.assertRaises(ValueError, self.module.scrypt, pw, s, 2**66+2) + self.assertRaises(ValueError, self.module.scrypt_mcf, pw, None, 1) + self.assertRaises(ValueError, self.module.scrypt_mcf, pw, None, 2**32) + + def test_huge_N(self): + pw, s = b'password', b'salt'*8 + self.assertRaises(ValueError, self.module.scrypt, pw, s, 2**50) + self.assertRaises(ValueError, self.module.scrypt, pw, s, 2**60) + self.assertRaises(ValueError, self.module.scrypt_mcf, pw, + N=2**31, prefix=b'$7$') + + def test_invalid_r(self): + pw, s, N = b'password', b'salt', 2 + self.assertRaises(ValueError, self.module.scrypt, pw, s, N, 0) + self.assertRaises(ValueError, self.module.scrypt, pw, s, N, -1) + self.assertRaises(TypeError, self.module.scrypt, pw, s, N, 7.5) + self.assertRaises(ValueError, self.module.scrypt, pw, s, N, 2**31) + self.assertRaises(ValueError, self.module.scrypt_mcf, pw, s, N, 256) + + def test_invalid_p(self): + pw, s, N = b'password', b'salt', 2 + self.assertRaises(ValueError, self.module.scrypt, pw, s, N, 1, 0) + self.assertRaises(ValueError, self.module.scrypt, pw, s, N, 1, -2**31) + self.assertRaises(TypeError, self.module.scrypt, pw, s, N, 1, 7.5) + self.assertRaises(ValueError, self.module.scrypt, pw, s, N, 2**35) + self.assertRaises(ValueError, self.module.scrypt_mcf, pw, s, N, 1, 256) + + def test_olen(self): + pw, s, N = b'password', b'salt', 2 + self.assertEquals(len(self.module.scrypt(pw, s, N, olen=42)), 42) + self.assertEquals(len(self.module.scrypt(pw, s, N, olen=100)), 100) + self.assertRaises(TypeError, self.module.scrypt, pw, s, N, olen=b'7') + self.assertRaises(ValueError, self.module.scrypt, pw, s, N, olen=-1) + + def test_invalid_olen(self): + pw, s, N = b'password', b'salt', 2**10 + self.assertRaises(TypeError, self.module.scrypt, pw, s, N, olen=b'7') + self.assertRaises(ValueError, self.module.scrypt, pw, s, N, olen=-1) + + def test_mcf(self): + pw = b'password' + self.assertRaises(ValueError, self.module.scrypt_mcf_check, b'', pw) + self.assertRaises(ValueError, self.module.scrypt_mcf_check, + b'$s1$ffffffff$aaaa$bbbb', pw) + self.assertRaises(TypeError, self.module.scrypt_mcf_check, u'mcf', pw) + self.assertRaises(TypeError, self.module.scrypt_mcf_check, b'mcf', 42) + + def test_mcf_padding(self): + if self.fast: + self.skipTest('slow testcase') + pw = b'pleaseletmein' + m1 = ( + b'$s1$020101$U29kaXVtQ2hsb3JpZGU$ux13AWxUOpn+YyycQ8YBgP0F4MrI' + b'spN029GFRWnLU09IckDPwGnWpZo18vpcdCiyHZvp+EMVRG1TcRGeAW/t9w==' + ) + m2 = ( + b'$s1$020101$U29kaXVtQ2hsb3JpZGU=$ux13AWxUOpn+YyycQ8YBgP0F4MrI' + b'spN029GFRWnLU09IckDPwGnWpZo18vpcdCiyHZvp+EMVRG1TcRGeAW/t9w=' + ) + m3 = ( + b'$s1$020101$U29kaXVtQ2hsb3JpZGU=$ux13AWxUOpn+YyycQ8YBgP0F4MrI' + b'spN029GFRWnLU09IckDPwGnWpZo18vpcdCiyHZvp+EMVRG1TcRGeAW/t9' + ) + self.assertTrue(self.module.scrypt_mcf_check(m1, pw)) + self.assertTrue(self.module.scrypt_mcf_check(m2, pw)) + self.assertRaises(ValueError, self.module.scrypt_mcf_check, m3, pw) + + def test_mcf_nonstandard(self): + pw = b'pass' + m1 = ( # empty salt + b'$s1$010801$$WA1vBj+HFlIk7pG/OPS5bY4NKHBGeGIxEY99farnu2C9uOHxKe' + b'LWP3sCXRvP98F7lVi2JNT/Bmte38iodf81VEYB0Nu3pBw9JqTwiCAqMwL+2kqB' + ) + m2 = ( # 31 byte hash + b'$7$16..../....l/htqjrI38qNowkQZL8RxFVxS8JV9PPJr1+A/WTQWiU' + b'$wOcPY0vsHHshxa0u87FDhmTo42WZr0JbSHY2w2Zkyr1' + ) + m3 = ( # 44 byte salt, 31 byte hash + b'$7$12..../....aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + b'$14hkhieutTQcbq.iU1FDZzYz1vW8NPYowy4WERDM70' + ) + self.assertTrue(self.module.scrypt_mcf_check(m1, pw)) + self.assertTrue(self.module.scrypt_mcf_check(m2, pw)) + self.assertTrue(self.module.scrypt_mcf_check(m3, pw)) + + def test_mcf_7(self): + if self.fast: + self.skipTest('slow testcase') + p, m = b'pleaseletmein', ( + b'$7$C6..../....SodiumChloride' + b'$kBGj9fHznVYFQMEn/qDCfrDevf9YDtcDdKvEqHJLV8D' + ) + self.assertTrue(self.module.scrypt_mcf_check(m, p)) + self.assertFalse(self.module.scrypt_mcf_check(m, b'X'+p)) + self.assertRaises(ValueError, self.module.scrypt_mcf_check, + b'$7$$', p + ) + self.assertRaises(ValueError, self.module.scrypt_mcf_check, + b'$7$$$', p + ) + + def test_mcf_7_2(self): + if self.fast: + self.skipTest('slow testcase') + p = b'pleaseletmein' + m1 = self.module.scrypt_mcf(p, None, 2**10, 8, 2, b'$7$') + self.assertTrue(m1.startswith(b'$7$')) + self.assertTrue(self.module.scrypt_mcf_check(m1, p)) + m2 = self.module.scrypt_mcf(p, None, 2**10, 8, 2, b'$s1$') + self.assertTrue(m2.startswith(b'$s1$')) + self.assertTrue(self.module.scrypt_mcf_check(m1, p)) + + def test_mcf_7_fast(self): + p, m1 = b'pleaseletmein', ( + b'$7$06..../....SodiumChloride' + b'$ENlyo6fGw4PCcDBOFepfSZjFUnVatHzCcW55.ZGz3B0' + ) + self.assertTrue(self.module.scrypt_mcf_check(m1, p)) + m2 = self.module.scrypt_mcf(p, b'NaCl', 4, 8, 1, b'$7$') + self.assertTrue(self.module.scrypt_mcf_check(m2, p)) + + def test_mcf_unknown(self): + p = b'pleaseletmein' + self.assertRaises(ValueError, self.module.scrypt_mcf, p, prefix=b'$$') + + def test_mcf_null(self): + p1, p2, p3 = b'please', b'please\0letmein', b'pleaseletmein' + self.assertRaises(ValueError, self.module.scrypt_mcf, p2, N=4) + m = ( + b'$s1$020801$m8/OZVv4hi8rHFVTvOH3tQ==$jwi4vgiCjyqrZKOaksMFks5A' + b'M9ZRcrVPhAwqT1iRMTqXYrwkTngwjR2rwbAet9cSGdFfSverOEVLiLuUzG4k' + b'Hg==' + ) + self.assertTrue(self.module.scrypt_mcf_check(m, p2)) + self.assertFalse(self.module.scrypt_mcf_check(m, p1)) + self.assertFalse(self.module.scrypt_mcf_check(m, p3)) + + +def load_scrypt_suite(name, module, fast=True): + loader = unittest.defaultTestLoader + tests = type(name, (ScryptTests,), {'module': module, 'fast': fast}) + return unittest.defaultTestLoader.loadTestsFromTestCase(tests) + + +def run_scrypt_suite(module, fast=False): + suite = unittest.TestSuite() + suite.addTest(load_scrypt_suite('scryptTests', module, fast)) + unittest.TextTestRunner().run(suite) + + +class PBKDF2Tests(unittest.TestCase): + """Tests a PBKDF2 implementation from module""" + def setUp(self): + if not self.module: + self.skipTest('module not tested') + + def _test_vector(self, vector): + n, p, s, c, l, h = vector + h = base64.b16decode(h, True) + self.assertEquals(self.module.pbkdf2_hmac(n, p, s, c, l), h) + + def test_vector1(self): + self._test_vector(('sha1', b'password', b'salt', 1, 20, + b'0c60c80f961f0e71f3a9b524af6012062fe037a6')) + + def test_vector2(self): + self._test_vector(('sha1', b'pass\0word', b'sa\0lt', 4096, 16, + b'56fa6aa75548099dcc37d7f03425e0c3')) + + def test_vector3(self): + self._test_vector(('sha256', b'password', b'NaCl', 7, 42, + b'8cb94b8721e20e643be099f3c31d332456b4c26f55' + b'b6403950267dc2b3c0806bda709a3f2d7f6107db73')) + + def test_long_key(self): + self.module.pbkdf2_hmac('sha256', b'pass'*100, b'NaCl', 2, 20) + + +def load_pbkdf2_suite(name, module): + loader = unittest.defaultTestLoader + tests = type(name, (PBKDF2Tests,), {'module': module}) + return unittest.defaultTestLoader.loadTestsFromTestCase(tests) + + +def run_pbkdf2_suite(module, fast=False): + suite = unittest.TestSuite() + suite.addTest(load_pbkdf2_suite('scryptTests', module)) + unittest.TextTestRunner().run(suite) + + +if __name__ == "__main__": + suite = unittest.TestSuite() + try: + from . import pylibscrypt + suite.addTest(load_scrypt_suite('pylibscryptTests', pylibscrypt, True)) + except ImportError: + suite.addTest(load_scrypt_suite('pylibscryptTests', None, True)) + + try: + from . import pyscrypt + suite.addTest(load_scrypt_suite('pyscryptTests', pyscrypt, True)) + except ImportError: + suite.addTest(load_scrypt_suite('pyscryptTests', None, True)) + + try: + from . import pylibsodium + suite.addTest(load_scrypt_suite('pylibsodiumTests', + pylibsodium, True)) + from . import pylibscrypt + loader = unittest.defaultTestLoader + def set_up_ll(self): + if not self.module._scrypt_ll: + self.skipTest('no ll') + self.tmp_ll = self.module._scrypt_ll + self.tmp_scr = self.module.scr_mod + self.module._scrypt_ll = None + self.module.scr_mod = pylibscrypt + def tear_down_ll(self): + self.module._scrypt_ll = self.tmp_ll + self.module.scr_mod = self.tmp_scr + tmp = type( + 'pylibsodiumFallbackTests', (ScryptTests,), + { + 'module': pylibsodium, 'fast': False, + 'set_up_lambda': set_up_ll, + 'tear_down_lambda': tear_down_ll, + } + ) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(tmp)) + except ImportError: + suite.addTest(load_scrypt_suite('pylibsodiumTests', None, True)) + + try: + from . import pylibsodium_salsa + suite.addTest(load_scrypt_suite('pylibsodium_salsaTests', + pylibsodium_salsa, True)) + except ImportError: + suite.addTest(load_scrypt_suite('pylibsodium_salsaTests', None, True)) + + try: + from . import pypyscrypt_inline as pypyscrypt + suite.addTest(load_scrypt_suite('pypyscryptTests', pypyscrypt, True)) + except ImportError: + suite.addTest(load_scrypt_suite('pypyscryptTests', None, True)) + + try: + from . import pbkdf2 + suite.addTest(load_pbkdf2_suite('pbkdf2', pbkdf2)) + except ImportError: + suite.addTest(load_pbkdf2_suite('pbkdf2', None)) + + if 'pbkdf2_hmac' in dir(hashlib): + suite.addTest(load_pbkdf2_suite('hashlib_pbkdf2', hashlib)) + else: + suite.addTest(load_pbkdf2_suite('hashlib_pbkdf2', None)) + + unittest.TextTestRunner().run(suite) + diff --git a/setup.py b/setup.py index 8317c343..b4a0739e 100644 --- a/setup.py +++ b/setup.py @@ -12,9 +12,9 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 'lib'))) sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 'src'))) print(sys.path) -includes = ["sip", "re", "json", "logging", "hashlib", "os", "urllib", "ucoinpy", "requests"] +includes = ["sip", "re", "json", "logging", "hashlib", "os", "urllib", "ucoinpy", "pylibscrypt", "requests"] excludes = [] -packages = ["libnacl", "pylibscrypt"] +packages = ["libnacl"] includefiles = [] if sys.platform == "win32": -- GitLab