Reputation: 2438
I'm working to write a ctypes wrapper in PySodium for a libsodium routine crypto_aead_chacha20poly1305_encrypt
, the routine is defined as:
def crypto_aead_chacha20poly1305_encrypt(message,
ad,
nonce,
key):
mlen = ctypes.c_ulonglong(len(message))
adlen = ctypes.c_ulonglong(len(ad))
c = ctypes.create_string_buffer(mlen.value+16L)
clen = ctypes.c_ulonglong(0)
sodium.crypto_aead_chacha20poly1305_encrypt(c,
clen,
message,
mlen,
ad,
adlen,
None,
nonce,
key)
return c.raw
My test driver is:
from pysodium import crypto_aead_chacha20poly1305_encrypt
from bitstring import BitStream
key = BitStream(hex="4290bcb154173531f314af57f3be3b5006da371ece272afa1b5dbdd1100a1007")
nonce = BitStream(hex="cd7cf67be39c794a")
ad = BitStream(hex="87e229d4500845a079c0")
msg = BitStream(hex="86d09974840bded2a5ca")
print(key)
print(nonce)
print(ad)
print(msg)
m = crypto_aead_chacha20poly1305_encrypt(message=msg.bytes,
ad=ad.bytes,
nonce=nonce.bytes,
key=key.bytes)
edata = BitStream(bytes=m)
print(edata)
and much to my surprise (being the first time I used libsodium or PySodium or ctypes) it worked without a hitch on an AMD x86_64 system. Unfortunately when I ported everything over to the RaspberryPi (ARMv6) things came undone. I ran gdb python-gdb
against it and can see the stacktrace:
#0 chacha_keysetup (x=0xbeffefc4, k=0x0) at crypto_stream/chacha20/ref/stream_chacha20_ref.c:69 #1 0x4059dcbc in crypto_stream_chacha20_ref (c=0xbefff0e4 "\264\335\024", clen=, n=0x0, k=0x0) at crypto_stream/chacha20/ref/stream_chacha20_ref.c:241 #2 0x40556f60 in crypto_aead_chacha20poly1305_encrypt (c=0x405385f4 "\206Йt\204\v\336", , clen=0x437f44, m=0x0, mlen=4634464344201201140, ad=0xa , adlen=1079196860, nsec=0xa , npub= 0x0, k=0x0) at crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c:49 #3 0x40501368 in ffi_call_VFP () from /usr/lib/python2.7/lib-dynload/_ctypes_d.so #4 0x40500930 in ffi_call () from /usr/lib/python2.7/lib-dynload/_ctypes_d.so #5 0x404ecf10 in _call_function_pointer (flags=4353, pProc=0x40556ea8 , avalues=0xbefff2d8, atypes=0xbefff2a8, restype=0x402a6ed8, resmem=0xbefff308, argcount=9) at /build/python2.7-xJctIx/python2.7-2.7.3/Modules/_ctypes/callproc.c:827 #6 0x404edb84 in _ctypes_callproc (pProc=0x40556ea8 , argtuple= (, , '\x86\xd0\x99t\x84\x0b\xde\xd2\xa5\xca', , '\x87\xe2)\xd4P\x08E\xa0y\xc0', , None, '\xcd|\xf6{\xe3\x9cyJ', "B\x90\xbc\xb1T\x1751\xf3\x14\xafW\xf3\xbe;P\x06\xda7\x1e\xce'*\xfa\x1b]\xbd\xd1\x10\n\x10\x07"), flags=4353, argtypes=0x0, restype= , checker=0x0) at /build/python2.7-xJctIx/python2.7-2.7.3/Modules/_ctypes/callproc.c:1174 #7 0x404e5154 in PyCFuncPtr_call (self=0x40518e90, inargs= (, , '\x86\xd0\x99t\x84\x0b\xde\xd2\xa5\xca', , '\x87\xe2)\xd4P\x08E\xa0y\xc0', , None, '\xcd|\xf6{\xe3\x9cyJ', "B\x90\xbc\xb1T\x1751\xf3\x14\xafW\xf3\xbe;P\x06\xda7\x1e\xce'*\xfa\x1b]\xbd\xd1\x10\n\x10\x07"), kwds=0x0) at /build/python2.7-xJctIx/python2.7-2.7.3/Modules/_ctypes/_ctypes.c:3913 #8 0x0002fa08 in PyObject_Call (func=, arg= (, , '\x86\xd0\x99t\x84\x0b\xde\xd2\xa5\xca', , '\x87\xe2)\xd4P\x08E\xa0y\xc0', , None, '\xcd|\xf6{\xe3\x9cyJ', "B\x90\xbc\xb1T\x1751\xf3\x14\xafW\xf3\xbe;P\x06\xda7\x1e\xce'*\xfa\x1b]\xbd\xd1\x10\n\x10\x07"), kw=0x0) at ../Objects/abstract.c:2529
Reading up on what FFI actually does I've got to think that for some reason when ctypes passes the data off to FFI it gets corrupted along the way. I've searched trying to find whether there's some known issue with ctypes/FFI etc. on the Pi/ARM platform but have drawn a blank. If it's my actual binding code I'd be interested to know why it behaves correctly on the x86_64 vs. ARM.
And finally, I'm open to suggestions to use an alternative to ctypes (though not crazy about SWIG, if that's what it takes).
Upvotes: 4
Views: 1993
Reputation: 2438
clen
below needs to be passed by reference using the ctypes.byref()
method. From the Ctypes documentation: Sometimes a C api function expects a pointer to a data type as parameter, probably to write into the corresponding location, or if the data is too large to be passed by value. This is also known as passing parameters by reference. ctypes exports the byref() function which is used to pass parameters by reference. For whatever reason not using ctypes.byref()
poses no problem on an x86 platform, but did on the ARM processor. Updating the code to use it cleared the segmentation fault on the ARM and also works as expected on x86.
def crypto_aead_chacha20poly1305_encrypt(message,
ad,
nonce,
key):
mlen = ctypes.c_ulonglong(len(message))
adlen = ctypes.c_ulonglong(len(ad))
c = ctypes.create_string_buffer(mlen.value+16L)
clen = ctypes.c_ulonglong(0)
sodium.crypto_aead_chacha20poly1305_encrypt(c,
ctypes.byref(clen),
message,
mlen,
ad,
adlen,
None,
nonce,
key)
return c.raw
The bindings for crypto_aead_chacha20poly1305_encrypt
, crypto_aead_chacha20poly1305_decrypt
, and crypto_stream_chacha20_xor
are available on a fork of pysodium at https://github.com/iachievedit/pysodium until a pull request is submitted and accepted.
Upvotes: 3