summaryrefslogtreecommitdiff
path: root/src/pycryptopp/cipher
diff options
context:
space:
mode:
Diffstat (limited to 'src/pycryptopp/cipher')
-rw-r--r--src/pycryptopp/cipher/__init__.py4
-rw-r--r--src/pycryptopp/cipher/aes.py68
-rw-r--r--src/pycryptopp/cipher/aesmodule.cpp191
-rw-r--r--src/pycryptopp/cipher/aesmodule.hpp7
-rw-r--r--src/pycryptopp/cipher/xsalsa20.py31
-rw-r--r--src/pycryptopp/cipher/xsalsa20module.cpp176
-rw-r--r--src/pycryptopp/cipher/xsalsa20module.hpp6
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*/