diff options
Diffstat (limited to 'src/pycryptopp/cipher')
-rw-r--r-- | src/pycryptopp/cipher/__init__.py | 4 | ||||
-rw-r--r-- | src/pycryptopp/cipher/aes.py | 68 | ||||
-rw-r--r-- | src/pycryptopp/cipher/aesmodule.cpp | 191 | ||||
-rw-r--r-- | src/pycryptopp/cipher/aesmodule.hpp | 7 | ||||
-rw-r--r-- | src/pycryptopp/cipher/xsalsa20.py | 31 | ||||
-rw-r--r-- | src/pycryptopp/cipher/xsalsa20module.cpp | 176 | ||||
-rw-r--r-- | src/pycryptopp/cipher/xsalsa20module.hpp | 6 |
7 files changed, 483 insertions, 0 deletions
diff --git a/src/pycryptopp/cipher/__init__.py b/src/pycryptopp/cipher/__init__.py new file mode 100644 index 0000000..0ebfadf --- /dev/null +++ b/src/pycryptopp/cipher/__init__.py @@ -0,0 +1,4 @@ +import aes +import xsalsa20 + +quiet_pyflakes=[aes, xsalsa20] diff --git a/src/pycryptopp/cipher/aes.py b/src/pycryptopp/cipher/aes.py new file mode 100644 index 0000000..7280063 --- /dev/null +++ b/src/pycryptopp/cipher/aes.py @@ -0,0 +1,68 @@ + +from pycryptopp import _import_my_names + +# These initializations to None are just to pacify pyflakes, which +# doesn't understand that we have to do some funky import trickery +# below in _import_my_names() in order to get sensible namespaces. +AES=None +Error=None + +_import_my_names(globals(), "aes_") + +del _import_my_names + +def start_up_self_test(): + """ + This is a quick test intended to detect major errors such as the library being + miscompiled and segfaulting or returning incorrect answers. We've had problems + of that kind many times, thus justifying running this self-test on import. + This idea was suggested to me by the second edition of "Practical + Cryptography" by Ferguson, Schneier, and Kohno. + These tests were copied from pycryptopp/test/test_aes.py on 2009-10-30. + """ + enc0 = "dc95c078a2408989ad48a21492842087530f8afbc74536b9a963b4f1c4cb738b" + from binascii import a2b_hex, b2a_hex + + cryptor = AES(key="\x00"*32) + ct = cryptor.process("\x00"*32) + if enc0 != b2a_hex(ct): + raise Error("pycryptopp failed startup self-test. Please run pycryptopp unit tests.") + + cryptor = AES(key="\x00"*32) + ct1 = cryptor.process("\x00"*15) + ct2 = cryptor.process("\x00"*17) + if enc0 != b2a_hex(ct1+ct2): + raise Error("pycryptopp failed startup self-test. Please run pycryptopp unit tests.") + + enc0 = "66e94bd4ef8a2c3b884cfa59ca342b2e" + cryptor = AES(key="\x00"*16) + ct = cryptor.process("\x00"*16) + if enc0 != b2a_hex(ct): + raise Error("pycryptopp failed startup self-test. Please run pycryptopp unit tests.") + + cryptor = AES(key="\x00"*16) + ct1 = cryptor.process("\x00"*8) + ct2 = cryptor.process("\x00"*8) + if enc0 != b2a_hex(ct1+ct2): + raise Error("pycryptopp failed startup self-test. Please run pycryptopp unit tests.") + + def _test_from_Niels_AES(keysize, result): + def fake_ecb_using_ctr(k, p): + return AES(key=k, iv=p).process('\x00'*16) + + E = fake_ecb_using_ctr + b = 16 + k = keysize + S = '\x00' * (k+b) + for i in range(1000): + K = S[-k:] + P = S[-k-b:-k] + S += E(K, E(K, P)) + + if S[-b:] != a2b_hex(result): + raise Error("pycryptopp failed startup self-test. Please run pycryptopp unit tests.") + + _test_from_Niels_AES(16, 'bd883f01035e58f42f9d812f2dacbcd8') + _test_from_Niels_AES(32, 'c84b0f3a2c76dd9871900b07f09bdd3e') + +start_up_self_test() diff --git a/src/pycryptopp/cipher/aesmodule.cpp b/src/pycryptopp/cipher/aesmodule.cpp new file mode 100644 index 0000000..0599911 --- /dev/null +++ b/src/pycryptopp/cipher/aesmodule.cpp @@ -0,0 +1,191 @@ +/** + * aesmodule.cpp -- Python wrappers around Crypto++'s AES-CTR + */ + +#define PY_SSIZE_T_CLEAN +#include <Python.h> +#if (PY_VERSION_HEX < 0x02050000) +typedef int Py_ssize_t; +#endif + +#include "aesmodule.hpp" + + +/* from Crypto++ */ +#ifdef DISABLE_EMBEDDED_CRYPTOPP +#include <cryptopp/modes.h> +#include <cryptopp/aes.h> +#else +#include <src-cryptopp/modes.h> +#include <src-cryptopp/aes.h> +#endif + +static const char*const aes___doc__ = "_aes counter mode cipher\n\ +You are advised to run aes.start_up_self_test() after importing this module."; + +static PyObject *aes_error; + +typedef struct { + PyObject_HEAD + + /* internal */ + CryptoPP::CTR_Mode<CryptoPP::AES>::Encryption * e; +} AES; + +PyDoc_STRVAR(AES__doc__, +"An AES cipher object.\n\ +\n\ +This object encrypts/decrypts in CTR mode, using a counter that is initialized\n\ +to zero when you instantiate the object. Successive calls to .process() will\n\ +use the current counter value and increment it.\n\ +\n\ +Note that you must never encrypt different data with the same key, or you\n\ +will leak information about your data. Therefore the only safe way to use\n\ +this class is to use a different AES key every time you are going to encrypt\n\ +different data. A good way to generate a different AES key is using AES, like\n\ +this:\n\ +\n\ + onetimekey = AES(key=masterkey).process(nonce)\n\ +\n\ +Where 'masterkey' is a secret key used only for generating onetimekeys this\ +way, and 'nonce' is a value that is guaranteed to never repeat.\ +\n\ +@param key: the symmetric encryption key; a string of exactly 16 bytes\ +"); + +static PyObject * +AES_process(AES* self, PyObject* msgobj) { + if (!PyString_CheckExact(msgobj)) { + PyStringObject* typerepr = reinterpret_cast<PyStringObject*>(PyObject_Repr(reinterpret_cast<PyObject*>(msgobj->ob_type))); + if (typerepr) { + PyErr_Format(aes_error, "Precondition violation: you are required to pass a Python string object (not a unicode, a subclass of string, or anything else), but you passed %s.", PyString_AS_STRING(reinterpret_cast<PyObject*>(typerepr))); + Py_DECREF(typerepr); + } else + PyErr_Format(aes_error, "Precondition violation: you are required to pass a Python string object (not a unicode, a subclass of string, or anything else)."); + return NULL; + } + + const char *msg; + Py_ssize_t msgsize; + if (PyString_AsStringAndSize(msgobj, const_cast<char**>(&msg), &msgsize)) + return NULL; + assert (msgsize >= 0); + + PyStringObject* result = reinterpret_cast<PyStringObject*>(PyString_FromStringAndSize(NULL, msgsize)); + if (!result) + return NULL; + + self->e->ProcessData(reinterpret_cast<byte*>(PyString_AS_STRING(result)), reinterpret_cast<const byte*>(msg), msgsize); + return reinterpret_cast<PyObject*>(result); +} + +PyDoc_STRVAR(AES_process__doc__, +"Encrypt or decrypt the next bytes, returning the result."); + +static PyMethodDef AES_methods[] = { + {"process", reinterpret_cast<PyCFunction>(AES_process), METH_O, AES_process__doc__}, + {NULL}, +}; + +static PyObject * +AES_new(PyTypeObject* type, PyObject *args, PyObject *kwdict) { + AES* self = reinterpret_cast<AES*>(type->tp_alloc(type, 0)); + if (!self) + return NULL; + self->e = NULL; + return reinterpret_cast<PyObject*>(self); +} + +static void +AES_dealloc(PyObject* self) { + if (reinterpret_cast<AES*>(self)->e) + delete reinterpret_cast<AES*>(self)->e; + self->ob_type->tp_free(self); +} + +static int +AES_init(PyObject* self, PyObject *args, PyObject *kwdict) { + static const char *kwlist[] = { "key", "iv", NULL }; + const char *key = NULL; + Py_ssize_t keysize = 0; + const char *iv = NULL; + const char defaultiv[CryptoPP::AES::BLOCKSIZE] = {0}; + Py_ssize_t ivsize = 0; + if (!PyArg_ParseTupleAndKeywords(args, kwdict, "t#|t#:AES.__init__", const_cast<char**>(kwlist), &key, &keysize, &iv, &ivsize)) + return -1; + assert (keysize >= 0); + assert (ivsize >= 0); + + if (!iv) + iv = defaultiv; + else if (ivsize != 16) { + PyErr_Format(aes_error, "Precondition violation: if an IV is passed, it must be exactly 16 bytes, not %d", ivsize); + return -1; + } + try { + reinterpret_cast<AES*>(self)->e = new CryptoPP::CTR_Mode<CryptoPP::AES>::Encryption(reinterpret_cast<const byte*>(key), keysize, reinterpret_cast<const byte*>(iv)); + } catch (CryptoPP::InvalidKeyLength le) { + PyErr_Format(aes_error, "Precondition violation: you are required to pass a valid key size. Crypto++ gave this exception: %s", le.what()); + return -1; + } + if (!reinterpret_cast<AES*>(self)->e) { + PyErr_NoMemory(); + return -1; + } + return 0; +} + +static PyTypeObject AES_type = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "_aes.AES", /*tp_name*/ + sizeof(AES), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + AES_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + AES__doc__, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + AES_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + AES_init, /* tp_init */ + 0, /* tp_alloc */ + AES_new, /* tp_new */ +}; + +void +init_aes(PyObject*const module) { + if (PyType_Ready(&AES_type) < 0) + return; + Py_INCREF(&AES_type); + PyModule_AddObject(module, "aes_AES", (PyObject *)&AES_type); + + aes_error = PyErr_NewException(const_cast<char*>("_aes.Error"), NULL, NULL); + PyModule_AddObject(module, "aes_Error", aes_error); + + PyModule_AddStringConstant(module, "aes___doc__", const_cast<char*>(aes___doc__)); +} diff --git a/src/pycryptopp/cipher/aesmodule.hpp b/src/pycryptopp/cipher/aesmodule.hpp new file mode 100644 index 0000000..d10f44f --- /dev/null +++ b/src/pycryptopp/cipher/aesmodule.hpp @@ -0,0 +1,7 @@ +#ifndef __INCL_AESMODULE_HPP +#define __INCL_AESMODULE_HPP + +extern void +init_aes(PyObject* module); + +#endif /* #ifndef __INCL_AESMODULE_HPP */ diff --git a/src/pycryptopp/cipher/xsalsa20.py b/src/pycryptopp/cipher/xsalsa20.py new file mode 100644 index 0000000..86ddcd3 --- /dev/null +++ b/src/pycryptopp/cipher/xsalsa20.py @@ -0,0 +1,31 @@ +from pycryptopp import _import_my_names + +_import_my_names(globals(), "xsalsa20_") + +del _import_my_names + +def selftest(): + # pyflakes doesn't know that XSalsa20 is made available above + XSalsa20 = globals()["XSalsa20"] + from binascii import unhexlify + key = unhexlify("ad5eadf7163b0d36e44c126037a03419" + "fcda2b3a1bb4ab064b6070e61b0fa5ca") + iv = unhexlify("6a059adb8c7d4acb1c537767d541506f" "c5ef0ace9a2a65bd") + encrypted = unhexlify("23a8ed0475150e988c545b11e3660de7" + "8bf88e6628c4c99ba36330c05cb919e7" + "901295db479c9a8a0401d5e040b8919b" + "7d64b2f728c59703c3") + p = XSalsa20(key, iv) + decrypted = p.process(encrypted) + expected = "crypto libraries should always test themselves at powerup" + assert decrypted == expected + + p = XSalsa20(key, iv) + decrypted = "" + offset = 0 + for chunksize in [13,11,1,2,3,20,999]: + decrypted += p.process(encrypted[offset:offset+chunksize]) + offset += chunksize + assert decrypted == expected + +selftest() diff --git a/src/pycryptopp/cipher/xsalsa20module.cpp b/src/pycryptopp/cipher/xsalsa20module.cpp new file mode 100644 index 0000000..ab29787 --- /dev/null +++ b/src/pycryptopp/cipher/xsalsa20module.cpp @@ -0,0 +1,176 @@ +/** + * xsalsa20module.cpp -- Python wrappers around Crypto++'s salsa + */ + +#define PY_SSIZE_T_CLEAN +#include <Python.h> +#if (PY_VERSION_HEX < 0x02050000) +typedef int Py_ssize_t; +#endif + +#include "xsalsa20module.hpp" + +#ifdef DISABLE_EMBEDDED_CRYPTOPP +#include <cryptopp/salsa.h> +#else +#include <src-cryptopp/salsa.h> +#endif + +static const char* const xsalsa20__doc__ = "_xsalsa20 cipher"; + +static PyObject *xsalsa20_error; + +typedef struct { + PyObject_HEAD + + /* internal */ +// CryptoPP::CTR_Mode<CryptoPP::XSalsa20>::Encryption *e; + CryptoPP::XSalsa20::Encryption *e; +} XSalsa20; + +PyDoc_STRVAR(XSalsa20__doc__, +"An XSalsa20 cipher object.\n\ +\n\ +This object encrypts/decrypts in CTR mode, using a counter that is initialized\n\ +to zero when you instantiate the object. Successive calls to .process() will \n\ +use the current counter and increment it.\n\ +\n\ +"); + +static PyObject *XSalsa20_process(XSalsa20* self, PyObject* msgobj) { + if(!PyString_CheckExact(msgobj)) { + PyStringObject* typerepr = reinterpret_cast<PyStringObject*>(PyObject_Repr(reinterpret_cast<PyObject*>(msgobj->ob_type))); + if (typerepr) { + PyErr_Format(xsalsa20_error, "Precondition violation: you are required to pass a Python string object (not a unicode, a subclass of string, or anything else), but you passed %s.", PyString_AS_STRING(reinterpret_cast<PyObject*>(typerepr))); + Py_DECREF(typerepr); + } else + PyErr_Format(xsalsa20_error, "Precondition violation: you are required to pass a Python string object (not a unicode, a subclass of string, or anything else)."); + return NULL; + } + + const char* msg; + Py_ssize_t msgsize; + if (PyString_AsStringAndSize(msgobj, const_cast<char**>(&msg), &msgsize)) + return NULL; + assert (msgsize >= 0); + + PyStringObject* result = reinterpret_cast<PyStringObject*>(PyString_FromStringAndSize(NULL, msgsize)); + if (!result) + return NULL; + + self->e->ProcessString(reinterpret_cast<byte*>(PyString_AS_STRING(result)), reinterpret_cast<const byte*>(msg), msgsize); + return reinterpret_cast<PyObject*>(result); +} + +PyDoc_STRVAR(XSalsa20_process__doc__, +"Encrypt or decrypt the next bytes, returning the result."); + +static PyMethodDef XSalsa20_methods[] = { + {"process", reinterpret_cast<PyCFunction>(XSalsa20_process), METH_O, XSalsa20_process__doc__}, + {NULL}, +}; + +static PyObject* XSalsa20_new(PyTypeObject* type, PyObject *args, PyObject *kwdict) { + XSalsa20* self = reinterpret_cast<XSalsa20*>(type->tp_alloc(type, 0)); + if (!self) + return NULL; + self->e = NULL; + return reinterpret_cast<PyObject*>(self); +} + +static void XSalsa20_dealloc(PyObject* self) { + if (reinterpret_cast<XSalsa20*>(self)->e) + delete reinterpret_cast<XSalsa20*>(self)->e; + self->ob_type->tp_free(self); +} + +static int XSalsa20_init(PyObject* self, PyObject *args, PyObject *kwdict) { + static const char *kwlist[] = { "key", "iv", NULL}; + const char *key = NULL; + Py_ssize_t keysize = 0; + const char *iv = NULL; + const char defaultiv[24] = {0}; + Py_ssize_t ivsize = 0; + if (!PyArg_ParseTupleAndKeywords(args, kwdict, "t#|t#:XSalsa20.__init__", const_cast<char**>(kwlist), &key, &keysize, &iv, &ivsize)) + return -1; + assert (keysize >= 0); + assert (ivsize >= 0); + + if (!iv) + iv = defaultiv; + else if (ivsize != 24) { + PyErr_Format(xsalsa20_error, "Precondition violation: if an IV is passed, it must be exactly 24 bytes, not %d", ivsize); + return -1; + } + + try { + reinterpret_cast<XSalsa20*>(self)->e = new CryptoPP::XSalsa20::Encryption(reinterpret_cast<const byte*>(key), keysize, reinterpret_cast<const byte*>(iv)); + } + catch (CryptoPP::InvalidKeyLength le) + { + PyErr_Format(xsalsa20_error, "Precondition violation: you are required to pass a valid key size. Crypto++ gave this exception: %s", le.what()); + return -1; + } + if (!reinterpret_cast<XSalsa20*>(self)->e) + { + PyErr_NoMemory(); + return -1; + } + return 0; +} + + +static PyTypeObject XSalsa20_type = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "_xsalsa.XSalsa20", /*tp_name*/ + sizeof(XSalsa20), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + XSalsa20_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + XSalsa20__doc__, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + XSalsa20_methods, /*tp_methods*/ + 0, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + XSalsa20_init, /*tp_init*/ + 0, /*tp_alloc*/ + XSalsa20_new, /*tp_new*/ +}; + +void init_xsalsa20(PyObject*const module) +{ + if (PyType_Ready(&XSalsa20_type) < 0) + return; + Py_INCREF(&XSalsa20_type); + PyModule_AddObject(module, "xsalsa20_XSalsa20", (PyObject *)&XSalsa20_type); + + xsalsa20_error = PyErr_NewException(const_cast<char*>("_xsalsa20.Error"), NULL, NULL); + PyModule_AddObject(module, "xsalsa20_Error", xsalsa20_error); + + PyModule_AddStringConstant(module, "xsalsa20__doc__", const_cast<char*>(xsalsa20__doc__)); +} diff --git a/src/pycryptopp/cipher/xsalsa20module.hpp b/src/pycryptopp/cipher/xsalsa20module.hpp new file mode 100644 index 0000000..24e1e2d --- /dev/null +++ b/src/pycryptopp/cipher/xsalsa20module.hpp @@ -0,0 +1,6 @@ +#ifndef __INCL_XSALSA20MODULE_HPP +#define __INCL_XSALSA20MODULE_HPP + +extern void init_xsalsa20(PyObject* module); + +#endif; /*#ifndef __INCL_XSALSA20MODULE_HPP*/ |