From eaa362c1fc6aad19140b0517f32d92b9baacab83 Mon Sep 17 00:00:00 2001 From: Donald Stufft <donald@stufft.io> Date: Sat, 9 Mar 2013 23:29:51 -0500 Subject: [PATCH] Add secret key encryption via Salsa20 + Poly1305 * Adds nacl.secret.SecretBox which represents a secret key. * nacl.secret.SecretBox().encrypt can be used to encrypt a message * nacl.secret.Secretbox().decrypt can be used to decrypt a message --- nacl/nacl.py | 13 ++++++++++- nacl/secret.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 nacl/secret.py diff --git a/nacl/nacl.py b/nacl/nacl.py index 3b53c74e..dd3e1f15 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 00000000..56924f8a --- /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 -- GitLab