Skip to content

Commit

Permalink
Generate random nonces if omitted in encryption calls (#210)
Browse files Browse the repository at this point in the history
* Generate random nonces if omitted in encryption calls

* Test encryption generates different nonces

* Use one line for each encryption/decryption call
  • Loading branch information
felipedau authored and reaperhulk committed Jan 5, 2017
1 parent 42ef7df commit d77988a
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 7 deletions.
10 changes: 7 additions & 3 deletions src/nacl/public.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,11 @@ def decode(cls, encoded, encoder=encoding.RawEncoder):

return box

def encrypt(self, plaintext, nonce, encoder=encoding.RawEncoder):
def encrypt(self, plaintext, nonce=None, encoder=encoding.RawEncoder):
"""
Encrypts the plaintext message using the given `nonce` and returns
the ciphertext encoded with the encoder.
Encrypts the plaintext message using the given `nonce` (or generates
one randomly if omitted) and returns the ciphertext encoded with the
encoder.
.. warning:: It is **VITALLY** important that the nonce is a nonce,
i.e. it is a number used only once for any given key. If you fail
Expand All @@ -153,6 +154,9 @@ def encrypt(self, plaintext, nonce, encoder=encoding.RawEncoder):
:param encoder: The encoder to use to encode the ciphertext
:rtype: [:class:`nacl.utils.EncryptedMessage`]
"""
if nonce is None:
nonce = random(self.NONCE_SIZE)

if len(nonce) != self.NONCE_SIZE:
raise ValueError("The nonce must be exactly %s bytes long" %
self.NONCE_SIZE)
Expand Down
12 changes: 8 additions & 4 deletions src/nacl/secret.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import nacl.bindings
from nacl import encoding
from nacl.utils import EncryptedMessage, StringFixer
from nacl.utils import EncryptedMessage, StringFixer, random


class SecretBox(encoding.Encodable, StringFixer, object):
Expand Down Expand Up @@ -58,10 +58,11 @@ def __init__(self, key, encoder=encoding.RawEncoder):
def __bytes__(self):
return self._key

def encrypt(self, plaintext, nonce, encoder=encoding.RawEncoder):
def encrypt(self, plaintext, nonce=None, encoder=encoding.RawEncoder):
"""
Encrypts the plaintext message using the given nonce and returns the
ciphertext encoded with the encoder.
Encrypts the plaintext message using the given `nonce` (or generates
one randomly if omitted) and returns the ciphertext encoded with the
encoder.
.. warning:: It is **VITALLY** important that the nonce is a nonce,
i.e. it is a number used only once for any given key. If you fail
Expand All @@ -74,6 +75,9 @@ def encrypt(self, plaintext, nonce, encoder=encoding.RawEncoder):
:param encoder: The encoder to use to encode the ciphertext
:rtype: [:class:`nacl.utils.EncryptedMessage`]
"""
if nonce is None:
nonce = random(self.NONCE_SIZE)

if len(nonce) != self.NONCE_SIZE:
raise ValueError(
"The nonce must be exactly %s bytes long" % self.NONCE_SIZE,
Expand Down
44 changes: 44 additions & 0 deletions tests/test_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,50 @@ def test_box_decryption_combined(
assert decrypted == plaintext


@pytest.mark.parametrize(
(
"privalice", "pubalice", "privbob", "pubbob", "nonce", "plaintext",
"ciphertext",
),
VECTORS,
)
def test_box_optional_nonce(
privalice, pubalice, privbob, pubbob, nonce, plaintext, ciphertext):
pubbob = PublicKey(pubbob, encoder=HexEncoder)
privalice = PrivateKey(privalice, encoder=HexEncoder)

box = Box(privalice, pubbob)

encrypted = box.encrypt(binascii.unhexlify(plaintext), encoder=HexEncoder)

decrypted = binascii.hexlify(box.decrypt(encrypted, encoder=HexEncoder))

assert decrypted == plaintext


@pytest.mark.parametrize(
(
"privalice", "pubalice", "privbob", "pubbob", "nonce", "plaintext",
"ciphertext",
),
VECTORS,
)
def test_box_encryption_generates_different_nonces(
privalice, pubalice, privbob, pubbob, nonce, plaintext, ciphertext):
pubbob = PublicKey(pubbob, encoder=HexEncoder)
privalice = PrivateKey(privalice, encoder=HexEncoder)

box = Box(privalice, pubbob)

nonce_0 = box.encrypt(binascii.unhexlify(plaintext),
encoder=HexEncoder).nonce

nonce_1 = box.encrypt(binascii.unhexlify(plaintext),
encoder=HexEncoder).nonce

assert nonce_0 != nonce_1


@pytest.mark.parametrize(
(
"privalice", "pubalice", "privbob", "pubbob", "nonce", "plaintext",
Expand Down
25 changes: 25 additions & 0 deletions tests/test_secret.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,31 @@ def test_secret_box_decryption_combined(key, nonce, plaintext, ciphertext):
assert decrypted == plaintext


@pytest.mark.parametrize(("key", "nonce", "plaintext", "ciphertext"), VECTORS)
def test_secret_box_optional_nonce(key, nonce, plaintext, ciphertext):
box = SecretBox(key, encoder=HexEncoder)

encrypted = box.encrypt(binascii.unhexlify(plaintext), encoder=HexEncoder)

decrypted = binascii.hexlify(box.decrypt(encrypted, encoder=HexEncoder))

assert decrypted == plaintext


@pytest.mark.parametrize(("key", "nonce", "plaintext", "ciphertext"), VECTORS)
def test_secret_box_encryption_generates_different_nonces(
key, nonce, plaintext, ciphertext):
box = SecretBox(key, encoder=HexEncoder)

nonce_0 = box.encrypt(binascii.unhexlify(plaintext),
encoder=HexEncoder).nonce

nonce_1 = box.encrypt(binascii.unhexlify(plaintext),
encoder=HexEncoder).nonce

assert nonce_0 != nonce_1


def test_secret_box_wrong_lengths():
with pytest.raises(ValueError):
SecretBox(b"")
Expand Down

0 comments on commit d77988a

Please sign in to comment.