File: test_chacha20.py

package info (click to toggle)
python-cryptography 43.0.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 5,400 kB
  • sloc: python: 49,159; java: 319; makefile: 161
file content (141 lines) | stat: -rw-r--r-- 5,232 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# 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.


import binascii
import os
import struct

import pytest

from cryptography.exceptions import AlreadyFinalized
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms

from ...utils import load_nist_vectors
from .utils import _load_all_params


@pytest.mark.supported(
    only_if=lambda backend: backend.cipher_supported(
        algorithms.ChaCha20(b"\x00" * 32, b"0" * 16), None
    ),
    skip_message="Does not support ChaCha20",
)
class TestChaCha20:
    @pytest.mark.parametrize(
        "vector",
        _load_all_params(
            os.path.join("ciphers", "ChaCha20"),
            ["counter-overflow.txt", "rfc7539.txt"],
            load_nist_vectors,
        ),
    )
    def test_vectors(self, vector, backend):
        key = binascii.unhexlify(vector["key"])
        nonce = binascii.unhexlify(vector["nonce"])
        ibc = struct.pack("<Q", int(vector["initial_block_counter"]))
        pt = binascii.unhexlify(vector["plaintext"])
        encryptor = Cipher(
            algorithms.ChaCha20(key, ibc + nonce), None, backend
        ).encryptor()
        computed_ct = encryptor.update(pt) + encryptor.finalize()
        assert binascii.hexlify(computed_ct) == vector["ciphertext"]

    def test_buffer_protocol(self, backend):
        key = bytearray(os.urandom(32))
        nonce = bytearray(os.urandom(16))
        cipher = Cipher(algorithms.ChaCha20(key, nonce), None, backend)
        enc = cipher.encryptor()
        ct = enc.update(bytearray(b"hello")) + enc.finalize()
        dec = cipher.decryptor()
        pt = dec.update(ct) + dec.finalize()
        assert pt == b"hello"

    def test_key_size(self):
        chacha = algorithms.ChaCha20(b"0" * 32, b"0" * 16)
        assert chacha.key_size == 256

    def test_invalid_key_size(self):
        with pytest.raises(ValueError):
            algorithms.ChaCha20(b"wrongsize", b"0" * 16)

    def test_invalid_nonce(self):
        with pytest.raises(ValueError):
            algorithms.ChaCha20(b"0" * 32, b"0")

        with pytest.raises(TypeError):
            algorithms.ChaCha20(b"0" * 32, object())  # type:ignore[arg-type]

    def test_invalid_key_type(self):
        with pytest.raises(TypeError, match="key must be bytes"):
            algorithms.ChaCha20("0" * 32, b"0" * 16)  # type:ignore[arg-type]

    def test_partial_blocks(self, backend):
        # Test that partial blocks and counter increments are handled
        # correctly. Successive calls to update should return the same
        # as if the entire input was passed in a single call:
        # update(pt[0:n]) + update(pt[n:m]) + update(pt[m:]) == update(pt)
        key = bytearray(os.urandom(32))
        nonce = bytearray(os.urandom(16))
        cipher = Cipher(algorithms.ChaCha20(key, nonce), None, backend)
        pt = bytearray(os.urandom(96 * 3))

        enc_full = cipher.encryptor()
        ct_full = enc_full.update(pt)

        enc_partial = cipher.encryptor()
        len_partial = len(pt) // 3
        ct_partial_1 = enc_partial.update(pt[:len_partial])
        ct_partial_2 = enc_partial.update(pt[len_partial : len_partial * 2])
        ct_partial_3 = enc_partial.update(pt[len_partial * 2 :])

        assert ct_full == ct_partial_1 + ct_partial_2 + ct_partial_3

    def test_reset_nonce(self, backend):
        data = b"helloworld" * 10
        key = b"\x00" * 32
        nonce = b"\x00" * 16
        nonce_alt = b"\xee" * 16
        cipher = Cipher(algorithms.ChaCha20(key, nonce), None)
        cipher_alt = Cipher(algorithms.ChaCha20(key, nonce_alt), None)
        enc = cipher.encryptor()
        ct1 = enc.update(data)
        assert len(ct1) == len(data)
        for _ in range(2):
            enc.reset_nonce(nonce)
            assert enc.update(data) == ct1
        # Reset the nonce to a different value
        # and check it matches with a different context
        enc_alt = cipher_alt.encryptor()
        ct2 = enc_alt.update(data)
        enc.reset_nonce(nonce_alt)
        assert enc.update(data) == ct2
        enc_alt.finalize()
        enc.finalize()
        with pytest.raises(AlreadyFinalized):
            enc.reset_nonce(nonce)
        dec = cipher.decryptor()
        assert dec.update(ct1) == data
        for _ in range(2):
            dec.reset_nonce(nonce)
            assert dec.update(ct1) == data
        # Reset the nonce to a different value
        # and check it matches with a different context
        dec_alt = cipher_alt.decryptor()
        dec.reset_nonce(nonce_alt)
        assert dec.update(ct2) == dec_alt.update(ct2)
        dec_alt.finalize()
        dec.finalize()
        with pytest.raises(AlreadyFinalized):
            dec.reset_nonce(nonce)

    def test_nonce_reset_invalid_length(self, backend):
        key = b"\x00" * 32
        nonce = b"\x00" * 16
        cipher = Cipher(algorithms.ChaCha20(key, nonce), None)
        enc = cipher.encryptor()
        with pytest.raises(ValueError):
            enc.reset_nonce(nonce[:-1])
        with pytest.raises(ValueError):
            enc.reset_nonce(nonce + b"\x00")