diff --git a/nacl/nacl.py b/nacl/nacl.py index 3b53c74edb03aea39ce6ddb9bf5baa279fc2ade1..dd3e1f156afc85af910193639fa58b901034e80f 100644 --- a/nacl/nacl.py +++ b/nacl/nacl.py @@ -14,7 +14,16 @@ __all__ = ["ffi", "lib"] ffi = FFI() ffi.cdef( - # pylint: disable=C0301 + # 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 """ @@ -59,6 +68,8 @@ def wrap_nacl_function(func): return ret == 0 return wrapper +lib.crypto_secretbox = wrap_nacl_function(lib.crypto_secretbox) +lib.crypto_secretbox_open = wrap_nacl_function(lib.crypto_secretbox_open) lib.crypto_sign_seed_keypair = wrap_nacl_function(lib.crypto_sign_seed_keypair) lib.crypto_sign = wrap_nacl_function(lib.crypto_sign) diff --git a/nacl/secret.py b/nacl/secret.py new file mode 100644 index 0000000000000000000000000000000000000000..56924f8a6e5d653b8bac7b8e36d6dc02bbc27c92 --- /dev/null +++ b/nacl/secret.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import +from __future__ import division + +from . import six + +from . import nacl, encoding +from .exceptions import CryptoError + + +class SecretBox(encoding.Encodable, six.StringFixer, object): + + KEY_SIZE = nacl.lib.crypto_secretbox_KEYBYTES + + def __init__(self, key, encoder=encoding.RawEncoder): + key = encoder.decode(key) + + if len(key) != self.KEY_SIZE: + raise ValueError("The key must be exactly %s bytes long" % + nacl.lib.crypto_secretbox_KEYBYTES) + + self._key = key + + def __bytes__(self): + return self._key + + def encrypt(self, plaintext, nonce, encoder=encoding.RawEncoder): + if len(nonce) != nacl.lib.crypto_secretbox_NONCEBYTES: + 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") + + box_zeros = nacl.lib.crypto_secretbox_BOXZEROBYTES + ciphertext = nacl.ffi.buffer(ciphertext, len(padded))[box_zeros:] + + return encoder.encode(ciphertext) + + def decrypt(self, ciphertext, nonce, encoder=encoding.RawEncoder): + if len(nonce) != nacl.lib.crypto_secretbox_NONCEBYTES: + raise ValueError("The nonce must be exactly %s bytes long" % + nacl.lib.crypto_secretbox_NONCEBYTES) + + ciphertext = encoder.decode(ciphertext) + + 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") + + box_zeros = nacl.lib.crypto_secretbox_ZEROBYTES + plaintext = nacl.ffi.buffer(plaintext, len(padded))[box_zeros:] + + return plaintext