diff --git a/MANIFEST.in b/MANIFEST.in index f7c92c539524cb53c6c9a23880df80a714efd8a8..c3b81f6a29783a888e2f0907d238322413ff24ea 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -26,3 +26,6 @@ recursive-include tests/data * # Remove CFFI files global-exclude __pycache__/* + +# Add PyNaCl header files +recursive-include src/nacl/_lib *.h diff --git a/setup.py b/setup.py index 7aa269b87ed76e1f9dc51f17515271e217ec95c2..336b1e42b2ee6ae1ce4980f8a315c2ee483c2299 100644 --- a/setup.py +++ b/setup.py @@ -62,13 +62,13 @@ def which(name, flags=os.X_OK): # Taken from twisted sys.path += glob.glob("*.egg") try: - import nacl.nacl + import nacl._lib except ImportError: # installing - there is no cffi yet ext_modules = [] else: # building bdist - cffi is here! - ext_modules = [nacl.nacl.ffi.verifier.get_extension()] + ext_modules = [nacl._lib.ffi.verifier.get_extension()] def use_system(): @@ -210,9 +210,12 @@ setup( package_dir={"": "src"}, packages=[ "nacl", + "nacl._lib", + "nacl.c", ], + package_data={"nacl._lib": ["*.h"]}, - ext_package="nacl", + ext_package="nacl._lib", ext_modules=ext_modules, cmdclass={ diff --git a/src/nacl/_lib/__init__.py b/src/nacl/_lib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..4bc8a2141b2b613ab9197fc5ae0ef6bc2263e7d6 --- /dev/null +++ b/src/nacl/_lib/__init__.py @@ -0,0 +1,84 @@ +# Copyright 2013 Donald Stufft and individual contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import, division, print_function + +import glob +import os.path + +import six + +# We need to import this prior to importing cffi to fix prebuilding the +# extension modules +from nacl import _cffi_fix + +from cffi import FFI +from cffi.verifier import Verifier + + +__all__ = ["ffi"] + + +HEADERS = glob.glob( + os.path.join(os.path.abspath(os.path.dirname(__file__)), "*.h") +) + + +# Build our FFI instance +ffi = FFI() + + +# Add all of our header files +for header in HEADERS: + with open(header, "r") as hfile: + ffi.cdef(hfile.read()) + + +# TODO: Can we use the ABI of libsodium for this instead? +ffi.verifier = Verifier(ffi, + "#include <sodium.h>", + + # We need to link to the sodium library + libraries=["sodium"], + + # Our ext_package is nacl so look for it + ext_package="nacl._lib", +) + + +class Library(object): + + def __init__(self, ffi): + self.ffi = ffi + self._initalized = False + + # This prevents the compile_module() from being called, the module + # should have been compiled by setup.py + def _compile_module(*args, **kwargs): + raise RuntimeError("Cannot compile module during runtime") + self.ffi.verifier.compile_module = _compile_module + + def __getattr__(self, name): + if not self._initalized: + self._lib = self.ffi.verifier.load_library() + + # redirect attribute access to the underlying lib + attr = getattr(self._lib, name) + + # Go ahead and assign the returned value to this class so we don't + # need to do this lookup again + setattr(self, name, attr) + + return attr + +lib = Library(ffi) diff --git a/src/nacl/_lib/crypto_box.h b/src/nacl/_lib/crypto_box.h new file mode 100644 index 0000000000000000000000000000000000000000..e7d59f76386f5e6fda65332418d35e9f80ff6f21 --- /dev/null +++ b/src/nacl/_lib/crypto_box.h @@ -0,0 +1,43 @@ +/* Copyright 2013 Donald Stufft and individual contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +size_t crypto_box_secretkeybytes(); +size_t crypto_box_publickeybytes(); +size_t crypto_box_zerobytes(); +size_t crypto_box_boxzerobytes(); +size_t crypto_box_noncebytes(); +size_t crypto_box_beforenmbytes(); + + +int crypto_box_keypair(unsigned char *pk, unsigned char *sk); + +int crypto_box(unsigned char *c, const unsigned char *m, + unsigned long long mlen, const unsigned char *n, + const unsigned char *pk, const unsigned char *sk); + +int crypto_box_open(unsigned char *m, const unsigned char *c, + unsigned long long clen, const unsigned char *n, + const unsigned char *pk, const unsigned char *sk); + +int crypto_box_beforenm(unsigned char *k, const unsigned char *pk, + const unsigned char *sk); + +int crypto_box_afternm(unsigned char *c, const unsigned char *m, + unsigned long long mlen, const unsigned char *n, + const unsigned char *k); + +int crypto_box_open_afternm(unsigned char *m, const unsigned char *c, + unsigned long long clen, const unsigned char *n, + const unsigned char *k); diff --git a/src/nacl/_lib/crypto_hash.h b/src/nacl/_lib/crypto_hash.h new file mode 100644 index 0000000000000000000000000000000000000000..9ca6473799a203325f3a1a47c48cf79c13ace25d --- /dev/null +++ b/src/nacl/_lib/crypto_hash.h @@ -0,0 +1,29 @@ +/* Copyright 2013 Donald Stufft and individual contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// size_t crypto_hash_bytes(); +size_t crypto_hash_sha256_bytes(); +size_t crypto_hash_sha512_bytes(); + + +int crypto_hash(unsigned char *out, const unsigned char *in, + unsigned long long inlen); + +int crypto_hash_sha256(unsigned char *out, const unsigned char *in, + unsigned long long inlen); + +int crypto_hash_sha512(unsigned char *out, const unsigned char *in, + unsigned long long inlen); diff --git a/src/nacl/_lib/crypto_scalarmult.h b/src/nacl/_lib/crypto_scalarmult.h new file mode 100644 index 0000000000000000000000000000000000000000..dee5e84928e2f2271df849d898f5a2d4bc4ab138 --- /dev/null +++ b/src/nacl/_lib/crypto_scalarmult.h @@ -0,0 +1,19 @@ +/* Copyright 2013 Donald Stufft and individual contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +size_t crypto_scalarmult_bytes(); +size_t crypto_scalarmult_scalarbytes(); + +int crypto_scalarmult_base(unsigned char *q, const unsigned char *n); diff --git a/src/nacl/_lib/crypto_secretbox.h b/src/nacl/_lib/crypto_secretbox.h new file mode 100644 index 0000000000000000000000000000000000000000..88da2a3563250bc185b1c955bf8805dc73d8cae7 --- /dev/null +++ b/src/nacl/_lib/crypto_secretbox.h @@ -0,0 +1,28 @@ +/* Copyright 2013 Donald Stufft and individual contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +size_t crypto_secretbox_keybytes(); +size_t crypto_secretbox_noncebytes(); +size_t crypto_secretbox_zerobytes(); +size_t crypto_secretbox_boxzerobytes(); + + +int crypto_secretbox(unsigned char *c, const unsigned char *m, + unsigned long long mlen, const unsigned char *n, + const unsigned char *k); + +int crypto_secretbox_open(unsigned char *m, const unsigned char *c, + unsigned long long clen, const unsigned char *n, + const unsigned char *k); diff --git a/src/nacl/_lib/crypto_sign.h b/src/nacl/_lib/crypto_sign.h new file mode 100644 index 0000000000000000000000000000000000000000..e401567981b434bd6d112b3f34fb7c16274c2c81 --- /dev/null +++ b/src/nacl/_lib/crypto_sign.h @@ -0,0 +1,33 @@ +/* Copyright 2013 Donald Stufft and individual contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +size_t crypto_sign_bytes(); +// size_t crypto_sign_seedbytes(); +size_t crypto_sign_publickeybytes(); +size_t crypto_sign_secretkeybytes(); + + +int crypto_sign_keypair(unsigned char *pk, unsigned char *sk); + +int crypto_sign_seed_keypair(unsigned char *pk, unsigned char *sk, + const unsigned char *seed); + +int crypto_sign(unsigned char *sm, unsigned long long *smlen, + const unsigned char *m, unsigned long long mlen, + const unsigned char *sk); + +int crypto_sign_open(unsigned char *m, unsigned long long *mlen, + const unsigned char *sm, unsigned long long smlen, + const unsigned char *pk); diff --git a/src/nacl/_lib/randombytes.h b/src/nacl/_lib/randombytes.h new file mode 100644 index 0000000000000000000000000000000000000000..6952fbbe803173407a7633d0958ef3bae365b262 --- /dev/null +++ b/src/nacl/_lib/randombytes.h @@ -0,0 +1,16 @@ +/* Copyright 2013 Donald Stufft and individual contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +void randombytes(unsigned char * const buf, const unsigned long long buf_len); diff --git a/src/nacl/c/__init__.py b/src/nacl/c/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..48b369a40ed57d6dcdc27b31d19be77d17987c71 --- /dev/null +++ b/src/nacl/c/__init__.py @@ -0,0 +1,85 @@ +# Copyright 2013 Donald Stufft and individual contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import, division, print_function + +from nacl.c.crypto_box import ( + crypto_box_SECRETKEYBYTES, crypto_box_PUBLICKEYBYTES, + crypto_box_NONCEBYTES, crypto_box_ZEROBYTES, crypto_box_BOXZEROBYTES, + crypto_box_BEFORENMBYTES, crypto_box_keypair, crypto_box, crypto_box_open, + crypto_box_beforenm, crypto_box_afternm, crypto_box_open_afternm, +) +from nacl.c.crypto_hash import ( + crypto_hash_BYTES, crypto_hash_sha256_BYTES, crypto_hash_sha512_BYTES, + crypto_hash, crypto_hash_sha256, crypto_hash_sha512, +) +from nacl.c.crypto_scalarmult import ( + crypto_scalarmult_BYTES, crypto_scalarmult_SCALARBYTES, + crypto_scalarmult_base, +) +from nacl.c.crypto_secretbox import ( + crypto_secretbox_KEYBYTES, crypto_secretbox_NONCEBYTES, + crypto_secretbox_ZEROBYTES, crypto_secretbox_BOXZEROBYTES, + crypto_secretbox, crypto_secretbox_open, +) +from nacl.c.crypto_sign import ( + crypto_sign_BYTES, crypto_sign_SEEDBYTES, crypto_sign_PUBLICKEYBYTES, + crypto_sign_SECRETKEYBYTES, crypto_sign_keypair, crypto_sign_seed_keypair, + crypto_sign, crypto_sign_open, +) +from nacl.c.randombytes import randombytes + + +__all__ = [ + "crypto_box_SECRETKEYBYTES", + "crypto_box_PUBLICKEYBYTES", + "crypto_box_NONCEBYTES", + "crypto_box_ZEROBYTES", + "crypto_box_BOXZEROBYTES", + "crypto_box_BEFORENMBYTES", + "crypto_box_keypair", + "crypto_box", + "crypto_box_open", + "crypto_box_beforenm", + "crypto_box_afternm", + "crypto_box_open_afternm", + + "crypto_hash_BYTES", + "crypto_hash_sha256_BYTES", + "crypto_hash_sha512_BYTES", + "crypto_hash", + "crypto_hash_sha256", + "crypto_hash_sha512", + + "crypto_scalarmult_BYTES", + "crypto_scalarmult_SCALARBYTES", + "crypto_scalarmult_base", + + "crypto_secretbox_KEYBYTES", + "crypto_secretbox_NONCEBYTES", + "crypto_secretbox_ZEROBYTES", + "crypto_secretbox_BOXZEROBYTES", + "crypto_secretbox", + "crypto_secretbox_open", + + "crypto_sign_BYTES", + "crypto_sign_SEEDBYTES", + "crypto_sign_PUBLICKEYBYTES", + "crypto_sign_SECRETKEYBYTES", + "crypto_sign_keypair", + "crypto_sign_seed_keypair", + "crypto_sign", + "crypto_sign_open", + + "randombytes", +] diff --git a/src/nacl/c/crypto_box.py b/src/nacl/c/crypto_box.py new file mode 100644 index 0000000000000000000000000000000000000000..069ff5c0a3cda089c3bc3ec1041bdce729d39296 --- /dev/null +++ b/src/nacl/c/crypto_box.py @@ -0,0 +1,179 @@ +# Copyright 2013 Donald Stufft and individual contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import, division, print_function + +from nacl._lib import lib +from nacl.exceptions import CryptoError + + +__all__ = ["crypto_box_keypair", "crypto_box"] + + +crypto_box_SECRETKEYBYTES = lib.crypto_box_secretkeybytes() +crypto_box_PUBLICKEYBYTES = lib.crypto_box_publickeybytes() +crypto_box_NONCEBYTES = lib.crypto_box_noncebytes() +crypto_box_ZEROBYTES = lib.crypto_box_zerobytes() +crypto_box_BOXZEROBYTES = lib.crypto_box_boxzerobytes() +crypto_box_BEFORENMBYTES = lib.crypto_box_beforenmbytes() + + +def crypto_box_keypair(): + """ + Returns a randomly generated secret and public key. + + :rtype: (bytes(secret_key), bytes(public_key)) + """ + sk = lib.ffi.new("unsigned char[]", crypto_box_SECRETKEYBYTES) + pk = lib.ffi.new("unsigned char[]", crypto_box_PUBLICKEYBYTES) + + if lib.crypto_box_keypair(pk, sk) != 0: + raise CryptoError("An error occurred trying to generate the keypair") + + return ( + lib.ffi.buffer(sk, crypto_box_SECRETKEYBYTES)[:], + lib.ffi.buffer(pk, crypto_box_PUBLICKEYBYTES)[:], + ) + + +def crypto_box(sk, pk, message, nonce): + """ + Encrypts and returns a message ``message`` using the secret key ``sk``, + public key ``pk``, and the nonce ``nonce``. + + :param sk: bytes + :param pk: bytes + :param message: bytes + :param nonce: bytes + :rtype: bytes + """ + if len(sk) != crypto_box_SECRETKEYBYTES: + raise ValueError("Invalid secret key") + + if len(pk) != crypto_box_PUBLICKEYBYTES: + raise ValueError("Invalid public key") + + if len(nonce) != crypto_box_NONCEBYTES: + raise ValueError("Invalid nonce size") + + padded = (b"\x00" * crypto_box_ZEROBYTES) + message + ciphertext = lib.ffi.new("unsigned char[]", len(padded)) + + if lib.crypto_box(ciphertext, padded, len(padded), nonce, pk, sk) != 0: + raise CryptoError("An error occurred trying to encrypt the message") + + return lib.ffi.buffer(ciphertext, len(padded))[crypto_box_BOXZEROBYTES:] + + +def crypto_box_open(sk, pk, ciphertext, nonce): + """ + Decrypts and returns an encrypted message ``ciphertext``, using the secret + key ``sk``, public key ``pk``, and the nonce ``nonce``. + + :param sk: bytes + :param pk: bytes + :param ciphertext: bytes + :param nonce: bytes + :rtype: bytes + """ + if len(sk) != crypto_box_SECRETKEYBYTES: + raise ValueError("Invalid secret key") + + if len(pk) != crypto_box_PUBLICKEYBYTES: + raise ValueError("Invalid public key") + + if len(nonce) != crypto_box_NONCEBYTES: + raise ValueError("Invalid nonce size") + + padded = (b"\x00" * crypto_box_BOXZEROBYTES) + ciphertext + plaintext = lib.ffi.new("unsigned char[]", len(padded)) + + if lib.crypto_box_open(plaintext, padded, len(padded), nonce, pk, sk) != 0: + raise CryptoError("An error occurred trying to decrypt the message") + + return lib.ffi.buffer(plaintext, len(padded))[crypto_box_ZEROBYTES:] + + +def crypto_box_beforenm(sk, pk): + """ + Computes and returns the shared key for the secret key ``sk`` and the + public key ``pk``. This can be used to speed up operations where the same + set of keys is going to be used multiple times. + + :param sk: bytes + :param pk: bytes + :rtype: bytes + """ + if len(sk) != crypto_box_SECRETKEYBYTES: + raise ValueError("Invalid secret key") + + if len(pk) != crypto_box_PUBLICKEYBYTES: + raise ValueError("Invalid public key") + + k = lib.ffi.new("unsigned char[]", crypto_box_BEFORENMBYTES) + + if lib.crypto_box_beforenm(k, pk, sk) != 0: + raise CryptoError("An error occurred computing the shared key.") + + return lib.ffi.buffer(k, crypto_box_BEFORENMBYTES)[:] + + +def crypto_box_afternm(k, message, nonce): + """ + Encrypts and returns the message ``message`` using the shared key ``k`` and + the nonce ``nonce``. + + :param k: bytes + :param message: bytes + :param nonce: bytes + :rtype: bytes + """ + if len(k) != crypto_box_BEFORENMBYTES: + raise ValueError("Invalid shared key") + + if len(nonce) != crypto_box_NONCEBYTES: + raise ValueError("Invalid nonce") + + padded = b"\x00" * crypto_box_ZEROBYTES + message + ciphertext = lib.ffi.new("unsigned char[]", len(padded)) + + if lib.crypto_box_afternm(ciphertext, padded, len(padded), nonce, k) != 0: + raise CryptoError("An error occurred trying to encrypt the message") + + return lib.ffi.buffer(ciphertext, len(padded))[crypto_box_BOXZEROBYTES:] + + +def crypto_box_open_afternm(k, ciphertext, nonce): + """ + Decrypts and returns the encrypted message ``ciphertext``, using the shared + key ``k`` and the nonce ``nonce``. + + :param k: bytes + :param ciphertext: bytes + :param nonce: bytes + :rtype: bytes + """ + if len(k) != crypto_box_BEFORENMBYTES: + raise ValueError("Invalid shared key") + + if len(nonce) != crypto_box_NONCEBYTES: + raise ValueError("Invalid nonce") + + padded = (b"\x00" * crypto_box_BOXZEROBYTES) + ciphertext + plaintext = lib.ffi.new("unsigned char[]", len(padded)) + + if lib.crypto_box_open_afternm( + plaintext, padded, len(padded), nonce, k) != 0: + raise CryptoError("An error occurred trying to decrypt the message") + + return lib.ffi.buffer(plaintext, len(padded))[crypto_box_ZEROBYTES:] diff --git a/src/nacl/c/crypto_hash.py b/src/nacl/c/crypto_hash.py new file mode 100644 index 0000000000000000000000000000000000000000..de6c3be5025c5c71f830d82f735f484edd5d7dfc --- /dev/null +++ b/src/nacl/c/crypto_hash.py @@ -0,0 +1,62 @@ +# Copyright 2013 Donald Stufft and individual contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import, division, print_function + +from nacl._lib import lib +from nacl.exceptions import CryptoError + + +# crypto_hash_BYTES = lib.crypto_hash_bytes() +crypto_hash_BYTES = lib.crypto_hash_sha512_bytes() +crypto_hash_sha256_BYTES = lib.crypto_hash_sha256_bytes() +crypto_hash_sha512_BYTES = lib.crypto_hash_sha512_bytes() + + +def crypto_hash(message): + """ + Hashes and returns the message ``message``. + + :param message: bytes + :rtype: bytes + """ + digest = lib.ffi.new("unsigned char[]", crypto_hash_BYTES) + if lib.crypto_hash(digest, message, len(message)) != 0: + raise CryptoError("Hashing failed") + return lib.ffi.buffer(digest, crypto_hash_BYTES)[:] + + +def crypto_hash_sha256(message): + """ + Hashes and returns the message ``message``. + + :param message: bytes + :rtype: bytes + """ + digest = lib.ffi.new("unsigned char[]", crypto_hash_sha256_BYTES) + if lib.crypto_hash_sha256(digest, message, len(message)) != 0: + raise CryptoError("Hashing failed") + return lib.ffi.buffer(digest, crypto_hash_sha256_BYTES)[:] + + +def crypto_hash_sha512(message): + """ + Hashes and returns the message ``message``. + + :param message: bytes + :rtype: bytes + """ + digest = lib.ffi.new("unsigned char[]", crypto_hash_sha512_BYTES) + if lib.crypto_hash_sha512(digest, message, len(message)) != 0: + raise CryptoError("Hashing failed") + return lib.ffi.buffer(digest, crypto_hash_sha512_BYTES)[:] diff --git a/src/nacl/c/crypto_scalarmult.py b/src/nacl/c/crypto_scalarmult.py new file mode 100644 index 0000000000000000000000000000000000000000..b94841e49879f3ff19e2b9d2eccca20bc48227bc --- /dev/null +++ b/src/nacl/c/crypto_scalarmult.py @@ -0,0 +1,38 @@ +# Copyright 2013 Donald Stufft and individual contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import, division, print_function + +from nacl._lib import lib +from nacl.exceptions import CryptoError + + +crypto_scalarmult_BYTES = lib.crypto_scalarmult_bytes() +crypto_scalarmult_SCALARBYTES = lib.crypto_scalarmult_scalarbytes() + + +def crypto_scalarmult_base(n): + """ + Computes and returns the scalar product of a standard group element and an + integer ``n``. + + :param n: bytes + :rtype: bytes + """ + q = lib.ffi.new("unsigned char[]", crypto_scalarmult_BYTES) + + if lib.crypto_scalarmult_base(q, n) != 0: + raise CryptoError( + "An error occurred while computing the scalar product") + + return lib.ffi.buffer(q, crypto_scalarmult_SCALARBYTES)[:] diff --git a/src/nacl/c/crypto_secretbox.py b/src/nacl/c/crypto_secretbox.py new file mode 100644 index 0000000000000000000000000000000000000000..94e6a66a35c2c0e78986571f4f070c4417de91af --- /dev/null +++ b/src/nacl/c/crypto_secretbox.py @@ -0,0 +1,76 @@ +# Copyright 2013 Donald Stufft and individual contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import, division, print_function + +from nacl._lib import lib +from nacl.exceptions import CryptoError + + +crypto_secretbox_KEYBYTES = lib.crypto_secretbox_keybytes() +crypto_secretbox_NONCEBYTES = lib.crypto_secretbox_noncebytes() +crypto_secretbox_ZEROBYTES = lib.crypto_secretbox_zerobytes() +crypto_secretbox_BOXZEROBYTES = lib.crypto_secretbox_boxzerobytes() + + +def crypto_secretbox(key, message, nonce): + """ + Encrypts and returns the message ``message`` with the secret ``key`` and + the nonce ``nonce``. + + :param key: bytes + :param message: bytes + :param nonce: bytes + :rtype: bytes + """ + if len(key) != crypto_secretbox_KEYBYTES: + raise ValueError("Invalid key") + + if len(nonce) != crypto_secretbox_NONCEBYTES: + raise ValueError("Invalid nonce") + + padded = b"\x00" * crypto_secretbox_ZEROBYTES + message + ciphertext = lib.ffi.new("unsigned char[]", len(padded)) + + if lib.crypto_secretbox(ciphertext, padded, len(padded), nonce, key) != 0: + raise CryptoError("Encryption failed") + + ciphertext = lib.ffi.buffer(ciphertext, len(padded)) + return ciphertext[crypto_secretbox_BOXZEROBYTES:] + + +def crypto_secretbox_open(key, ciphertext, nonce): + """ + Decrypt and returns the encrypted message ``ciphertext`` with the secret + ``key`` and the nonce ``nonce``. + + :param key: bytes + :param ciphertext: bytes + :param nonce: bytes + :rtype: bytes + """ + if len(key) != crypto_secretbox_KEYBYTES: + raise ValueError("Invalid key") + + if len(nonce) != crypto_secretbox_NONCEBYTES: + raise ValueError("Invalid nonce") + + padded = b"\x00" * crypto_secretbox_BOXZEROBYTES + ciphertext + plaintext = lib.ffi.new("unsigned char[]", len(padded)) + + if lib.crypto_secretbox_open( + plaintext, padded, len(padded), nonce, key) != 0: + raise CryptoError("Decryption failed. Ciphertext failed verification") + + plaintext = lib.ffi.buffer(plaintext, len(padded)) + return plaintext[crypto_secretbox_ZEROBYTES:] diff --git a/src/nacl/c/crypto_sign.py b/src/nacl/c/crypto_sign.py new file mode 100644 index 0000000000000000000000000000000000000000..7286de062889e0743feecb5e4c730af91df49397 --- /dev/null +++ b/src/nacl/c/crypto_sign.py @@ -0,0 +1,101 @@ +# Copyright 2013 Donald Stufft and individual contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import, division, print_function + +from nacl._lib import lib +from nacl.exceptions import BadSignatureError, CryptoError + + +crypto_sign_BYTES = lib.crypto_sign_bytes() +# crypto_sign_SEEDBYTES = lib.crypto_sign_seedbytes() +crypto_sign_SEEDBYTES = lib.crypto_sign_secretkeybytes() // 2 +crypto_sign_PUBLICKEYBYTES = lib.crypto_sign_publickeybytes() +crypto_sign_SECRETKEYBYTES = lib.crypto_sign_secretkeybytes() + + +def crypto_sign_keypair(): + """ + Returns a randomly generated secret key and public key. + + :rtype: (bytes(secret_key), bytes(public_key)) + """ + sk = lib.ffi.new("unsigned char[]", crypto_sign_SECRETKEYBYTES) + pk = lib.ffi.new("unsigned char[]", crypto_sign_PUBLICKEYBYTES) + + if lib.crypto_sign_keypair(pk, sk) != 0: + raise CryptoError("An error occurred while generating keypairs") + + return ( + lib.ffi.buffer(sk, crypto_sign_SECRETKEYBYTES)[:], + lib.ffi.buffer(pk, crypto_sign_PUBLICKEYBYTES)[:], + ) + + +def crypto_sign_seed_keypair(seed): + """ + Computes and returns the secret key and public key using the seed ``seed``. + + :param seed: bytes + :rtype: (bytes(secret_key), bytes(public_key)) + """ + if len(seed) != crypto_sign_SEEDBYTES: + raise ValueError("Invalid seed") + + sk = lib.ffi.new("unsigned char[]", crypto_sign_SECRETKEYBYTES) + pk = lib.ffi.new("unsigned char[]", crypto_sign_PUBLICKEYBYTES) + + if lib.crypto_sign_seed_keypair(pk, sk, seed) != 0: + raise CryptoError("An error occured while generating keypairs") + + return ( + lib.ffi.buffer(sk, crypto_sign_SECRETKEYBYTES)[:], + lib.ffi.buffer(pk, crypto_sign_PUBLICKEYBYTES)[:], + ) + + +def crypto_sign(sk, message): + """ + Signs the message ``message`` using the secret key ``sk`` and returns the + signed message. + + :param sk: bytes + :param message: bytes + :rtype: bytes + """ + signed = lib.ffi.new("unsigned char[]", len(message) + crypto_sign_BYTES) + signed_len = lib.ffi.new("unsigned long long *") + + if lib.crypto_sign(signed, signed_len, message, len(message), sk) != 0: + raise CryptoError("Failed to sign the message") + + return lib.ffi.buffer(signed, signed_len[0])[:] + + +def crypto_sign_open(pk, signed): + """ + Verifies the signature of the signed message ``signed`` using the public + key ``pkg`` and returns the unsigned message. + + :param pk: bytes + :param signed: bytes + :rtype: bytes + """ + message = lib.ffi.new("unsigned char[]", len(signed)) + message_len = lib.ffi.new("unsigned long long *") + + if lib.crypto_sign_open( + message, message_len, signed, len(signed), pk) != 0: + raise BadSignatureError("Signature was forged or corrupt") + + return lib.ffi.buffer(message, message_len[0])[:] diff --git a/src/nacl/c/randombytes.py b/src/nacl/c/randombytes.py new file mode 100644 index 0000000000000000000000000000000000000000..d6f01f5157bf62c1a96db27e4e4d30ca945f2e98 --- /dev/null +++ b/src/nacl/c/randombytes.py @@ -0,0 +1,29 @@ +# Copyright 2013 Donald Stufft and individual contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import, division, print_function + +from nacl._lib import lib + + +def randombytes(size): + """ + Returns ``size`` number of random bytes from a cryptographically secure + random source. + + :param size: int + :rtype: bytes + """ + buf = lib.ffi.new("unsigned char[]", size) + lib.randombytes(buf, size) + return lib.ffi.buffer(buf, size)[:] diff --git a/src/nacl/exceptions.py b/src/nacl/exceptions.py index a50656b63e47faff20d4ed3d602b54a492f4ceca..ae8630f5ebb3845921a34def820b7afc642c7b5d 100644 --- a/src/nacl/exceptions.py +++ b/src/nacl/exceptions.py @@ -19,3 +19,9 @@ class CryptoError(Exception): """ Base exception for all nacl related errors """ + + +class BadSignatureError(CryptoError): + """ + Raised when the signature was forged or otherwise corrupt. + """ diff --git a/src/nacl/hash.py b/src/nacl/hash.py index a6f7638111d10dd39eb9b90e696de56925f1103d..61f2f884de9601955a173b8142fc788a9351e962 100644 --- a/src/nacl/hash.py +++ b/src/nacl/hash.py @@ -14,23 +14,13 @@ from __future__ import absolute_import from __future__ import division -from . import nacl, encoding -from .exceptions import CryptoError +import nacl.c +import nacl.encoding -def sha256(message, encoder=encoding.HexEncoder): - digest = nacl.ffi.new("unsigned char[]", nacl.lib.crypto_hash_sha256_BYTES) - if not nacl.lib.crypto_hash_sha256(digest, message, len(message)): - raise CryptoError("Hashing failed") - digest = nacl.ffi.buffer(digest, nacl.lib.crypto_hash_sha256_BYTES)[:] +def sha256(message, encoder=nacl.encoding.HexEncoder): + return encoder.encode(nacl.c.crypto_hash_sha256(message)) - return encoder.encode(digest) - -def sha512(message, encoder=encoding.HexEncoder): - digest = nacl.ffi.new("unsigned char[]", nacl.lib.crypto_hash_sha512_BYTES) - if not nacl.lib.crypto_hash_sha512(digest, message, len(message)): - raise CryptoError("Hashing failed") - digest = nacl.ffi.buffer(digest, nacl.lib.crypto_hash_sha512_BYTES)[:] - - return encoder.encode(digest) +def sha512(message, encoder=nacl.encoding.HexEncoder): + return encoder.encode(nacl.c.crypto_hash_sha512(message)) diff --git a/src/nacl/nacl.py b/src/nacl/nacl.py deleted file mode 100644 index 35ee3010c095b2669e3b59fbbb4d2bccbe1b9518..0000000000000000000000000000000000000000 --- a/src/nacl/nacl.py +++ /dev/null @@ -1,178 +0,0 @@ -# Copyright 2013 Donald Stufft and individual contributors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -CFFI interface to NaCl and libsodium library -""" -from __future__ import absolute_import -from __future__ import division - -import functools - -# We need to patch cffi before importing it -from nacl import _cffi_fix - -import cffi.verifier - -from cffi import FFI - - -__all__ = ["ffi", "lib"] - - -ffi = FFI() -ffi.cdef( - # Secret Key Encryption - """ - static const int crypto_secretbox_KEYBYTES; - static const int crypto_secretbox_NONCEBYTES; - static const int crypto_secretbox_ZEROBYTES; - static const int crypto_secretbox_BOXZEROBYTES; - - int crypto_secretbox(unsigned char *c, const unsigned char *m, unsigned long long mlen, const unsigned char *n, const unsigned char *k); - int crypto_secretbox_open(unsigned char *m, const unsigned char *c, unsigned long long clen, const unsigned char *n, const unsigned char *k); - """ - - # Public Key Encryption - Signatures - """ - static const int crypto_sign_PUBLICKEYBYTES; - static const int crypto_sign_SECRETKEYBYTES; - static const int crypto_sign_BYTES; - - int crypto_sign_seed_keypair(unsigned char *pk, unsigned char *sk, unsigned char *seed); - int crypto_sign(unsigned char *sm, unsigned long long *smlen, const unsigned char *m, unsigned long long mlen, const unsigned char *sk); - int crypto_sign_open(unsigned char *m, unsigned long long *mlen, const unsigned char *sm, unsigned long long smlen, const unsigned char *pk); - """ - - # Public Key Encryption - """ - static const int crypto_box_PUBLICKEYBYTES; - static const int crypto_box_SECRETKEYBYTES; - static const int crypto_box_BEFORENMBYTES; - static const int crypto_box_NONCEBYTES; - static const int crypto_box_ZEROBYTES; - static const int crypto_box_BOXZEROBYTES; - - int crypto_box_keypair(unsigned char *pk, unsigned char *sk); - int crypto_box_afternm(unsigned char *c, const unsigned char *m, unsigned long long mlen, const unsigned char *n, const unsigned char *k); - int crypto_box_open_afternm(unsigned char *m, const unsigned char *c, unsigned long long clen, const unsigned char *n, const unsigned char *k); - int crypto_box_beforenm(unsigned char *k, const unsigned char *pk, const unsigned char *sk); - """ - - # Hashing - """ - static const int crypto_hash_BYTES; - static const int crypto_hash_sha256_BYTES; - static const int crypto_hash_sha512_BYTES; - - int crypto_hash(unsigned char *out, const unsigned char *in, unsigned long long inlen); - int crypto_hash_sha256(unsigned char *out, const unsigned char *in, unsigned long long inlen); - int crypto_hash_sha512(unsigned char *out, const unsigned char *in, unsigned long long inlen); - """ - - # Secure Random - """ - void randombytes(unsigned char * const buf, const unsigned long long buf_len); - """ - - # Low Level - Scalar Multiplication - """ - int crypto_scalarmult_curve25519_base(unsigned char *q, const unsigned char *n); - """ -) - - -ffi.verifier = cffi.verifier.Verifier(ffi, - "#include <sodium.h>", - - # We need to link to the sodium library - libraries=["sodium"], - - # Our ext_package is nacl so look for it - ext_package="nacl", -) - - -# This works around a bug in PyPy where CFFI exposed functions do not have a -# __name__ attribute. See https://bugs.pypy.org/issue1452 -def wraps(wrapped): - def inner(func): - if hasattr(wrapped, "__name__"): - return functools.wraps(wrapped)(func) - else: - return func - return inner - - -# A lot of the functions in nacl return 0 for success and a negative integer -# for failure. This is inconvenient in Python as 0 is a falsey value while -# negative integers are truthy. This wrapper has them return True/False as -# you'd expect in Python -def wrap_nacl_function(func): - @wraps(func) - def wrapper(*args, **kwargs): - ret = func(*args, **kwargs) - return ret == 0 - return wrapper - - -class Library(object): - - wrap = [ - "crypto_secretbox", - "crypto_secretbox_open", - - "crypto_sign_seed_keypair", - "crypto_sign", - "crypto_sign_open", - - "crypto_box_keypair", - "crypto_box_afternm", - "crypto_box_open_afternm", - "crypto_box_beforenm", - - "crypto_hash", - "crypto_hash_sha256", - "crypto_hash_sha512", - - "crypto_scalarmult_curve25519_base", - ] - - def __init__(self, ffi): - self._ffi = ffi - self._initalized = False - - # This prevents the compile_module() from being called, the module - # should have been compiled by setup.py - def _compile_module(*args, **kwargs): - raise RuntimeError("Cannot compile module during runtime") - self._ffi.verifier.compile_module = _compile_module - - def __getattr__(self, name): - if not self._initalized: - self._lib = self._ffi.verifier.load_library() - - # redirect attribute access to the underlying lib - attr = getattr(self._lib, name) - - # If this is a function that we're wrapping do the actual wrapping - if name in self.wrap: - attr = wrap_nacl_function(attr) - - # Go ahead and assign the returned value to this class so we don't - # need to do this lookup again - setattr(self, name, attr) - - return attr - -lib = Library(ffi) diff --git a/src/nacl/public.py b/src/nacl/public.py index b9604b1088673e76f141c2752c9b396ba3d88a2a..eb083116abbd32de1fd3440e52dc0807bd90ca2a 100644 --- a/src/nacl/public.py +++ b/src/nacl/public.py @@ -14,9 +14,11 @@ from __future__ import absolute_import from __future__ import division -from . import nacl, encoding -from .exceptions import CryptoError -from .utils import EncryptedMessage, StringFixer, random +import nacl.c +import nacl.c.crypto_box + +from nacl import encoding +from nacl.utils import EncryptedMessage, StringFixer, random class PublicKey(encoding.Encodable, StringFixer, object): @@ -30,7 +32,7 @@ class PublicKey(encoding.Encodable, StringFixer, object): :cvar SIZE: The size that the public key is required to be """ - SIZE = nacl.lib.crypto_box_PUBLICKEYBYTES + SIZE = nacl.c.crypto_box_PUBLICKEYBYTES def __init__(self, public_key, encoder=encoding.RawEncoder): self._public_key = encoder.decode(public_key) @@ -58,7 +60,7 @@ class PrivateKey(encoding.Encodable, StringFixer, object): :cvar SIZE: The size that the private key is required to be """ - SIZE = nacl.lib.crypto_box_SECRETKEYBYTES + SIZE = nacl.c.crypto_box_SECRETKEYBYTES def __init__(self, private_key, encoder=encoding.RawEncoder): # Decode the secret_key @@ -69,15 +71,10 @@ class PrivateKey(encoding.Encodable, StringFixer, object): raise ValueError( "The secret key must be exactly %d bytes long" % self.SIZE) - pk = nacl.ffi.new("unsigned char[]", PublicKey.SIZE) - - if not nacl.lib.crypto_scalarmult_curve25519_base(pk, private_key): - raise CryptoError("Failed to generate a key pair") - - _pkey = nacl.ffi.buffer(pk, nacl.lib.crypto_box_PUBLICKEYBYTES)[:] + raw_public_key = nacl.c.crypto_scalarmult_base(private_key) self._private_key = private_key - self.public_key = PublicKey(_pkey) + self.public_key = PublicKey(raw_public_key) def __bytes__(self): return self._private_key @@ -112,21 +109,14 @@ class Box(encoding.Encodable, StringFixer, object): :cvar NONCE_SIZE: The size that the nonce is required to be. """ - NONCE_SIZE = nacl.lib.crypto_box_NONCEBYTES + NONCE_SIZE = nacl.c.crypto_box_NONCEBYTES def __init__(self, private_key, public_key): if private_key and public_key: - _shared_key_size = nacl.lib.crypto_box_BEFORENMBYTES - _shared_key = nacl.ffi.new("unsigned char[]", _shared_key_size) - - if not nacl.lib.crypto_box_beforenm( - _shared_key, - public_key.encode(encoder=encoding.RawEncoder), - private_key.encode(encoder=encoding.RawEncoder), - ): - raise CryptoError("Failed to derive shared key") - - self._shared_key = nacl.ffi.buffer(_shared_key, _shared_key_size)[:] + self._shared_key = nacl.c.crypto_box_beforenm( + private_key.encode(encoder=encoding.RawEncoder), + public_key.encode(encoder=encoding.RawEncoder), + ) else: self._shared_key = None @@ -161,20 +151,11 @@ class Box(encoding.Encodable, StringFixer, object): raise ValueError("The nonce must be exactly %s bytes long" % self.NONCE_SIZE) - padded = b"\x00" * nacl.lib.crypto_box_ZEROBYTES + plaintext - ciphertext = nacl.ffi.new("unsigned char[]", len(padded)) - - if not nacl.lib.crypto_box_afternm( - ciphertext, - padded, - len(padded), - nonce, - self._shared_key, - ): - raise CryptoError("Encryption failed") - - box_zeros = nacl.lib.crypto_box_BOXZEROBYTES - ciphertext = nacl.ffi.buffer(ciphertext, len(padded))[box_zeros:] + ciphertext = nacl.c.crypto_box_afternm( + self._shared_key, + plaintext, + nonce, + ) encoded_nonce = encoder.encode(nonce) encoded_ciphertext = encoder.encode(ciphertext) @@ -208,20 +189,10 @@ class Box(encoding.Encodable, StringFixer, object): raise ValueError("The nonce must be exactly %s bytes long" % self.NONCE_SIZE) - padded = b"\x00" * nacl.lib.crypto_box_BOXZEROBYTES + ciphertext - plaintext = nacl.ffi.new("unsigned char[]", len(padded)) - - if not nacl.lib.crypto_box_open_afternm( - plaintext, - padded, - len(padded), - nonce, - self._shared_key, - ): - raise CryptoError( - "Decryption failed. Ciphertext failed verification") - - box_zeros = nacl.lib.crypto_box_ZEROBYTES - plaintext = nacl.ffi.buffer(plaintext, len(padded))[box_zeros:] + plaintext = nacl.c.crypto_box_open_afternm( + self._shared_key, + ciphertext, + nonce, + ) return plaintext diff --git a/src/nacl/secret.py b/src/nacl/secret.py index e0b4e113aaff24835a1e131b559bf0809b0302d8..31701f44fd4573b3f3ba984008b3baefa940483e 100644 --- a/src/nacl/secret.py +++ b/src/nacl/secret.py @@ -14,8 +14,9 @@ from __future__ import absolute_import from __future__ import division -from . import nacl, encoding -from .exceptions import CryptoError +import nacl.c + +from . import encoding from .utils import EncryptedMessage, StringFixer @@ -39,8 +40,8 @@ class SecretBox(encoding.Encodable, StringFixer, object): :cvar NONCE_SIZE: The size that the nonce is required to be. """ - KEY_SIZE = nacl.lib.crypto_secretbox_KEYBYTES - NONCE_SIZE = nacl.lib.crypto_secretbox_NONCEBYTES + KEY_SIZE = nacl.c.crypto_secretbox_KEYBYTES + NONCE_SIZE = nacl.c.crypto_secretbox_NONCEBYTES def __init__(self, key, encoder=encoding.RawEncoder): key = encoder.decode(key) @@ -72,18 +73,9 @@ class SecretBox(encoding.Encodable, StringFixer, object): """ if len(nonce) != self.NONCE_SIZE: raise ValueError("The nonce must be exactly %s bytes long" % - nacl.lib.crypto_secretbox_NONCEBYTES) - - padded = b"\x00" * nacl.lib.crypto_secretbox_ZEROBYTES + plaintext - ciphertext = nacl.ffi.new("unsigned char[]", len(padded)) - - if not nacl.lib.crypto_secretbox( - ciphertext, padded, len(padded), nonce, self._key, - ): - raise CryptoError("Encryption failed") + self.NONCE_SIZE) - box_zeros = nacl.lib.crypto_secretbox_BOXZEROBYTES - ciphertext = nacl.ffi.buffer(ciphertext, len(padded))[box_zeros:] + ciphertext = nacl.c.crypto_secretbox(self._key, plaintext, nonce) encoded_nonce = encoder.encode(nonce) encoded_ciphertext = encoder.encode(ciphertext) @@ -115,18 +107,8 @@ class SecretBox(encoding.Encodable, StringFixer, object): if len(nonce) != self.NONCE_SIZE: raise ValueError("The nonce must be exactly %s bytes long" % - nacl.lib.crypto_secretbox_NONCEBYTES) - - padded = b"\x00" * nacl.lib.crypto_secretbox_BOXZEROBYTES + ciphertext - plaintext = nacl.ffi.new("unsigned char[]", len(padded)) - - if not nacl.lib.crypto_secretbox_open( - plaintext, padded, len(padded), nonce, self._key, - ): - raise CryptoError( - "Decryption failed. Ciphertext failed verification") + self.NONCE_SIZE) - box_zeros = nacl.lib.crypto_secretbox_ZEROBYTES - plaintext = nacl.ffi.buffer(plaintext, len(padded))[box_zeros:] + plaintext = nacl.c.crypto_secretbox_open(self._key, ciphertext, nonce) return plaintext diff --git a/src/nacl/signing.py b/src/nacl/signing.py index 8ee774160f41588a073534349ede6ec0fe2f238b..036b4d76466a55c1ce1ea0fadec40faa5ccf44ac 100644 --- a/src/nacl/signing.py +++ b/src/nacl/signing.py @@ -16,15 +16,10 @@ from __future__ import division import six -from . import nacl, encoding -from .exceptions import CryptoError -from .utils import StringFixer, random - +import nacl.c -class BadSignatureError(CryptoError): - """ - Raised when the signature was forged or otherwise corrupt. - """ +from . import encoding +from .utils import StringFixer, random class SignedMessage(six.binary_type): @@ -68,9 +63,9 @@ class VerifyKey(encoding.Encodable, StringFixer, object): # Decode the key key = encoder.decode(key) - if len(key) != nacl.lib.crypto_sign_PUBLICKEYBYTES: + if len(key) != nacl.c.crypto_sign_PUBLICKEYBYTES: raise ValueError("The key must be exactly %s bytes long" % - nacl.lib.crypto_sign_PUBLICKEYBYTES) + nacl.c.crypto_sign_PUBLICKEYBYTES) self._key = key @@ -99,13 +94,7 @@ class VerifyKey(encoding.Encodable, StringFixer, object): # Decode the signed message smessage = encoder.decode(smessage) - message = nacl.ffi.new("unsigned char[]", len(smessage)) - message_len = nacl.ffi.new("unsigned long long *") - - if not nacl.lib.crypto_sign_open(message, message_len, smessage, len(smessage), self._key): - raise BadSignatureError("Signature was forged or corrupt") - - return nacl.ffi.buffer(message, message_len[0])[:] + return nacl.c.crypto_sign_open(self._key, smessage) class SigningKey(encoding.Encodable, StringFixer, object): @@ -132,23 +121,15 @@ class SigningKey(encoding.Encodable, StringFixer, object): seed = encoder.decode(seed) # Verify that our seed is the proper size - seed_size = nacl.lib.crypto_sign_SECRETKEYBYTES // 2 - if len(seed) != seed_size: - raise ValueError( - 'The seed must be exactly %d bytes long' % (seed_size,)) - - pk = nacl.ffi.new("unsigned char[]", nacl.lib.crypto_sign_PUBLICKEYBYTES) - sk = nacl.ffi.new("unsigned char[]", nacl.lib.crypto_sign_SECRETKEYBYTES) + if len(seed) != nacl.c.crypto_sign_SEEDBYTES: + raise ValueError("The seed must be exactly %d bytes long" % + nacl.c.crypto_sign_SEEDBYTES) - if not nacl.lib.crypto_sign_seed_keypair(pk, sk, seed): - raise CryptoError("Failed to generate a key pair") + secret_key, public_key = nacl.c.crypto_sign_seed_keypair(seed) - # Secret values self._seed = seed - self._signing_key = nacl.ffi.buffer(sk, nacl.lib.crypto_sign_SECRETKEYBYTES)[:] - - # Public values - self.verify_key = VerifyKey(nacl.ffi.buffer(pk, nacl.lib.crypto_sign_PUBLICKEYBYTES)[:]) + self._signing_key = secret_key + self.verify_key = VerifyKey(public_key) def __bytes__(self): return self._seed @@ -160,9 +141,10 @@ class SigningKey(encoding.Encodable, StringFixer, object): :rtype: :class:`~nacl.signing.SigningKey` """ - return cls(random(nacl.lib.crypto_sign_SECRETKEYBYTES // 2), - encoder=encoding.RawEncoder, - ) + return cls( + random(nacl.c.crypto_sign_SEEDBYTES), + encoder=encoding.RawEncoder, + ) def sign(self, message, encoder=encoding.RawEncoder): """ @@ -172,16 +154,10 @@ class SigningKey(encoding.Encodable, StringFixer, object): :param encoder: A class that is used to encode the signed message. :rtype: :class:`~nacl.signing.SignedMessage` """ - sm = nacl.ffi.new("unsigned char[]", len(message) + nacl.lib.crypto_sign_BYTES) - smlen = nacl.ffi.new("unsigned long long *") - - if not nacl.lib.crypto_sign(sm, smlen, message, len(message), self._signing_key): - raise CryptoError("Failed to sign the message") - - raw_signed = nacl.ffi.buffer(sm, smlen[0])[:] + raw_signed = nacl.c.crypto_sign(self._signing_key, message) - signature = encoder.encode(raw_signed[:nacl.lib.crypto_sign_BYTES]) - message = encoder.encode(raw_signed[nacl.lib.crypto_sign_BYTES:]) + signature = encoder.encode(raw_signed[:nacl.c.crypto_sign_BYTES]) + message = encoder.encode(raw_signed[nacl.c.crypto_sign_BYTES:]) signed = encoder.encode(raw_signed) return SignedMessage._from_parts(signature, message, signed) diff --git a/src/nacl/utils.py b/src/nacl/utils.py index 98c41891364e46744f977b95697b8c512b03fae4..61b901cb0c47df3d1a703324cb2f3acdabaad0d0 100644 --- a/src/nacl/utils.py +++ b/src/nacl/utils.py @@ -16,7 +16,7 @@ from __future__ import division import six -from . import nacl +import nacl.c class EncryptedMessage(six.binary_type): @@ -57,6 +57,4 @@ class StringFixer(object): def random(size=32): - data = nacl.ffi.new("unsigned char[]", size) - nacl.lib.randombytes(data, size) - return nacl.ffi.buffer(data, size)[:] + return nacl.c.randombytes(size) diff --git a/tests/test_signing.py b/tests/test_signing.py index 92ca3f043108551020a2309a0504886bf1b04e36..803f3617b006fe9aaba889ad722307d93beb16f6 100644 --- a/tests/test_signing.py +++ b/tests/test_signing.py @@ -21,7 +21,7 @@ import pytest import nacl.signing import nacl.encoding -import nacl.nacl +import nacl.exceptions def ed25519_known_answers(): @@ -86,9 +86,9 @@ class TestVerifyKey: # Small sanity check assert skey.verify_key.verify(smessage) - with pytest.raises(nacl.signing.BadSignatureError): + with pytest.raises(nacl.exceptions.BadSignatureError): skey.verify_key.verify(message, signature) - with pytest.raises(nacl.signing.BadSignatureError): + with pytest.raises(nacl.exceptions.BadSignatureError): forged = nacl.signing.SignedMessage(signature + message) skey.verify_key.verify(forged)