diff --git a/docs/index.rst b/docs/index.rst index 6be9babe475bb5974449dd8b13fb582799513859..4730603d5e08d661905b2ade98b3ade53c97c928 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,6 +6,7 @@ Contents: .. toctree:: :maxdepth: 2 + secret signing diff --git a/docs/secret.rst b/docs/secret.rst new file mode 100644 index 0000000000000000000000000000000000000000..1794ac9461eeddff6bcd616d26bbb31d71eb9a2e --- /dev/null +++ b/docs/secret.rst @@ -0,0 +1,70 @@ +Secret Key Encryption +===================== + +Secret key encryption is analogous to a safe. You can store something secret +through it and anyone who has the key can open it and view the contents. +:class:`~nacl.secret.SecretBox` functions as just such a safe, and like any +good safe any attempts to tamper with the contents is easily detected. + +Secret Key Encryption allows you to store or transmit data over insecure +channels without leaking the contents of that message, nor anything about it +other than the length. + +Example +------- + +.. code:: python + + import nacl + import nacl.secret + + # This must be kept secret, this is the combination to your safe + key = nacl.random(nacl.secret.SecretBox.KEY_SIZE) + + # This is your safe, you can use it to encrypt or decrypt messages + box = nacl.secret.SecretBox(key) + + # This is our message to send, it must be a bytestring as SecretBox will + # treat is as just a binary blob of data. + message = b"The president will be exiting through the lower levels" + + # This is a nonce, it *MUST* only be used once, but it is not considered + # secret and can be transmitted or stored alongside the ciphertext. A + # good source of nonce is just 24 random bytes. + nonce = nacl.random(24) + + # Encrypt our message, it will be exactly 16 bytes longer than the original + # message as it stores authentication information alongside it. + ciphertext = box.encrypt(message, nonce) + + # Decrypt our message, an exception will be raised if the encryption was + # tampered with or there was otherwise an error. + plaintext = box.decrypt(ciphertext, nonce) + + +Usage Information +----------------- + +* :class:`~nacl.secret.SecretBox` requires a 32 byte key that must be kept + secret. It is the combination to your "safe" and anyone with this key will + be able to decrypt the data. +* :class:`~nacl.secret.SecretBox` requires a new 24 bytes nonce with every + encrypted message. This nonce is *not* secret and be freely transfered or + stored in plaintext alongside the ciphertext. However it is absolutely + imperative that you **NEVER** reuse a nonce with the same key. Reusing the + nonce with the same key provides enough information for an attacker + to decrypt any and all messages made with your key. + + +Reference +--------- + +.. autoclass:: nacl.secret.SecretBox + :members: + + +Algorithm details +----------------- + +:Encryption: `Salsa20 steam cipher <https://en.wikipedia.org/wiki/Salsa20>`_ +:Authentication: `Poly1305 MAC <https://en.wikipedia.org/wiki/Poly1305-AES>`_ diff --git a/nacl/secret.py b/nacl/secret.py index 56924f8a6e5d653b8bac7b8e36d6dc02bbc27c92..c76029de7529d52d0b9fcb81b80d8398c9d9b12f 100644 --- a/nacl/secret.py +++ b/nacl/secret.py @@ -8,8 +8,27 @@ from .exceptions import CryptoError class SecretBox(encoding.Encodable, six.StringFixer, object): + """ + The SecretBox class encrypted and decrypts messages using the given secret + key. + + The ciphertexts generated by :class:`~nacl.secret.Secretbox` include a 16 + byte authenticator which is checked as part of the decryption. An invalid + authenticator will cause the decrypt function to raise an exception. The + authenticator is not a signature. Once you've decrypted the message you've + demonstrated the ability to create arbitrary valid message, so messages you + send are repudiable. For non-repudiable messages, sign them after + encryption. + + :param key: The secret key used to encrypt and decrypt messages + :param encoder: The encoder class used to decode the given key + + :cvar KEY_SIZE: The size that the key is required to be. + :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 def __init__(self, key, encoder=encoding.RawEncoder): key = encoder.decode(key) @@ -24,7 +43,22 @@ class SecretBox(encoding.Encodable, six.StringFixer, object): return self._key def encrypt(self, plaintext, nonce, encoder=encoding.RawEncoder): - if len(nonce) != nacl.lib.crypto_secretbox_NONCEBYTES: + """ + Encrypts the plaintext message using the given nonce and returns the + ciphertext encoded with the encoder. + + .. warning:: It is **VITALLY** important that the nonce is a nonce, + i.e. itis a number used only once for any given key. If you fail to + do this, you compromise the privacy of the messages encrypted. Give + your nonces a different prefix, or have one side use an odd counter + and one an even counter. Just make sure they are different. + + :param plaintext: [:class:`bytes`] The plaintext message to encrypt + :param nonce: [:class:`bytes`] The nonce to use in the encryption + :param encoder: The encoder to use to encode the ciphertext + :rtype: [:class:`bytes`] + """ + if len(nonce) != self.NONCE_SIZE: raise ValueError("The nonce must be exactly %s bytes long" % nacl.lib.crypto_secretbox_NONCEBYTES) @@ -42,7 +76,17 @@ class SecretBox(encoding.Encodable, six.StringFixer, object): return encoder.encode(ciphertext) def decrypt(self, ciphertext, nonce, encoder=encoding.RawEncoder): - if len(nonce) != nacl.lib.crypto_secretbox_NONCEBYTES: + """ + Decrypts the ciphertext using the given nonce and returns the plaintext + message. + + :param ciphertext: [:class:`bytes`] The encrypted message to decrypt + :param nonce: [:class:`bytes`] The nonce used when encrypting the + ciphertext + :param encoder: The encoder used to decode the ciphertext. + :rtype: [:class:`bytes`] + """ + if len(nonce) != self.NONCE_SIZE: raise ValueError("The nonce must be exactly %s bytes long" % nacl.lib.crypto_secretbox_NONCEBYTES)