diff --git a/DIRECTORY.md b/DIRECTORY.md index fa731e32ff23..d7a5659e91cf 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -113,6 +113,7 @@ * [Porta Cipher](ciphers/porta_cipher.py) * [Rabin Miller](ciphers/rabin_miller.py) * [Rail Fence Cipher](ciphers/rail_fence_cipher.py) + * [Rc4](ciphers/rc4.py) * [Rot13](ciphers/rot13.py) * [Rsa Cipher](ciphers/rsa_cipher.py) * [Rsa Factorization](ciphers/rsa_factorization.py) diff --git a/ciphers/rc4.py b/ciphers/rc4.py new file mode 100644 index 000000000000..463708fff9e9 --- /dev/null +++ b/ciphers/rc4.py @@ -0,0 +1,118 @@ +""" +Implementation of RC4 (Rivest Cipher 4) stream cipher algorithm. + +Reference: +- https://en.wikipedia.org/wiki/RC4 +""" + + +def ksa(key: bytes) -> list[int]: + """ + Key Scheduling Algorithm (KSA) for RC4. + + Args: + key: The secret key as bytes. + + Returns: + A permutation (list) of 256 integers (S-box). + + Example: + >>> ksa(b'Key')[:5] + [0, 1, 2, 3, 4] + """ + key_length = len(key) + s_box = list(range(256)) + j = 0 + + for i in range(256): + j = (j + s_box[i] + key[i % key_length]) % 256 + s_box[i], s_box[j] = s_box[j], s_box[i] + + return s_box + + +def prga(s_box: list[int], message_length: int) -> list[int]: + """ + Pseudo-Random Generation Algorithm (PRGA) for RC4. + + Args: + s_box: The S-box after KSA. + message_length: Number of bytes to generate. + + Returns: + A keystream as a list of integers. + + Example: + >>> prga(list(range(256)), 5) + [0, 1, 2, 3, 4] + """ + i = 0 + j = 0 + keystream = [] + + for _ in range(message_length): + i = (i + 1) % 256 + j = (j + s_box[i]) % 256 + s_box[i], s_box[j] = s_box[j], s_box[i] + k = s_box[(s_box[i] + s_box[j]) % 256] + keystream.append(k) + + return keystream + + +def rc4(key: bytes, data: bytes) -> bytes: + """ + Encrypt or decrypt data using RC4 stream cipher. + + Args: + key: The secret key as bytes. + data: The plaintext or ciphertext as bytes. + + Returns: + Encrypted or decrypted output as bytes. + + Example: + >>> ciphertext = rc4(b'Key', b'Plaintext') + >>> rc4(b'Key', ciphertext) + b'Plaintext' + """ + s_box = ksa(key) + keystream = prga(s_box, len(data)) + output = bytes( + [ + data_byte ^ keystream_byte + for data_byte, keystream_byte in zip(data, keystream) + ] + ) + return output + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="Encrypt or decrypt data using RC4 cipher." + ) + parser.add_argument( + "mode", choices=["encrypt", "decrypt"], help="Mode: encrypt or decrypt" + ) + parser.add_argument("key", type=str, help="Encryption/Decryption key") + parser.add_argument("input", type=str, help="Input text") + + args = parser.parse_args() + + key_bytes = args.key.encode("ascii") + input_bytes = args.input.encode("ascii") + + result_bytes = rc4(key_bytes, input_bytes) + + if args.mode == "encrypt": + print(result_bytes.hex()) + else: # decrypt mode + try: + # if user passed hex data, decode it + input_bytes = bytes.fromhex(args.input) + result_bytes = rc4(key_bytes, input_bytes) + print(result_bytes.decode("ascii")) + except ValueError: + print("Error: Input must be valid hex string when decrypting.")