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