Skip to content
Snippets Groups Projects
Commit be4fa256 authored by Donald Stufft's avatar Donald Stufft
Browse files

Merge pull request #20 from dstufft/inline-nonces

Encrypted Messages + Nonces
parents 64df1344 4e934b4d
No related branches found
No related tags found
No related merge requests found
...@@ -67,16 +67,16 @@ with equal that from (pkbob, skalice). This is how the system works: ...@@ -67,16 +67,16 @@ with equal that from (pkbob, skalice). This is how the system works:
# good source of nonce is just 24 random bytes. # good source of nonce is just 24 random bytes.
nonce = nacl.utils.random(Box.NONCE_SIZE) nonce = nacl.utils.random(Box.NONCE_SIZE)
# Encrypt our message, it will be exactly 16 bytes longer than the original # Encrypt our message, it will be exactly 40 bytes longer than the original
# message as it stores authentication information alongside it. # message as it stores authentication information and nonce alongside it.
ciphertext = bob_box.encrypt(message, nonce) encrypted = bob_box.encrypt(message, nonce)
# Alice creates a second box with her private key to decrypt the message # Alice creates a second box with her private key to decrypt the message
alice_box = Box(skalice, pkbob) alice_box = Box(skalice, pkbob)
# Decrypt our message, an exception will be raised if the encryption was # Decrypt our message, an exception will be raised if the encryption was
# tampered with or there was otherwise an error. # tampered with or there was otherwise an error.
plaintext = alice_box.decrypt(ciphertext, nonce) plaintext = alice_box.decrypt(encrypted)
...@@ -85,7 +85,12 @@ Reference ...@@ -85,7 +85,12 @@ Reference
.. autoclass:: nacl.public.PublicKey .. autoclass:: nacl.public.PublicKey
:members: :members:
.. autoclass:: nacl.public.PrivateKey .. autoclass:: nacl.public.PrivateKey
:members: :members:
.. autoclass:: nacl.public.Box .. autoclass:: nacl.public.Box
:members: :members:
.. autoclass:: nacl.utils.EncryptedMessage
:members:
...@@ -33,13 +33,13 @@ Example ...@@ -33,13 +33,13 @@ Example
# good source of nonce is just 24 random bytes. # good source of nonce is just 24 random bytes.
nonce = nacl.utils.random(nacl.secret.SecretBox.NONCE_SIZE) nonce = nacl.utils.random(nacl.secret.SecretBox.NONCE_SIZE)
# Encrypt our message, it will be exactly 16 bytes longer than the original # Encrypt our message, it will be exactly 40 bytes longer than the original
# message as it stores authentication information alongside it. # message as it stores authentication information and nonce alongside it.
ciphertext = box.encrypt(message, nonce) encrypted = box.encrypt(message, nonce)
# Decrypt our message, an exception will be raised if the encryption was # Decrypt our message, an exception will be raised if the encryption was
# tampered with or there was otherwise an error. # tampered with or there was otherwise an error.
plaintext = box.decrypt(ciphertext, nonce) plaintext = box.decrypt(encrypted)
Requirements Requirements
...@@ -89,6 +89,10 @@ Reference ...@@ -89,6 +89,10 @@ Reference
.. autoclass:: nacl.secret.SecretBox .. autoclass:: nacl.secret.SecretBox
:members: :members:
.. autoclass:: nacl.utils.EncryptedMessage
:members:
:noindex:
Algorithm details Algorithm details
----------------- -----------------
......
...@@ -5,7 +5,7 @@ from . import six ...@@ -5,7 +5,7 @@ from . import six
from . import nacl, encoding from . import nacl, encoding
from .exceptions import CryptoError from .exceptions import CryptoError
from .utils import random from .utils import EncryptedMessage, random
class PublicKey(encoding.Encodable, six.StringFixer, object): class PublicKey(encoding.Encodable, six.StringFixer, object):
...@@ -144,7 +144,7 @@ class Box(encoding.Encodable, six.StringFixer, object): ...@@ -144,7 +144,7 @@ class Box(encoding.Encodable, six.StringFixer, object):
:param plaintext: [:class:`bytes`] The plaintext message to encrypt :param plaintext: [:class:`bytes`] The plaintext message to encrypt
:param nonce: [:class:`bytes`] The nonce to use in the encryption :param nonce: [:class:`bytes`] The nonce to use in the encryption
:param encoder: The encoder to use to encode the ciphertext :param encoder: The encoder to use to encode the ciphertext
:rtype: [:class:`bytes`] :rtype: [:class:`nacl.utils.EncryptedMessage`]
""" """
if len(nonce) != self.NONCE_SIZE: if len(nonce) != self.NONCE_SIZE:
raise ValueError("The nonce must be exactly %s bytes long" % raise ValueError("The nonce must be exactly %s bytes long" %
...@@ -165,9 +165,16 @@ class Box(encoding.Encodable, six.StringFixer, object): ...@@ -165,9 +165,16 @@ class Box(encoding.Encodable, six.StringFixer, object):
box_zeros = nacl.lib.crypto_box_BOXZEROBYTES box_zeros = nacl.lib.crypto_box_BOXZEROBYTES
ciphertext = nacl.ffi.buffer(ciphertext, len(padded))[box_zeros:] ciphertext = nacl.ffi.buffer(ciphertext, len(padded))[box_zeros:]
return encoder.encode(ciphertext) encoded_nonce = encoder.encode(nonce)
encoded_ciphertext = encoder.encode(ciphertext)
return EncryptedMessage._from_parts(
encoded_nonce,
encoded_ciphertext,
encoder.encode(nonce + ciphertext),
)
def decrypt(self, ciphertext, nonce, encoder=encoding.RawEncoder): def decrypt(self, ciphertext, nonce=None, encoder=encoding.RawEncoder):
""" """
Decrypts the ciphertext using the given nonce and returns the Decrypts the ciphertext using the given nonce and returns the
plaintext message. plaintext message.
...@@ -178,12 +185,18 @@ class Box(encoding.Encodable, six.StringFixer, object): ...@@ -178,12 +185,18 @@ class Box(encoding.Encodable, six.StringFixer, object):
:param encoder: The encoder used to decode the ciphertext. :param encoder: The encoder used to decode the ciphertext.
:rtype: [:class:`bytes`] :rtype: [:class:`bytes`]
""" """
# Decode our ciphertext
ciphertext = encoder.decode(ciphertext)
if nonce is None:
# If we were given the nonce and ciphertext combined, split them.
nonce = ciphertext[:self.NONCE_SIZE]
ciphertext = ciphertext[self.NONCE_SIZE:]
if len(nonce) != self.NONCE_SIZE: if len(nonce) != self.NONCE_SIZE:
raise ValueError("The nonce must be exactly %s bytes long" % raise ValueError("The nonce must be exactly %s bytes long" %
self.NONCE_SIZE) self.NONCE_SIZE)
ciphertext = encoder.decode(ciphertext)
padded = b"\x00" * nacl.lib.crypto_box_BOXZEROBYTES + ciphertext padded = b"\x00" * nacl.lib.crypto_box_BOXZEROBYTES + ciphertext
plaintext = nacl.ffi.new("unsigned char[]", len(padded)) plaintext = nacl.ffi.new("unsigned char[]", len(padded))
......
...@@ -5,6 +5,7 @@ from . import six ...@@ -5,6 +5,7 @@ from . import six
from . import nacl, encoding from . import nacl, encoding
from .exceptions import CryptoError from .exceptions import CryptoError
from .utils import EncryptedMessage
class SecretBox(encoding.Encodable, six.StringFixer, object): class SecretBox(encoding.Encodable, six.StringFixer, object):
...@@ -56,7 +57,7 @@ class SecretBox(encoding.Encodable, six.StringFixer, object): ...@@ -56,7 +57,7 @@ class SecretBox(encoding.Encodable, six.StringFixer, object):
:param plaintext: [:class:`bytes`] The plaintext message to encrypt :param plaintext: [:class:`bytes`] The plaintext message to encrypt
:param nonce: [:class:`bytes`] The nonce to use in the encryption :param nonce: [:class:`bytes`] The nonce to use in the encryption
:param encoder: The encoder to use to encode the ciphertext :param encoder: The encoder to use to encode the ciphertext
:rtype: [:class:`bytes`] :rtype: [:class:`nacl.utils.EncryptedMessage`]
""" """
if len(nonce) != self.NONCE_SIZE: if len(nonce) != self.NONCE_SIZE:
raise ValueError("The nonce must be exactly %s bytes long" % raise ValueError("The nonce must be exactly %s bytes long" %
...@@ -73,9 +74,16 @@ class SecretBox(encoding.Encodable, six.StringFixer, object): ...@@ -73,9 +74,16 @@ class SecretBox(encoding.Encodable, six.StringFixer, object):
box_zeros = nacl.lib.crypto_secretbox_BOXZEROBYTES box_zeros = nacl.lib.crypto_secretbox_BOXZEROBYTES
ciphertext = nacl.ffi.buffer(ciphertext, len(padded))[box_zeros:] ciphertext = nacl.ffi.buffer(ciphertext, len(padded))[box_zeros:]
return encoder.encode(ciphertext) encoded_nonce = encoder.encode(nonce)
encoded_ciphertext = encoder.encode(ciphertext)
return EncryptedMessage._from_parts(
encoded_nonce,
encoded_ciphertext,
encoder.encode(nonce + ciphertext),
)
def decrypt(self, ciphertext, nonce, encoder=encoding.RawEncoder): def decrypt(self, ciphertext, nonce=None, encoder=encoding.RawEncoder):
""" """
Decrypts the ciphertext using the given nonce and returns the plaintext Decrypts the ciphertext using the given nonce and returns the plaintext
message. message.
...@@ -86,12 +94,18 @@ class SecretBox(encoding.Encodable, six.StringFixer, object): ...@@ -86,12 +94,18 @@ class SecretBox(encoding.Encodable, six.StringFixer, object):
:param encoder: The encoder used to decode the ciphertext. :param encoder: The encoder used to decode the ciphertext.
:rtype: [:class:`bytes`] :rtype: [:class:`bytes`]
""" """
# Decode our ciphertext
ciphertext = encoder.decode(ciphertext)
if nonce is None:
# If we were given the nonce and ciphertext combined, split them.
nonce = ciphertext[:self.NONCE_SIZE]
ciphertext = ciphertext[self.NONCE_SIZE:]
if len(nonce) != self.NONCE_SIZE: if len(nonce) != self.NONCE_SIZE:
raise ValueError("The nonce must be exactly %s bytes long" % raise ValueError("The nonce must be exactly %s bytes long" %
nacl.lib.crypto_secretbox_NONCEBYTES) nacl.lib.crypto_secretbox_NONCEBYTES)
ciphertext = encoder.decode(ciphertext)
padded = b"\x00" * nacl.lib.crypto_secretbox_BOXZEROBYTES + ciphertext padded = b"\x00" * nacl.lib.crypto_secretbox_BOXZEROBYTES + ciphertext
plaintext = nacl.ffi.new("unsigned char[]", len(padded)) plaintext = nacl.ffi.new("unsigned char[]", len(padded))
......
...@@ -2,6 +2,35 @@ from __future__ import absolute_import ...@@ -2,6 +2,35 @@ from __future__ import absolute_import
from __future__ import division from __future__ import division
from . import nacl from . import nacl
from . import six
class EncryptedMessage(six.binary_type):
"""
A bytes subclass that holds a messaged that has been encrypted by a
:class:`SecretBox`.
"""
@classmethod
def _from_parts(cls, nonce, ciphertext, combined):
obj = cls(combined)
obj._nonce = nonce
obj._ciphertext = ciphertext
return obj
@property
def nonce(self):
"""
The nonce used during the encryption of the :class:`EncryptedMessage`.
"""
return self._nonce
@property
def ciphertext(self):
"""
The ciphertext contained within the :class:`EncryptedMessage`.
"""
return self._ciphertext
def random(size=32): def random(size=32):
......
...@@ -38,11 +38,19 @@ def test_box_encryption(skalice, pkalice, skbob, pkbob, nonce, plaintext, cipher ...@@ -38,11 +38,19 @@ def test_box_encryption(skalice, pkalice, skbob, pkbob, nonce, plaintext, cipher
skbob = PrivateKey(skbob, encoder=HexEncoder) skbob = PrivateKey(skbob, encoder=HexEncoder)
box = Box(skbob, pkalice) box = Box(skbob, pkalice)
encrypted = box.encrypt(
binascii.unhexlify(plaintext),
binascii.unhexlify(nonce),
encoder=HexEncoder,
)
plaintext = binascii.unhexlify(plaintext) expected = binascii.hexlify(
nonce = binascii.unhexlify(nonce) binascii.unhexlify(nonce) + binascii.unhexlify(ciphertext),
)
assert box.encrypt(plaintext, nonce, encoder=HexEncoder) == ciphertext assert encrypted == expected
assert encrypted.nonce == nonce
assert encrypted.ciphertext == ciphertext
@pytest.mark.parametrize(("skalice", "pkalice", "skbob", "pkbob", "nonce", "plaintext", "ciphertext"), VECTORS) @pytest.mark.parametrize(("skalice", "pkalice", "skbob", "pkbob", "nonce", "plaintext", "ciphertext"), VECTORS)
...@@ -59,6 +67,20 @@ def test_box_decryption(skalice, pkalice, skbob, pkbob, nonce, plaintext, cipher ...@@ -59,6 +67,20 @@ def test_box_decryption(skalice, pkalice, skbob, pkbob, nonce, plaintext, cipher
assert decrypted == plaintext assert decrypted == plaintext
@pytest.mark.parametrize(("skalice", "pkalice", "skbob", "pkbob", "nonce", "plaintext", "ciphertext"), VECTORS)
def test_box_decryption_combined(skalice, pkalice, skbob, pkbob, nonce, plaintext, ciphertext):
pkbob = PublicKey(pkbob, encoder=HexEncoder)
skalice = PrivateKey(skalice, encoder=HexEncoder)
box = Box(skalice, pkbob)
combined = binascii.hexlify(
binascii.unhexlify(nonce) + binascii.unhexlify(ciphertext))
decrypted = binascii.hexlify(box.decrypt(combined, encoder=HexEncoder))
assert decrypted == plaintext
@pytest.mark.parametrize(("skalice", "pkalice", "skbob", "pkbob", "nonce", "plaintext", "ciphertext"), VECTORS) @pytest.mark.parametrize(("skalice", "pkalice", "skbob", "pkbob", "nonce", "plaintext", "ciphertext"), VECTORS)
def test_box_failed_decryption(skalice, pkalice, skbob, pkbob, nonce, plaintext, ciphertext): def test_box_failed_decryption(skalice, pkalice, skbob, pkbob, nonce, plaintext, ciphertext):
pkbob = PublicKey(pkbob, encoder=HexEncoder) pkbob = PublicKey(pkbob, encoder=HexEncoder)
......
...@@ -26,11 +26,19 @@ def test_secret_box_creation(): ...@@ -26,11 +26,19 @@ def test_secret_box_creation():
@pytest.mark.parametrize(("key", "nonce", "plaintext", "ciphertext"), VECTORS) @pytest.mark.parametrize(("key", "nonce", "plaintext", "ciphertext"), VECTORS)
def test_secret_box_encryption(key, nonce, plaintext, ciphertext): def test_secret_box_encryption(key, nonce, plaintext, ciphertext):
box = SecretBox(key, encoder=HexEncoder) box = SecretBox(key, encoder=HexEncoder)
encrypted = box.encrypt(
binascii.unhexlify(plaintext),
binascii.unhexlify(nonce),
encoder=HexEncoder,
)
plaintext = binascii.unhexlify(plaintext) expected = binascii.hexlify(
nonce = binascii.unhexlify(nonce) binascii.unhexlify(nonce) + binascii.unhexlify(ciphertext),
)
assert box.encrypt(plaintext, nonce, encoder=HexEncoder) == ciphertext assert encrypted == expected
assert encrypted.nonce == nonce
assert encrypted.ciphertext == ciphertext
@pytest.mark.parametrize(("key", "nonce", "plaintext", "ciphertext"), VECTORS) @pytest.mark.parametrize(("key", "nonce", "plaintext", "ciphertext"), VECTORS)
...@@ -42,3 +50,14 @@ def test_secret_box_decryption(key, nonce, plaintext, ciphertext): ...@@ -42,3 +50,14 @@ def test_secret_box_decryption(key, nonce, plaintext, ciphertext):
box.decrypt(ciphertext, nonce, encoder=HexEncoder)) box.decrypt(ciphertext, nonce, encoder=HexEncoder))
assert decrypted == plaintext assert decrypted == plaintext
@pytest.mark.parametrize(("key", "nonce", "plaintext", "ciphertext"), VECTORS)
def test_secret_box_decryption_combined(key, nonce, plaintext, ciphertext):
box = SecretBox(key, encoder=HexEncoder)
combined = binascii.hexlify(
binascii.unhexlify(nonce) + binascii.unhexlify(ciphertext))
decrypted = binascii.hexlify(box.decrypt(combined, encoder=HexEncoder))
assert decrypted == plaintext
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment