From c1949653bec9f11ba43d32e1c349e2df17d6fbd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Praszmo?= Date: Thu, 1 Oct 2020 10:29:57 +0200 Subject: [PATCH] Add chacha20 and salsa20 ciphers (#46) * Add salsa20 and chacha20 ciphers * Fix the hex escapes in documentation --- docs/crypto.rst | 43 +++++++++++++++++++++++++++++++++++-- malduck/__init__.py | 15 ++++++++++++- malduck/crypto/__init__.py | 15 ++++++++++++- malduck/crypto/chacha20.py | 44 ++++++++++++++++++++++++++++++++++++++ malduck/crypto/salsa20.py | 44 ++++++++++++++++++++++++++++++++++++++ malduck/crypto/serpent.py | 4 ++-- tests/test_crypto.py | 22 ++++++++++++++++++- 7 files changed, 180 insertions(+), 7 deletions(-) create mode 100644 malduck/crypto/chacha20.py create mode 100644 malduck/crypto/salsa20.py diff --git a/docs/crypto.rst b/docs/crypto.rst index ea42ea8..f117e14 100644 --- a/docs/crypto.rst +++ b/docs/crypto.rst @@ -56,6 +56,25 @@ Supported modes: ECB. .. autofunction:: malduck.blowfish.ecb.encrypt .. autofunction:: malduck.blowfish.ecb.decrypt +ChaCha20 +-------- + +ChaCha20 stream cipher. + +Assumes empty nonce if none given. + +.. code-block:: python + + from malduck import chacha20 + + key = b'chachaKeyHereNow' * 2 + nonce = b'\x01\x02\x03\x04\x05\0x6\0x7' + plaintext = b'data'*16 + ciphertext = chacha20.decrypt(key, plaintext, nonce) + +.. autofunction:: malduck.chacha20.encrypt +.. autofunction:: malduck.chacha20.decrypt + DES/DES3 (CBC only) ------------------- @@ -71,12 +90,32 @@ Supported modes: CBC. key = b'des3des3' iv = b'3des3des' - plaintext = b'data'*16 - ciphertext = des3.cbc.decrypt(key, plaintext) + plaintext = b'data' * 16 + ciphertext = des3.cbc.encrypt(key, plaintext) .. autofunction:: malduck.des3.cbc.encrypt .. autofunction:: malduck.des3.cbc.decrypt +Salsa20 +-------- + +Salsa20 stream cipher. + +Assumes empty nonce if none given. + +.. code-block:: python + + from malduck import salsa20 + + key = b'salsaFTW' * 4 + nonce = b'\x01\x02\x03\x04\x05\0x6\0x7' + plaintext = b'data' * 16 + ciphertext = salsa20.decrypt(key, plaintext, nonce) + +.. autofunction:: malduck.salsa20.encrypt +.. autofunction:: malduck.salsa20.decrypt + + Serpent (CBC only) ------------------ diff --git a/malduck/__init__.py b/malduck/__init__.py index 3ed4c5f..ba6586a 100644 --- a/malduck/__init__.py +++ b/malduck/__init__.py @@ -6,7 +6,18 @@ from .compression import aplib, gzip, lznt1 -from .crypto import aes, blowfish, des3, rabbit, rc4, rsa, serpent, xor +from .crypto import ( + aes, + blowfish, + chacha20, + des3, + rabbit, + rc4, + rsa, + salsa20, + serpent, + xor, +) from .disasm import disasm, insn @@ -125,10 +136,12 @@ # crypto "aes", "blowfish", + "chacha20", "des3", "rabbit", "rc4", "rsa", + "salsa20", "serpent", "xor", # disasm diff --git a/malduck/crypto/__init__.py b/malduck/crypto/__init__.py index d0bff44..b1ab647 100644 --- a/malduck/crypto/__init__.py +++ b/malduck/crypto/__init__.py @@ -1,10 +1,23 @@ from .aes import aes from .blowfish import blowfish +from .chacha20 import chacha20 from .des3 import des3 from .rabbit import rabbit from .rc import rc4 from .rsa import rsa +from .salsa20 import salsa20 from .serpent import serpent from .xor import xor -__all__ = ["aes", "blowfish", "des3", "rabbit", "rc4", "rsa", "serpent", "xor"] +__all__ = [ + "aes", + "blowfish", + "chacha20", + "des3", + "rabbit", + "rc4", + "rsa", + "salsa20", + "serpent", + "xor", +] diff --git a/malduck/crypto/chacha20.py b/malduck/crypto/chacha20.py new file mode 100644 index 0000000..afa41c5 --- /dev/null +++ b/malduck/crypto/chacha20.py @@ -0,0 +1,44 @@ +from typing import Optional +from Cryptodome.Cipher import ChaCha20 as ChaCha20Cipher + + +__all__ = ["chacha20"] + + +class ChaCha20: + def encrypt(self, key: bytes, data: bytes, nonce: Optional[bytes] = None) -> bytes: + """ + Encrypts buffer using ChaCha20 algorithm. + + :param key: Cryptographic key (32 bytes) + :type key: bytes + :param data: Buffer to be encrypted + :type data: bytes + :param nonce: Nonce value (8/12 bytes, defaults to `b"\\\\x00"*8` ) + :type nonce: bytes, optional + :return: Encrypted data + :rtype: bytes + """ + if nonce is None: + nonce = b"\x00" * 8 + return ChaCha20Cipher.new(key=key, nonce=nonce).encrypt(data) + + def decrypt(self, key: bytes, data: bytes, nonce: Optional[bytes] = None) -> bytes: + """ + Decrypts buffer using ChaCha20 algorithm. + + :param key: Cryptographic key (32 bytes) + :type key: bytes + :param data: Buffer to be decrypted + :type data: bytes + :param nonce: Nonce value (8/12 bytes, defaults to `b"\\\\x00"*8` ) + :type nonce: bytes, optional + :return: Decrypted data + :rtype: bytes + """ + if nonce is None: + nonce = b"\x00" * 8 + return ChaCha20Cipher.new(key=key, nonce=nonce).decrypt(data) + + +chacha20 = ChaCha20() diff --git a/malduck/crypto/salsa20.py b/malduck/crypto/salsa20.py new file mode 100644 index 0000000..58cb667 --- /dev/null +++ b/malduck/crypto/salsa20.py @@ -0,0 +1,44 @@ +from typing import Optional +from Cryptodome.Cipher import Salsa20 as Salsa20Cipher + + +__all__ = ["salsa20"] + + +class Salsa20: + def encrypt(self, key: bytes, data: bytes, nonce: Optional[bytes] = None) -> bytes: + """ + Encrypts buffer using Salsa20 algorithm. + + :param key: Cryptographic key (16/32 bytes) + :type key: bytes + :param data: Buffer to be encrypted + :type data: bytes + :param nonce: Nonce value (8 bytes, defaults to `b"\\\\x00"*8` ) + :type nonce: bytes, optional + :return: Encrypted data + :rtype: bytes + """ + if nonce is None: + nonce = b"\x00" * 8 + return Salsa20Cipher.new(key=key, nonce=nonce).encrypt(data) + + def decrypt(self, key: bytes, data: bytes, nonce: Optional[bytes] = None) -> bytes: + """ + Decrypts buffer using Salsa20 algorithm. + + :param key: Cryptographic key (16/32 bytes) + :type key: bytes + :param data: Buffer to be decrypted + :type data: bytes + :param nonce: Nonce value (8 bytes, defaults to `b"\\\\x00"*8` ) + :type nonce: bytes, optional + :return: Decrypted data + :rtype: bytes + """ + if nonce is None: + nonce = b"\x00" * 8 + return Salsa20Cipher.new(key=key, nonce=nonce).decrypt(data) + + +salsa20 = Salsa20() diff --git a/malduck/crypto/serpent.py b/malduck/crypto/serpent.py index 83d85da..f0c56d3 100644 --- a/malduck/crypto/serpent.py +++ b/malduck/crypto/serpent.py @@ -14,7 +14,7 @@ def encrypt(self, key: bytes, data: bytes, iv: Optional[bytes] = None) -> bytes: :type key: bytes :param data: Buffer to be encrypted :type data: bytes - :param iv: Initialization vector (defaults to `b"\x00" * 16`) + :param iv: Initialization vector (defaults to `b"\\\\x00" * 16`) :type iv: bytes, optional :return: Encrypted data :rtype: bytes @@ -29,7 +29,7 @@ def decrypt(self, key: bytes, data: bytes, iv: Optional[bytes] = None) -> bytes: :type key: bytes :param data: Buffer to be decrypted :type data: bytes - :param iv: Initialization vector (defaults to `b"\x00" * 16`) + :param iv: Initialization vector (defaults to `b"\\\\x00" * 16`) :type iv: bytes, optional :return: Decrypted data :rtype: bytes diff --git a/tests/test_crypto.py b/tests/test_crypto.py index e5eea2a..45e3306 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -2,7 +2,7 @@ # This file is part of Roach - https://github.com/jbremer/roach. # See the file 'docs/LICENSE.txt' for copying permission. -from malduck import aes, blowfish, des3, rc4, rsa, xor, base64, unhex, rabbit, p8, serpent +from malduck import aes, blowfish, des3, rc4, rsa, xor, base64, unhex, rabbit, p8, serpent, chacha20, salsa20 def test_aes(): @@ -65,6 +65,26 @@ def test_des(): ) == b"\x1d\xed\xc37pV\x89S\xac\xaeT\xaf\xa1\xcfW\xa3" +def test_chacha20(): + assert chacha20.decrypt( + key=b"A"*32, data=b'P\xb6\x12W\xf4\xd7\x83|,\xea\x04n\xba\x08Kj', nonce=b"C"*8 + ) == b"B"*16 + + assert chacha20.encrypt( + key=b"A"*32, data=b"B"*16, nonce=b"C"*8 + ) == b'P\xb6\x12W\xf4\xd7\x83|,\xea\x04n\xba\x08Kj' + + +def test_salsa20(): + assert salsa20.decrypt( + key=b"A"*32, data=b'\x16\x0e\x8d\xe2\xef\x8d\xf1\xc0\xf1\x17\xdf3\xed\xc6\xb5y', nonce=b"C"*8 + ) == b"B"*16 + + assert salsa20.encrypt( + key=b"A"*32, data=b"B"*16, nonce=b"C"*8 + ) == b'\x16\x0e\x8d\xe2\xef\x8d\xf1\xc0\xf1\x17\xdf3\xed\xc6\xb5y' + + def test_rc4(): assert rc4(b"Key", b"Plaintext") == unhex("bbf316e8d940af0ad3") assert rc4(b"Wiki", b"pedia") == unhex("1021bf0420")