diff --git a/AUTHORS b/AUTHORS index 8b5220bb7..cce3452c7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -11,4 +11,5 @@ Pratik Goyal Jay Thorat Rajveer Singh Bharadwaj Kishan Ved -Arvinder Singh Dhoul \ No newline at end of file +Arvinder Singh Dhoul +Parasa V Prajwal \ No newline at end of file diff --git a/docs/source/pydatastructs/strings/algorithms.rst b/docs/source/pydatastructs/strings/algorithms.rst index aec29a31a..98d2cdad8 100644 --- a/docs/source/pydatastructs/strings/algorithms.rst +++ b/docs/source/pydatastructs/strings/algorithms.rst @@ -1,4 +1,6 @@ Algorithms ========== -.. autofunction:: pydatastructs.find \ No newline at end of file +.. autofunction:: pydatastructs.find + +.. autoclass:: pydatastructs.Crypto \ No newline at end of file diff --git a/pydatastructs/strings/__init__.py b/pydatastructs/strings/__init__.py index 33930b426..b0bc17ea2 100644 --- a/pydatastructs/strings/__init__.py +++ b/pydatastructs/strings/__init__.py @@ -12,7 +12,8 @@ __all__.extend(trie.__all__) from .algorithms import ( - find + find, + Crypto ) __all__.extend(algorithms.__all__) diff --git a/pydatastructs/strings/algorithms.py b/pydatastructs/strings/algorithms.py index 1e26b9411..b4aadadc3 100644 --- a/pydatastructs/strings/algorithms.py +++ b/pydatastructs/strings/algorithms.py @@ -2,9 +2,11 @@ DynamicOneDimensionalArray, OneDimensionalArray) from pydatastructs.utils.misc_util import ( Backend, raise_if_backend_is_not_python) +import struct __all__ = [ - 'find' + 'find', + 'Crypto' ] PRIME_NUMBER, MOD = 257, 1000000007 @@ -245,3 +247,188 @@ def _z_function(text, query): positions.append(pos) return positions + +class Crypto: + + @staticmethod + def _right_rotate(value, shift, size=32): + return (value >> shift) | (value << (size - shift)) & (2**size - 1) + + @staticmethod + def sha256_encrypt(text) -> str: + """ + Finds the SHA256 ciphertext of the given plaintext + + Parameters + ========== + + text: str + The string on which SHA256 encryption is to be performed. + + Returns + ======= + + text: str + The SHA256 encoded ciphertext + + Examples + ======== + + >>> from pydatastructs import Crypto + >>> text = "PyDataStructs" + >>> ciphertext = Crypto.sha256_encrypt(text) + >>> print(ciphertext) + 777a305fe4f1cfc7ce270891ec50651331e2ab6d09312b906740a5ea413bd057 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/SHA-2 + .. [2] https://github.com/TheAlgorithms/Python/blob/master/hashes/sha256.py + + """ + # SHA-256 Constants + k = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + ] + + h = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 + ] + + message = bytearray(text, 'utf-8') + length = len(message) * 8 + message.append(0x80) + while (len(message) * 8) % 512 != 448: + message.append(0) + message += struct.pack('>Q', length) + + for i in range(0, len(message), 64): + chunk = message[i:i+64] + w = list(struct.unpack('>16L', chunk)) + [0] * 48 + for j in range(16, 64): + s0 = (Crypto._right_rotate(w[j-15], 7) ^ Crypto._right_rotate(w[j-15], 18) ^ (w[j-15] >> 3)) + s1 = (Crypto._right_rotate(w[j-2], 17) ^ Crypto._right_rotate(w[j-2], 19) ^ (w[j-2] >> 10)) + w[j] = (w[j-16] + s0 + w[j-7] + s1) & 0xFFFFFFFF + + a, b, c, d, e, f, g, h0 = h + + for j in range(64): + S1 = Crypto._right_rotate(e, 6) ^ Crypto._right_rotate(e, 11) ^ Crypto._right_rotate(e, 25) + ch = (e & f) ^ (~e & g) + temp1 = (h0 + S1 + ch + k[j] + w[j]) & 0xFFFFFFFF + S0 = Crypto._right_rotate(a, 2) ^ Crypto._right_rotate(a, 13) ^ Crypto._right_rotate(a, 22) + maj = (a & b) ^ (a & c) ^ (b & c) + temp2 = (S0 + maj) & 0xFFFFFFFF + + h0, g, f, e, d, c, b, a = (g, f, e, (d + temp1) & 0xFFFFFFFF, c, b, a, (temp1 + temp2) & 0xFFFFFFFF) + + h = [(x + y) & 0xFFFFFFFF for x, y in zip(h, [a, b, c, d, e, f, g, h0])] + + return ''.join(f'{value:08x}' for value in h) + + @staticmethod + def yield_chacha20_xor_stream(key, iv, position=0): + """Generate the xor stream with the ChaCha20 cipher.""" + if not isinstance(position, int): + raise TypeError + if position & ~0xFFFFFFFF: + raise ValueError("Position is not uint32.") + if not isinstance(key, bytes): + raise TypeError + if not isinstance(iv, bytes): + raise TypeError + if len(key) != 32: + raise ValueError + if len(iv) != 8: + raise ValueError + + def rotate(v, c): + return ((v << c) & 0xFFFFFFFF) | v >> (32 - c) + + def quarter_round(x, a, b, c, d): + x[a] = (x[a] + x[b]) & 0xFFFFFFFF + x[d] = rotate(x[d] ^ x[a], 16) + x[c] = (x[c] + x[d]) & 0xFFFFFFFF + x[b] = rotate(x[b] ^ x[c], 12) + x[a] = (x[a] + x[b]) & 0xFFFFFFFF + x[d] = rotate(x[d] ^ x[a], 8) + x[c] = (x[c] + x[d]) & 0xFFFFFFFF + x[b] = rotate(x[b] ^ x[c], 7) + + ctx = [0] * 16 + ctx[:4] = (1634760805, 857760878, 2036477234, 1797285236) + ctx[4:12] = struct.unpack("<8L", key) + ctx[12] = ctx[13] = position + ctx[14:16] = struct.unpack(" 32: + key = key[:32] + + return bytes( + a ^ b for a, b in zip(data, Crypto.yield_chacha20_xor_stream(key, nonce, position)) + ) diff --git a/pydatastructs/strings/tests/test_algorithms.py b/pydatastructs/strings/tests/test_algorithms.py index 37622cf80..833d1cb2f 100644 --- a/pydatastructs/strings/tests/test_algorithms.py +++ b/pydatastructs/strings/tests/test_algorithms.py @@ -1,4 +1,4 @@ -from pydatastructs.strings import find +from pydatastructs.strings import find, Crypto import random, string @@ -74,3 +74,40 @@ def gen_random_string(length): if text != query: positions = find(text, query, algorithm) assert positions.size == 0 + +def _test_sha256_encrypt(): + + test_cases = [ + "HelloWorld", + "1234567890", + "abcdefABCDEF", + "", + "The quick brown fox jumps over the lazy dog", + "Pydatastructs" + ] + + expected_hashes = [ + "872e4e50ce9990d8b041330c47c9ddd11bec6b503ae9386a99da8584e9bb12c4", + "c775e7b757ede630cd0aa1113bd102661ab38829ca52a6422ab782862f268646", + "4a098074bfa74c04454ebbc7d286d085c420934c27ab11e9c00cc53247d9959e", + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592", + "1b5b22944c188c3e90d126ebd27e10d7497fbf5924f23c05775fa2dd9e1d8c86" + ] + + for text, expected in zip(test_cases, expected_hashes): + assert Crypto.sha256_encrypt(text) == expected + +def _test_chacha20(): + import binascii + cnvt = lambda x: binascii.unhexlify(bytes(x, 'ascii')) + test_cases = [ + ('76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669', '0000000000000000000000000000000000000000000000000000000000000000', '0000000000000000'), + ('4540f05a9f1fb296d7736e7b208e3c96eb4fe1834688d2604f450952ed432d41bbe2a0b6ea7566d2a5d1e7e20d42af2c53d792b1c43fea817e9ad275', '0000000000000000000000000000000000000000000000000000000000000001', '0000000000000000'), + ('de9cba7bf3d69ef5e786dc63973f653a0b49e015adbff7134fcb7df137821031e85a050278a7084527214f73efc7fa5b5277062eb7a0433e445f41e3', '0000000000000000000000000000000000000000000000000000000000000000', '0000000000000001'), + ('ef3fdfd6c61578fbf5cf35bd3dd33b8009631634d21e42ac33960bd138e50d32111e4caf237ee53ca8ad6426194a88545ddc497a0b466e7d6bbdb004', '0000000000000000000000000000000000000000000000000000000000000000', '0100000000000000'), + ('f798a189f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c134a4547b733b46413042c9440049176905d3be59ea1c53f15916155c2be8241a38008b9a26bc35941e2444177c8ade6689de95264986d95889fb60e84629c9bd9a5acb1cc118be563eb9b3a4a472f82e09a7e778492b562ef7130e88dfe031c79db9d4f7c7a899151b9a475032b63fc385245fe054e3dd5a97a5f576fe064025d3ce042c566ab2c507b138db853e3d6959660996546cc9c4a6eafdc777c040d70eaf46f76dad3979e5c5360c3317166a1c894c94a371876a94df7628fe4eaaf2ccb27d5aaae0ad7ad0f9d4b6ad3b54098746d4524d38407a6deb', '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', '0001020304050607') + ] + + for i, (ciphertext, key, iv) in enumerate(map(lambda t: tuple(map(cnvt, t)), test_cases)): + assert Crypto.chacha20_encrypt(b'\0' * len(ciphertext), key, iv) == ciphertext