Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* poly1305 support * some more tests * have I mentioned how bad the spellchecker is? * doc improvements * EVP_PKEY_new_raw_private_key copies the key but that's not documented Let's assume that might change and be very defensive * review feedback * add a test that fails on a tag of the correct length but wrong value * docs improvements
- Loading branch information
1 parent
3a300e6
commit b73ed5a
Showing
9 changed files
with
334 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
.. hazmat:: | ||
|
||
Poly1305 | ||
======== | ||
|
||
.. currentmodule:: cryptography.hazmat.primitives.poly1305 | ||
|
||
.. testsetup:: | ||
|
||
key = b"\x01" * 32 | ||
|
||
Poly1305 is an authenticator that takes a 32-byte key and a message and | ||
produces a 16-byte tag. This tag is used to authenticate the message. Each key | ||
**must** only be used once. Using the same key to generate tags for multiple | ||
messages allows an attacker to forge tags. Poly1305 is described in | ||
:rfc:`7539`. | ||
|
||
.. class:: Poly1305(key) | ||
|
||
.. versionadded:: 2.7 | ||
|
||
.. warning:: | ||
|
||
Using the same key to generate tags for multiple messages allows an | ||
attacker to forge tags. Always generate a new key per message you want | ||
to authenticate. If you are using this as a MAC for | ||
symmetric encryption please use | ||
:class:`~cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305` | ||
instead. | ||
|
||
.. doctest:: | ||
|
||
>>> from cryptography.hazmat.primitives import poly1305 | ||
>>> p = poly1305.Poly1305(key) | ||
>>> p.update(b"message to authenticate") | ||
>>> p.finalize() | ||
b'T\xae\xff3\xbdW\xef\xd5r\x01\xe2n=\xb7\xd2h' | ||
|
||
To check that a given tag is correct use the :meth:`verify` method. | ||
You will receive an exception if the tag is wrong: | ||
|
||
.. doctest:: | ||
|
||
>>> p = poly1305.Poly1305(key) | ||
>>> p.update(b"message to authenticate") | ||
>>> p.verify(b"an incorrect tag") | ||
Traceback (most recent call last): | ||
... | ||
cryptography.exceptions.InvalidSignature: Value did not match computed tag. | ||
|
||
:param key: Secret key as ``bytes``. | ||
:type key: :term:`bytes-like` | ||
:raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if | ||
the version of OpenSSL ``cryptography`` is compiled against does not | ||
support this algorithm. | ||
|
||
.. method:: update(data) | ||
|
||
:param data: The bytes to hash and authenticate. | ||
:type data: :term:`bytes-like` | ||
:raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` | ||
:raises TypeError: This exception is raised if ``data`` is not ``bytes``. | ||
|
||
.. method:: verify(tag) | ||
|
||
Finalize the current context and securely compare the MAC to | ||
``tag``. | ||
|
||
:param bytes tag: The bytes to compare against. | ||
:raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` | ||
:raises cryptography.exceptions.InvalidSignature: If tag does not | ||
match. | ||
:raises TypeError: This exception is raised if ``tag`` is not | ||
``bytes``. | ||
|
||
.. method:: finalize() | ||
|
||
Finalize the current context and return the message authentication code | ||
as bytes. | ||
|
||
After ``finalize`` has been called this object can no longer be used | ||
and :meth:`update`, :meth:`verify`, and :meth:`finalize` | ||
will raise an :class:`~cryptography.exceptions.AlreadyFinalized` | ||
exception. | ||
|
||
:return bytes: The message authentication code as bytes. | ||
:raises cryptography.exceptions.AlreadyFinalized: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# This file is dual licensed under the terms of the Apache License, Version | ||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository | ||
# for complete details. | ||
|
||
from __future__ import absolute_import, division, print_function | ||
|
||
|
||
from cryptography.exceptions import InvalidSignature | ||
from cryptography.hazmat.primitives import constant_time | ||
|
||
|
||
_POLY1305_TAG_SIZE = 16 | ||
_POLY1305_KEY_SIZE = 32 | ||
|
||
|
||
class _Poly1305Context(object): | ||
def __init__(self, backend, key): | ||
self._backend = backend | ||
|
||
key_ptr = self._backend._ffi.from_buffer(key) | ||
# This function copies the key into OpenSSL-owned memory so we don't | ||
# need to retain it ourselves | ||
evp_pkey = self._backend._lib.EVP_PKEY_new_raw_private_key( | ||
self._backend._lib.NID_poly1305, | ||
self._backend._ffi.NULL, key_ptr, len(key) | ||
) | ||
self._backend.openssl_assert(evp_pkey != self._backend._ffi.NULL) | ||
self._evp_pkey = self._backend._ffi.gc( | ||
evp_pkey, self._backend._lib.EVP_PKEY_free | ||
) | ||
ctx = self._backend._lib.Cryptography_EVP_MD_CTX_new() | ||
self._backend.openssl_assert(ctx != self._backend._ffi.NULL) | ||
self._ctx = self._backend._ffi.gc( | ||
ctx, self._backend._lib.Cryptography_EVP_MD_CTX_free | ||
) | ||
res = self._backend._lib.EVP_DigestSignInit( | ||
self._ctx, self._backend._ffi.NULL, self._backend._ffi.NULL, | ||
self._backend._ffi.NULL, self._evp_pkey | ||
) | ||
self._backend.openssl_assert(res == 1) | ||
|
||
def update(self, data): | ||
data_ptr = self._backend._ffi.from_buffer(data) | ||
res = self._backend._lib.EVP_DigestSignUpdate( | ||
self._ctx, data_ptr, len(data) | ||
) | ||
self._backend.openssl_assert(res != 0) | ||
|
||
def finalize(self): | ||
buf = self._backend._ffi.new("unsigned char[]", _POLY1305_TAG_SIZE) | ||
outlen = self._backend._ffi.new("size_t *") | ||
res = self._backend._lib.EVP_DigestSignFinal(self._ctx, buf, outlen) | ||
self._backend.openssl_assert(res != 0) | ||
self._backend.openssl_assert(outlen[0] == _POLY1305_TAG_SIZE) | ||
return self._backend._ffi.buffer(buf)[:outlen[0]] | ||
|
||
def verify(self, tag): | ||
mac = self.finalize() | ||
if not constant_time.bytes_eq(mac, tag): | ||
raise InvalidSignature("Value did not match computed tag.") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# This file is dual licensed under the terms of the Apache License, Version | ||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository | ||
# for complete details. | ||
|
||
from __future__ import absolute_import, division, print_function | ||
|
||
|
||
from cryptography import utils | ||
from cryptography.exceptions import ( | ||
AlreadyFinalized, UnsupportedAlgorithm, _Reasons | ||
) | ||
|
||
|
||
class Poly1305(object): | ||
def __init__(self, key): | ||
from cryptography.hazmat.backends.openssl.backend import backend | ||
if not backend.poly1305_supported(): | ||
raise UnsupportedAlgorithm( | ||
"poly1305 is not supported by this version of OpenSSL.", | ||
_Reasons.UNSUPPORTED_MAC | ||
) | ||
self._ctx = backend.create_poly1305_ctx(key) | ||
|
||
def update(self, data): | ||
if self._ctx is None: | ||
raise AlreadyFinalized("Context was already finalized.") | ||
utils._check_byteslike("data", data) | ||
self._ctx.update(data) | ||
|
||
def finalize(self): | ||
if self._ctx is None: | ||
raise AlreadyFinalized("Context was already finalized.") | ||
mac = self._ctx.finalize() | ||
self._ctx = None | ||
return mac | ||
|
||
def verify(self, tag): | ||
utils._check_bytes("tag", tag) | ||
if self._ctx is None: | ||
raise AlreadyFinalized("Context was already finalized.") | ||
|
||
ctx, self._ctx = self._ctx, None | ||
ctx.verify(tag) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
# This file is dual licensed under the terms of the Apache License, Version | ||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository | ||
# for complete details. | ||
|
||
from __future__ import absolute_import, division, print_function | ||
|
||
import binascii | ||
import os | ||
|
||
import pytest | ||
|
||
from cryptography.exceptions import ( | ||
AlreadyFinalized, InvalidSignature, _Reasons | ||
) | ||
from cryptography.hazmat.primitives.poly1305 import Poly1305 | ||
|
||
from ...utils import ( | ||
load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm | ||
) | ||
|
||
|
||
@pytest.mark.supported( | ||
only_if=lambda backend: not backend.poly1305_supported(), | ||
skip_message="Requires OpenSSL without poly1305 support" | ||
) | ||
def test_poly1305_unsupported(backend): | ||
with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MAC): | ||
Poly1305(b"0" * 32) | ||
|
||
|
||
@pytest.mark.supported( | ||
only_if=lambda backend: backend.poly1305_supported(), | ||
skip_message="Requires OpenSSL with poly1305 support" | ||
) | ||
class TestPoly1305(object): | ||
@pytest.mark.parametrize( | ||
"vector", | ||
load_vectors_from_file( | ||
os.path.join("poly1305", "rfc7539.txt"), load_nist_vectors | ||
) | ||
) | ||
def test_vectors(self, vector, backend): | ||
key = binascii.unhexlify(vector["key"]) | ||
msg = binascii.unhexlify(vector["msg"]) | ||
tag = binascii.unhexlify(vector["tag"]) | ||
poly = Poly1305(key) | ||
poly.update(msg) | ||
assert poly.finalize() == tag | ||
|
||
def test_key_with_no_additional_references(self, backend): | ||
poly = Poly1305(os.urandom(32)) | ||
assert len(poly.finalize()) == 16 | ||
|
||
def test_raises_after_finalize(self, backend): | ||
poly = Poly1305(b"0" * 32) | ||
poly.finalize() | ||
|
||
with pytest.raises(AlreadyFinalized): | ||
poly.update(b"foo") | ||
|
||
with pytest.raises(AlreadyFinalized): | ||
poly.finalize() | ||
|
||
def test_reject_unicode(self, backend): | ||
poly = Poly1305(b"0" * 32) | ||
with pytest.raises(TypeError): | ||
poly.update(u'') | ||
|
||
def test_verify(self, backend): | ||
poly = Poly1305(b"0" * 32) | ||
poly.update(b"msg") | ||
tag = poly.finalize() | ||
|
||
with pytest.raises(AlreadyFinalized): | ||
poly.verify(b"") | ||
|
||
poly2 = Poly1305(b"0" * 32) | ||
poly2.update(b"msg") | ||
poly2.verify(tag) | ||
|
||
def test_invalid_verify(self, backend): | ||
poly = Poly1305(b"0" * 32) | ||
poly.update(b"msg") | ||
with pytest.raises(InvalidSignature): | ||
poly.verify(b"") | ||
|
||
p2 = Poly1305(b"0" * 32) | ||
p2.update(b"msg") | ||
with pytest.raises(InvalidSignature): | ||
p2.verify(b"\x00" * 16) | ||
|
||
def test_verify_reject_unicode(self, backend): | ||
poly = Poly1305(b"0" * 32) | ||
with pytest.raises(TypeError): | ||
poly.verify(u'') | ||
|
||
def test_invalid_key_type(self, backend): | ||
with pytest.raises(TypeError): | ||
Poly1305(object()) | ||
|
||
def test_invalid_key_length(self, backend): | ||
with pytest.raises(ValueError): | ||
Poly1305(b"0" * 31) | ||
|
||
with pytest.raises(ValueError): | ||
Poly1305(b"0" * 33) | ||
|
||
def test_buffer_protocol(self, backend): | ||
key = binascii.unhexlify( | ||
b"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cb" | ||
b"c207075c0" | ||
) | ||
msg = binascii.unhexlify( | ||
b"2754776173206272696c6c69672c20616e642074686520736c69746" | ||
b"87920746f7665730a446964206779726520616e642067696d626c65" | ||
b"20696e2074686520776162653a0a416c6c206d696d7379207765726" | ||
b"52074686520626f726f676f7665732c0a416e6420746865206d6f6d" | ||
b"65207261746873206f757467726162652e" | ||
) | ||
key = bytearray(key) | ||
poly = Poly1305(key) | ||
poly.update(bytearray(msg)) | ||
assert poly.finalize() == binascii.unhexlify( | ||
b"4541669a7eaaee61e708dc7cbcc5eb62" | ||
) |