A very simple, pure-Python implementation of the scrypt password-based key derivation function and scrypt file format libraries.
- Pure Python (no external dependancies)
- Python 2.x and 3.x support (see below for Python 3 details)
--
The sample code in this documentation is for Python 2.x. For Python 3.x, see the Python 3 section below.
The scrypt algorithm is a password-based key derivation function, which takes in several parameters to adjust the difficulty and returns a string of bytes. This is useful for transforming passwords into a target length, while at the same time increaing the cost of attempting to brute-froce guess a password.
password
- a passowrdsalt
- a cryptographic saltN
- general work factorr
- memory costp
- computation cost (parallelization factor)dkLen
- the output length (in bytes) to return
import pyscrypt
hashed = pyscrypt.hash(password = "correct horse battery staple",
salt = "seasalt",
N = 1024,
r = 1,
p = 1,
dkLen = 32)
print hashed.encode('hex')
When writing a file the N
, r
and p
parameters are required. The salt
parameter is optional, and if omitted will be generated from urandom.
import pyscrypt
with pyscrypt.ScryptFile('filename.scrypt', "password", N = 1024, r = 1, p = 1) as f:
f.write("Hello World")
To write to a file-like object without the context manager, it is important to either close the ScryptFile manually or to call finalize to ensure the footer gets flushed:
import pyscrypt
import StringIO
output = StringIO.StringIO()
sf = pyscrypt.ScryptFile(output, "pass123", 1024, 1, 1)
sf.write("Hello world")
sf.finalize()
output.seek(0)
encrypted = output.read()
import pyscrypt
# Read the entire contents
with pyscrypt.ScryptFile('filename.scrypt', password = "password") as f:
print f.read()
# Iterate over each line
with pyscrypt.ScryptFile('filename.scrypt', password = "password") as f:
for line in f:
print line
# Ensure the integrity of the file after completely read
print f.valid
A handful of test cases are provided for both the hash algorithm and the ScryptFile library. The ScryptFile tests generate tests that can be validated against the command line utility (http://www.tarsnap.com/scrypt.html).
# python tests/run-tests-hash.py
Version: 1.6.0
Test 1: pass
Test 2: pass
Test 3: pass
Test 4: pass
Test 5: pass
# python tests/run-tests-file.py
Version: 1.6.0
Test Encrypt/Decrypt: text_length=3 result=pass valid=True
Test Encrypt/Decrypt: text_length=16 result=pass valid=True
Test Encrypt/Decrypt: text_length=127 result=pass valid=True
Test Encrypt/Decrypt: text_length=128 result=pass valid=True
Test Encrypt/Decrypt: text_length=129 result=pass valid=True
Test Encrypt/Decrypt: text_length=1500 result=pass valid=True
Created /tmp/test-10.scrypt and /tmp/test-10.txt. Check with tarsnap.
Created /tmp/test-100.scrypt and /tmp/test-100.txt. Check with tarsnap.
Created /tmp/test-1000.scrypt and /tmp/test-1000.txt. Check with tarsnap.
Test With filename: result=pass
Test Verify: filename=tests/test1.scrypt result=pass
Test Decrypt: dec('tests/test1.scrypt') == 'tests/test1.txt' result=pass valid=None
Test Decrypt: dec('tests/test1.scrypt') == 'tests/test1.txt' result=pass valid=True
Test Decrypt: dec('tests/test1.scrypt') == 'tests/test1.txt' result=pass valid=True
Test Verify: filename=tests/test2.scrypt result=pass
Test Decrypt: dec('tests/test2.scrypt') == 'tests/test2.txt' result=pass valid=None
Test Decrypt: dec('tests/test2.scrypt') == 'tests/test2.txt' result=pass valid=None
Test Decrypt: dec('tests/test2.scrypt') == 'tests/test2.txt' result=pass valid=True
Notice that valid
is sometimes None. The value of valid
can take on one of three values:
- None - File has not been entirely read, so the checksum cannot be verified
- True - The end-of-file checksum is valid
- False - The end-of-file checksum is invalid (some bytes in the file are corrupt)
The scrypt algorithm is a CPU and memory intense algorithm, by design. For comparison, here are numbers based on my MacBook Air for scrypt hashing with (N = 1024, r = 1, p =1):
CPython (what you probably have installed)
6 hashes per second
Pypy (a much faster Python implementation, see pypy.org)
250 hashes per second
C-Wrapper (See the FAQ below)
2364 hashes per second
This library is Python 3 friendly, however, there are a few things to note.
- The parameters
password
andsalt
must be byte objects. e.g.b"pass123"
instead of"pass123"
. - ScryptFile's mode must be either
rb
orwb
. ScryptFile has two constants to help write portable code,ScryptFile.MODE_READ
andScryptFile.MODE_WRITE
.
import pyscrypt
# Hash
hashed = pyscrypt.hash(password = b"correct horse battery staple",
salt = b"seasalt",
N = 1024,
r = 1,
p = 1,
dkLen = 256)
print hashed
# Write a file
with pyscrypt.ScryptFile('filename.scrypt', b'password', 1024, 1, 1) as f:
f.write(b"Hello world")
# Read a file
with pyscrypt.ScryptFile('filename.scrypt', b'password') as f:
data = f.read()
print(data)
Why is this so slow? It is written in pure Python. It is not meant to be fast, more of a reference solution.
How do I get one of these C wrappers you speak of?
> # Download the source
> curl -L https://github.com/forrestv/p2pool/archive/13.4.tar.gz > p2pool-13.4.tar.gz
> # Untar
> tar -xzf p2pool-13.4.tar.gz
> # Build and install
> cd p2pool-13.4/litecoin_scrypt/
> python setup.py build
> sudo python setup.py install
> python
>>> import scrypt
>>> scrypt.hash(password = "correct horse staple battery",
salt = "seasalt",
N = 1024,
p = 1,
r = 1,
buflen = 256)
How do I get a question I have added? E-mail me at pyscrypt@ricmoo.com with any questions, suggestions, comments, et cetera.
Can I give you my money? Umm... Ok? :-)
Bitcoin - 1LNdGsYtZXWeiKjGba7T997qvzrWqLXLma