Skip to content

Commit 923dbab

Browse files
committed
implemented ChaCha-20
*implemented and added tests for ChaCha-20 Poly1305
1 parent 9597a64 commit 923dbab

File tree

2 files changed

+112
-0
lines changed

2 files changed

+112
-0
lines changed

pydatastructs/strings/algorithms.py

+98
Original file line numberDiff line numberDiff line change
@@ -334,3 +334,101 @@ def sha256_encrypt(text) -> str:
334334
h = [(x + y) & 0xFFFFFFFF for x, y in zip(h, [a, b, c, d, e, f, g, h0])]
335335

336336
return ''.join(f'{value:08x}' for value in h)
337+
338+
@staticmethod
339+
def yield_chacha20_xor_stream(key, iv, position=0):
340+
"""Generate the xor stream with the ChaCha20 cipher."""
341+
if not isinstance(position, int):
342+
raise TypeError
343+
if position & ~0xFFFFFFFF:
344+
raise ValueError("Position is not uint32.")
345+
if not isinstance(key, bytes):
346+
raise TypeError
347+
if not isinstance(iv, bytes):
348+
raise TypeError
349+
if len(key) != 32:
350+
raise ValueError
351+
if len(iv) != 8:
352+
raise ValueError
353+
354+
def rotate(v, c):
355+
return ((v << c) & 0xFFFFFFFF) | v >> (32 - c)
356+
357+
def quarter_round(x, a, b, c, d):
358+
x[a] = (x[a] + x[b]) & 0xFFFFFFFF
359+
x[d] = rotate(x[d] ^ x[a], 16)
360+
x[c] = (x[c] + x[d]) & 0xFFFFFFFF
361+
x[b] = rotate(x[b] ^ x[c], 12)
362+
x[a] = (x[a] + x[b]) & 0xFFFFFFFF
363+
x[d] = rotate(x[d] ^ x[a], 8)
364+
x[c] = (x[c] + x[d]) & 0xFFFFFFFF
365+
x[b] = rotate(x[b] ^ x[c], 7)
366+
367+
ctx = [0] * 16
368+
ctx[:4] = (1634760805, 857760878, 2036477234, 1797285236)
369+
ctx[4:12] = struct.unpack("<8L", key)
370+
ctx[12] = ctx[13] = position
371+
ctx[14:16] = struct.unpack("<LL", iv)
372+
while 1:
373+
x = list(ctx)
374+
for i in range(10):
375+
quarter_round(x, 0, 4, 8, 12)
376+
quarter_round(x, 1, 5, 9, 13)
377+
quarter_round(x, 2, 6, 10, 14)
378+
quarter_round(x, 3, 7, 11, 15)
379+
quarter_round(x, 0, 5, 10, 15)
380+
quarter_round(x, 1, 6, 11, 12)
381+
quarter_round(x, 2, 7, 8, 13)
382+
quarter_round(x, 3, 4, 9, 14)
383+
for c in struct.pack(
384+
"<16L", *((x[i] + ctx[i]) & 0xFFFFFFFF for i in range(16))
385+
):
386+
yield c
387+
ctx[12] = (ctx[12] + 1) & 0xFFFFFFFF
388+
if ctx[12] == 0:
389+
ctx[13] = (ctx[13] + 1) & 0xFFFFFFFF
390+
391+
@staticmethod
392+
def chacha20(data:bytes, key:bytes, nonce=None, position=0):
393+
"""
394+
Encrypt (or decrypt) data using the ChaCha20 stream cipher.
395+
396+
Parameters
397+
==========
398+
data : bytes
399+
Input data to encrypt/decrypt
400+
key : bytes
401+
32-byte encryption key. Shorter keys will be padded by repetition.
402+
nonce : bytes, optional
403+
8-byte nonce
404+
405+
Returns
406+
=======
407+
bytes
408+
Processed output
409+
410+
Notes
411+
=====
412+
Chacha20 is symmetric - same operation encrypts and decrypts.
413+
414+
References
415+
==========
416+
417+
.. [1] https://en.wikipedia.org/wiki/ChaCha20-Poly1305
418+
.. [2] https://github.com/pts/chacha20/blob/master/chacha20_python3.py
419+
420+
"""
421+
422+
if nonce is None:
423+
nonce = b"\0" * 8
424+
425+
if not key:
426+
raise ValueError("Key is empty.")
427+
if len(key) < 32:
428+
key = (key * (32 // len(key) + 1))[:32]
429+
if len(key) > 32:
430+
key = key[:32]
431+
432+
return bytes(
433+
a ^ b for a, b in zip(data, Crypto.yield_chacha20_xor_stream(key, nonce, position))
434+
)

pydatastructs/strings/tests/test_algorithms.py

+14
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,17 @@ def _test_sha256_encrypt():
9797

9898
for text, expected in zip(test_cases, expected_hashes):
9999
assert Crypto.sha256_encrypt(text) == expected
100+
101+
def _test_chacha20():
102+
import binascii
103+
cnvt = lambda x: binascii.unhexlify(bytes(x, 'ascii'))
104+
test_cases = [
105+
('76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669', '0000000000000000000000000000000000000000000000000000000000000000', '0000000000000000'),
106+
('4540f05a9f1fb296d7736e7b208e3c96eb4fe1834688d2604f450952ed432d41bbe2a0b6ea7566d2a5d1e7e20d42af2c53d792b1c43fea817e9ad275', '0000000000000000000000000000000000000000000000000000000000000001', '0000000000000000'),
107+
('de9cba7bf3d69ef5e786dc63973f653a0b49e015adbff7134fcb7df137821031e85a050278a7084527214f73efc7fa5b5277062eb7a0433e445f41e3', '0000000000000000000000000000000000000000000000000000000000000000', '0000000000000001'),
108+
('ef3fdfd6c61578fbf5cf35bd3dd33b8009631634d21e42ac33960bd138e50d32111e4caf237ee53ca8ad6426194a88545ddc497a0b466e7d6bbdb004', '0000000000000000000000000000000000000000000000000000000000000000', '0100000000000000'),
109+
('f798a189f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c134a4547b733b46413042c9440049176905d3be59ea1c53f15916155c2be8241a38008b9a26bc35941e2444177c8ade6689de95264986d95889fb60e84629c9bd9a5acb1cc118be563eb9b3a4a472f82e09a7e778492b562ef7130e88dfe031c79db9d4f7c7a899151b9a475032b63fc385245fe054e3dd5a97a5f576fe064025d3ce042c566ab2c507b138db853e3d6959660996546cc9c4a6eafdc777c040d70eaf46f76dad3979e5c5360c3317166a1c894c94a371876a94df7628fe4eaaf2ccb27d5aaae0ad7ad0f9d4b6ad3b54098746d4524d38407a6deb', '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', '0001020304050607')
110+
]
111+
112+
for i, (ciphertext, key, iv) in enumerate(map(lambda t: tuple(map(cnvt, t)), test_cases)):
113+
assert Crypto.chacha20_encrypt(b'\0' * len(ciphertext), key, iv) == ciphertext

0 commit comments

Comments
 (0)