pynacl

Python wrapper for http://nacl.cace-project.eu/
git clone https://code.literati.org/pynacl.git
Log | Files | Refs | README

commit b0de5e10dc26c7324aa20be076054af636b7a2dd
parent ac2efbd18f31ab81c6f2e61b9cbfdf705172dde7
Author: Sean Richard Lynch <seanl@literati.org>
Date:   Fri,  3 Feb 2012 23:48:02 -0800

Merge pull request #1 from k3d3/master

Added scalar multiplication functions
Diffstat:
A.gitignore | 3+++
MREADME.md | 22+++++++++++++++++-----
Mnacl.i | 102+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msetup.py | 50++++++++++++++++++++++++++++++++++++++++++++++----
Mtest.py | 45+++++++++++++++++++++++++++++++--------------
5 files changed, 161 insertions(+), 61 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,3 @@ +/build/ +/nacl.py +/nacl_wrap.c diff --git a/README.md b/README.md @@ -8,7 +8,7 @@ This is a simple wrapper for the [NaCl](http://nacl.cace-project.eu/) cryptographic library (not Google's NativeClient). It currently wraps crypto\_hash\_sha256, crypto\_hash\_sha512, crypto\_randombytes, and the crypto\_box, crypto\_sign, crypto\_secretbox, crypto\_stream, -crypto\_auth, and crypto\_onetimeauth default primitives. +crypto\_auth, crypto\_scalarmult and crypto\_onetimeauth default primitives. API @@ -28,14 +28,26 @@ test.py and the [NaCl documentation](http://nacl.cace-project.eu/). Installation ---------- -First, download and build NaCl. Then: +First, download NaCl. NaCl does not build with the -fPIC flag and +therefore you must modify a file: - export NACL_LIB=<location of libnacl.a and randombytes.o, required> - export NACL_INCLUDE=<location of NaCl header files> + cd <location of extracted NaCl> + sed -i "s/$/ -fPIC/" okcompilers/c* + +If you want the build status output printed to screen, you may also do: + + sed -i "s/exec 2\?>.*//" do + +You can then build NaCl with: + + ./do + +Once NaCl is successfully built, you can clone pynacl and run: + + export NACL_DIR=<location of extracted nacl directory> python setup.py build sudo python setup.py install - Testing ------- diff --git a/nacl.i b/nacl.i @@ -19,6 +19,7 @@ %{ #include "crypto_box.h" #include "crypto_sign.h" + #include "crypto_scalarmult_curve25519.h" #include "crypto_secretbox.h" #include "crypto_stream.h" #include "crypto_auth.h" @@ -72,22 +73,28 @@ return 0; } + #if PY_MAJOR_VERSION > 2 + #define MAKEINT PyLong_AsUnsignedLongLong + #else + #define MAKEINT PyInt_AsUnsignedLongLongMask + #endif + %} %include <typemaps.i> %typemap(in) (const unsigned char *m, unsigned long long mlen) { - if (!PyString_Check($input)) { + if (!PyBytes_Check($input)) { PyErr_SetString(PyExc_ValueError, "Expecting a string"); SWIG_fail; } - $1 = (unsigned char *)PyString_AS_STRING($input); - $2 = PyString_GET_SIZE($input); + $1 = (unsigned char *)PyBytes_AS_STRING($input); + $2 = PyBytes_GET_SIZE($input); } %typemap(in, numinputs=0) unsigned char [ANY] { - $result = PyString_FromStringAndSize(NULL, $1_dim0); - $1 = (unsigned char *)PyString_AS_STRING($result); + $result = PyBytes_FromStringAndSize(NULL, $1_dim0); + $1 = (unsigned char *)PyBytes_AS_STRING($result); } // For some reason [ANY] doesn't work for multi-argument typemaps. @@ -97,91 +104,91 @@ (unsigned char pk[crypto_box_PUBLICKEYBYTES], unsigned char sk[crypto_box_SECRETKEYBYTES]) (PyObject *temp1, PyObject *temp2) { - temp1 = PyString_FromStringAndSize(NULL, $1_dim0); - $1 = (unsigned char *)PyString_AS_STRING(temp1); - temp2 = PyString_FromStringAndSize(NULL, $2_dim0); - $2 = (unsigned char *)PyString_AS_STRING(temp2); + temp1 = PyBytes_FromStringAndSize(NULL, $1_dim0); + $1 = (unsigned char *)PyBytes_AS_STRING(temp1); + temp2 = PyBytes_FromStringAndSize(NULL, $2_dim0); + $2 = (unsigned char *)PyBytes_AS_STRING(temp2); $result = PyTuple_Pack(2, temp1, temp2); Py_DECREF(temp1); Py_DECREF(temp2); } %typemap(in) (const unsigned char *seed, unsigned long long seedlen) { - if (!PyString_Check($input)) { + if (!PyBytes_Check($input)) { PyErr_SetString(PyExc_ValueError, "Expecting a string"); SWIG_fail; } - $1 = (unsigned char *)PyString_AS_STRING($input); - $2 = (unsigned long long)PyString_GET_SIZE($input); + $1 = (unsigned char *)PyBytes_AS_STRING($input); + $2 = (unsigned long long)PyBytes_GET_SIZE($input); } %typemap(in) const unsigned char [ANY] { - if (!PyString_Check($input)) { + if (!PyBytes_Check($input)) { PyErr_SetString(PyExc_ValueError, "Expecting a string"); SWIG_fail; } - if (PyString_GET_SIZE($input) != $1_dim0) { + if (PyBytes_GET_SIZE($input) != $1_dim0) { PyErr_Format(PyExc_ValueError, "Expecting a string of length %d", $1_dim0); SWIG_fail; } - $1 = (unsigned char *)PyString_AS_STRING($input); + $1 = (unsigned char *)PyBytes_AS_STRING($input); } %typemap(in) (unsigned char *sm, unsigned long long *smlen, const unsigned char *m, unsigned long long mlen) (unsigned long long temp) { - if (!PyString_Check($input)) { + if (!PyBytes_Check($input)) { PyErr_SetString(PyExc_ValueError, "Expecting a string"); SWIG_fail; } - $4 = PyString_GET_SIZE($input); - $result = PyString_FromStringAndSize(NULL, $4 + crypto_sign_BYTES); - $1 = (unsigned char *)PyString_AS_STRING($result); + $4 = PyBytes_GET_SIZE($input); + $result = PyBytes_FromStringAndSize(NULL, $4 + crypto_sign_BYTES); + $1 = (unsigned char *)PyBytes_AS_STRING($result); $2 = &temp; - $3 = (unsigned char *)PyString_AS_STRING($input); + $3 = (unsigned char *)PyBytes_AS_STRING($input); } %typemap(in) (unsigned char *m, unsigned long long *mlen, const unsigned char *sm, unsigned long long smlen) (unsigned long long temp) { - if (!PyString_Check($input)) { + if (!PyBytes_Check($input)) { PyErr_SetString(PyExc_ValueError, "Expecting a string"); SWIG_fail; } - $4 = PyString_GET_SIZE($input); - $result = PyString_FromStringAndSize(NULL, $4); - $1 = (unsigned char *)PyString_AS_STRING($result); + $4 = PyBytes_GET_SIZE($input); + $result = PyBytes_FromStringAndSize(NULL, $4); + $1 = (unsigned char *)PyBytes_AS_STRING($result); $2 = &temp; - $3 = (unsigned char *)PyString_AS_STRING($input); + $3 = (unsigned char *)PyBytes_AS_STRING($input); } %typemap(argout) (unsigned char *sm, unsigned long long *smlen), (unsigned char *m, unsigned long long *mlen) { - _PyString_Resize(&$result, *$2); + _PyBytes_Resize(&$result, *$2); } %typemap(in) (unsigned char *buffer, unsigned long long bytes), (unsigned char *c, unsigned long long clen) { - $2 = PyInt_AsUnsignedLongLongMask($input); + $2 = MAKEINT($input); if ($2 == -1 && PyErr_Occurred() != NULL) { SWIG_fail; } - $result = PyString_FromStringAndSize(NULL, $2); - $1 = (unsigned char *)PyString_AS_STRING($result); + $result = PyBytes_FromStringAndSize(NULL, $2); + $1 = (unsigned char *)PyBytes_AS_STRING($result); } %typemap(in) (unsigned char *c, const unsigned char *in, unsigned long long clen) { - if (!PyString_Check($input)) { + if (!PyBytes_Check($input)) { PyErr_SetString(PyExc_ValueError, "Expecting a string"); SWIG_fail; } - $3 = PyString_GET_SIZE($input); - $result = PyString_FromStringAndSize(NULL, $3); - $1 = (unsigned char *)PyString_AS_STRING($result); - $2 = (unsigned char *)PyString_AS_STRING($input); + $3 = PyBytes_GET_SIZE($input); + $result = PyBytes_FromStringAndSize(NULL, $3); + $1 = (unsigned char *)PyBytes_AS_STRING($result); + $2 = (unsigned char *)PyBytes_AS_STRING($input); } %typemap(out) int { @@ -208,15 +215,15 @@ (unsigned char out[crypto_secretbox_ZEROBYTES], const unsigned char in[crypto_secretbox_BOXZEROBYTES], unsigned long long mlen) { - if (!PyString_Check($input)) { + if (!PyBytes_Check($input)) { PyErr_SetString(PyExc_ValueError, "Expecting a string"); SWIG_fail; } - $3 = PyString_GET_SIZE($input) + $2_dim0; + $3 = PyBytes_GET_SIZE($input) + $2_dim0; // Need to pad the beginning $1 = (unsigned char *)calloc($3 + $1_dim0, sizeof(unsigned char)); $2 = (unsigned char *)calloc($3 + $2_dim0, sizeof(unsigned char)); - memcpy(&$2[$2_dim0], PyString_AS_STRING($input), $3); + memcpy(&$2[$2_dim0], PyBytes_AS_STRING($input), $3); } %typemap(argout) (unsigned char out[crypto_box_BOXZEROBYTES], @@ -231,7 +238,7 @@ (unsigned char out[crypto_secretbox_ZEROBYTES], const unsigned char in[crypto_secretbox_BOXZEROBYTES], unsigned long long mlen) { - $result = PyString_FromStringAndSize((char *)&$1[$1_dim0], $3 - $1_dim0); + $result = PyBytes_FromStringAndSize((char *)&$1[$1_dim0], $3 - $1_dim0); free($1); free($2); } @@ -248,6 +255,7 @@ Py_INCREF($result); } + /** * Utilities */ @@ -311,6 +319,24 @@ int crypto_box_open_afternm(unsigned char out[crypto_box_ZEROBYTES], const unsigned char k[crypto_box_BEFORENMBYTES]); /** + * Scalar multiplication + */ +%constant int crypto_scalarmult_curve25519_SCALARBYTES; +%constant int crypto_scalarmult_curve25519_BYTES; + +int crypto_scalarmult_curve25519(unsigned char q[crypto_scalarmult_curve25519_BYTES], + const unsigned char n[crypto_scalarmult_curve25519_SCALARBYTES], + const unsigned char p[crypto_scalarmult_curve25519_BYTES]); +int crypto_scalarmult_curve25519_base(unsigned char q[crypto_scalarmult_curve25519_BYTES], + const unsigned char n[crypto_scalarmult_curve25519_SCALARBYTES]); + + +%pythoncode %{ +crypto_scalarmult = crypto_scalarmult_curve25519 +crypto_scalarmult_base = crypto_scalarmult_curve25519_base +%} + +/** * Signatures */ %constant int crypto_sign_BYTES; diff --git a/setup.py b/setup.py @@ -3,7 +3,10 @@ Build and install the NaCl wrapper. Environment variables: +NACL_DIR - location of NaCl's main directory. The INCLUDE and LIB + build directories are found automatically +THESE ENVIRONMENT VARIABLES ARE NOW OPTIONAL NACL_INCLUDE - location of NaCl's include files. Not needed if they're in a normal system location. NACL_LIB - location of libnacl.(a|dll) and randombytes.o. Probably @@ -12,29 +15,68 @@ NACL_LIB - location of libnacl.(a|dll) and randombytes.o. Probably """ import os +import subprocess from distutils.core import setup, Extension include_dirs = [] library_dirs = [] -NACL_INCLUDE = os.environ.get('NACL_INCLUDE') +def check_output(command, **kwargs): + p = subprocess.Popen(command, stdout=subprocess.PIPE, **kwargs) + output, err = p.communicate() + rc = p.poll() + if rc: + raise subprocess.CalledProcessError(rc, command, output=output) + return output + +try: + arch = check_output("uname -m", shell=True).rstrip().decode("utf8") +except subprocess.CalledProcessError: + arch = '' + +try: + shost = check_output("hostname | sed 's/\..*//' | tr -cd '[a-z][A-Z][0-9]'", shell=True).rstrip().decode("utf8") +except subprocess.CalledProcessError: + shost = '' + +if arch == 'x86_64': + arch='amd64' +if arch in ['i686','oi586','i486','i386']: + arch='x86' + +if os.environ.get("NACL_DIR"): + NACL_DIR = os.environ.get("NACL_DIR").rstrip("/") +else: + NACL_DIR="." + +if os.environ.get("NACL_INCLUDE") == None: + NACL_INCLUDE = NACL_DIR + '/build/%s/include/%s' % (shost, arch) +else: + NACL_INCLUDE = os.environ.get("NACL_INCLUDE") + +if os.environ.get("NACL_LIB") == None: + NACL_LIB = NACL_DIR + '/build/%s/lib/%s' % (shost, arch) +else: + NACL_LIB = os.environ.get("NACL_LIB") if NACL_INCLUDE is not None: include_dirs.append(NACL_INCLUDE) - -NACL_LIB = os.environ.get('NACL_LIB') +else: + include_dirs.append('.') if NACL_LIB is not None: library_dirs.append(NACL_LIB) extra_objects = ['{0}/randombytes.o'.format(NACL_LIB)] else: # This probably won't work. - extra_objects = ['randombytes.o'] + extra_objects = ['./randombytes.o'] nacl_module = Extension('_nacl', ['nacl.i'], include_dirs=include_dirs, library_dirs=library_dirs, + extra_compiler_args=['-fPIC'], + extra_link_args=['-fPIC'], libraries=['nacl'], extra_objects=extra_objects) diff --git a/test.py b/test.py @@ -4,8 +4,15 @@ import unittest import nacl +if bytes([65]) == b"A": # py3 + def bchr(c): + return bytes([c]) +else: # py2 + def bchr(c): + return chr(c) + def perturb(s): - return s[:-1] + chr((ord(s[-1]) + 1) % 256) + return s[:-1] + bchr((ord(s[-1:]) + 1) % 256) class RandomTestCase(unittest.TestCase): @@ -21,14 +28,14 @@ class RandomTestCase(unittest.TestCase): class HashTestCase(unittest.TestCase): - fox = "The quick brown fox jumps over the lazy dog." + fox = b"The quick brown fox jumps over the lazy dog." def check_hash(self, func, s, h): f = getattr(nacl, func) r = f(s) - self.assertEqual(binascii.b2a_hex(r), h) + self.assertEqual(binascii.b2a_hex(r).decode("ascii"), h) def test_sha256_empty(self): - self.check_hash("crypto_hash_sha256", "", + self.check_hash("crypto_hash_sha256", b"", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca4959" "91b7852b855") @@ -38,7 +45,7 @@ class HashTestCase(unittest.TestCase): "48c8635fb6c") def test_sha512_empty(self): - self.check_hash("crypto_hash_sha512", "", + self.check_hash("crypto_hash_sha512", b"", "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a" "921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47" "417a81a538327af927da3e") @@ -51,7 +58,7 @@ class HashTestCase(unittest.TestCase): class BoxTestCase(unittest.TestCase): - msg = "The quick brown fox jumps over the lazy dog." + msg = b"The quick brown fox jumps over the lazy dog." def nonce(self): return nacl.randombytes(nacl.crypto_box_NONCEBYTES) @@ -91,11 +98,11 @@ class BoxTestCase(unittest.TestCase): class SignTestCase(unittest.TestCase): - msg = "The quick brown fox jumps over the lazy dog." + msg = b"The quick brown fox jumps over the lazy dog." def setUp(self): self.pk, self.sk = nacl.crypto_sign_keypair() - self.pk1, self.sk1 = nacl.crypto_sign_keypair_fromseed("hello world") + self.pk1, self.sk1 = nacl.crypto_sign_keypair_fromseed(b"hello world") def test_keys_different(self): self.assertNotEqual(self.pk, self.pk1) @@ -108,10 +115,10 @@ class SignTestCase(unittest.TestCase): self.assertEqual(len(self.sk), nacl.crypto_sign_SECRETKEYBYTES) def test_seed(self): - self.assertEqual(binascii.b2a_hex(self.pk1), + self.assertEqual(binascii.b2a_hex(self.pk1).decode("ascii"), "683d8d0458ef6ec4cfef25157f5d88ce7a0bba334fd102fafc7e" "2751410d5718") - self.assertEqual(binascii.b2a_hex(self.sk1), + self.assertEqual(binascii.b2a_hex(self.sk1).decode("ascii"), "309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd" "3ca86d4cd86f989dd35bc5ff499670da34255b45b0cfd830e81f" "605dcf7dc5542e93ae9cd76f") @@ -126,8 +133,18 @@ class SignTestCase(unittest.TestCase): self.assertRaises(ValueError, nacl.crypto_sign_open, sm, self.pk1) +class ScalarMultTestCase(unittest.TestCase): + def setUp(self): + self.dh_a = nacl.crypto_box_keypair() + self.dh_b = nacl.crypto_box_keypair() + + def test_sharedkey(self): + shk_a = nacl.crypto_scalarmult(self.dh_a[1], self.dh_b[0]) + shk_b = nacl.crypto_scalarmult(self.dh_b[1], self.dh_a[0]) + self.assertEqual(shk_a, shk_b) + class SecretBoxTestCase(unittest.TestCase): - msg = "The quick brown fox jumps over the lazy dog." + msg = b"The quick brown fox jumps over the lazy dog." def setUp(self): self.k = nacl.randombytes(nacl.crypto_secretbox_KEYBYTES) @@ -140,13 +157,13 @@ class SecretBoxTestCase(unittest.TestCase): def test_secretbox_badsig(self): nonce = nacl.randombytes(nacl.crypto_secretbox_NONCEBYTES) c = nacl.crypto_secretbox(self.msg, nonce, self.k) - c1 = c[:-1] + chr((ord(c[-1]) + 1) % 256) + c1 = c[:-1] + bchr((ord(c[-1:]) + 1) % 256) self.assertRaises(ValueError, nacl.crypto_secretbox_open, c1, nonce, self.k) class StreamTestCase(unittest.TestCase): - msg = "The quick brown fox jumps over the lazy dog." + msg = b"The quick brown fox jumps over the lazy dog." def setUp(self): self.k = nacl.randombytes(nacl.crypto_stream_KEYBYTES) @@ -163,7 +180,7 @@ class StreamTestCase(unittest.TestCase): class AuthTestCaseMixin(object): - msg = "The quick brown fox jumps over the lazy dog." + msg = b"The quick brown fox jumps over the lazy dog." def attr(self, a): return getattr(nacl, 'crypto_{0}{1}'.format(self.name, a))