From 413292b6c4950ab5b887c78fad0beff349108823 Mon Sep 17 00:00:00 2001 From: LilPetia Date: Wed, 7 Apr 2021 02:40:23 +0700 Subject: [PATCH 1/4] First release --- README.md | 17 ++++++++++ alphabet.py | 48 ++++++++++++++++++++++++++++ caesar.py | 37 +++++++++++++++++++++ encode.py | 24 ++++++++++++++ encoder_abstract.py | 25 +++++++++++++++ encryptor.py | 22 +++++++++++++ example.txt | 3 ++ main.py | 78 +++++++++++++++++++++++++++++++++++++++++++++ vigenere.py | 38 ++++++++++++++++++++++ 9 files changed, 292 insertions(+) create mode 100644 alphabet.py create mode 100644 caesar.py create mode 100644 encode.py create mode 100644 encoder_abstract.py create mode 100644 encryptor.py create mode 100644 example.txt create mode 100644 main.py create mode 100644 vigenere.py diff --git a/README.md b/README.md index 3f8073c..f95e6b5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,19 @@ # encryptor Encruptor Python MIPT 2021 +Aplication runnig by comand : python3 encryptor.py[command][parameters] +Commands: +encode - ecoding text +--cipher [type of cipher] || Cipher type +--key [key] || key to encrypt +--input [file path] || Path to input file +--output [file path] || Path to output file + + +dencode - decoding text +--cipher [type of cipher] || Cipher type +--key [key] || key to encrypt +--input [file path] || Path to input file +--output [file path] || Path to output file + + +Example: python3 main.py encode --cipher caesar --key 3 --input ./example.txt --output ./encrypted.txt \ No newline at end of file diff --git a/alphabet.py b/alphabet.py new file mode 100644 index 0000000..8586534 --- /dev/null +++ b/alphabet.py @@ -0,0 +1,48 @@ +class Alphabet: + num_to_char = dict() + char_to_num = dict() + for num_char in range(ord('a'), ord('z') + 1): + char = chr(num_char) + pos = len(char_to_num) + num_to_char[pos] = char + char_to_num[char] = pos + + for num_char in range(ord('A'), ord('Z') + 1): + char = chr(num_char) + pos = len(char_to_num) + num_to_char[pos] = char + char_to_num[char] = pos + + @staticmethod + def contains(char: str) -> bool: + return (char in Alphabet.char_to_num) + pass + + @staticmethod + def isalpha(char: str) -> bool: + return (contains(char) and char.isalpha()) + pass + + @staticmethod + def lower(char: str) -> str: + + return char.lower() + + @staticmethod + def order(char: str) -> int: + + return Alphabet.char_to_num[char] + pass + + @staticmethod + def getchar(num: int) -> str: + + return Alphabet.num_to_char[num] + pass + + @staticmethod + def size() -> int: + return len(Alphabet.num_to_char) + pass + + pass diff --git a/caesar.py b/caesar.py new file mode 100644 index 0000000..260ef79 --- /dev/null +++ b/caesar.py @@ -0,0 +1,37 @@ +import abc +from alphabet import Alphabet +from encoder_abstract import Encoder + + +class CaesarEncoder(Encoder): + + def __init__(self, key: int): + self.key = int(key) % Alphabet.size() + + + def encode_char(self, char: str, pos: int) -> str: + if Alphabet.contains(char): + sum_code = Alphabet.order(char) + self.key + return Alphabet.getchar(sum_code % Alphabet.size()) + else: + return char + + + + + +class CaesarDecoder(Encoder): + + def __init__(self, key: int): + self.key = int(key) % Alphabet.size() + + + def encode_char(self, char: str, pos: int) -> str: + if Alphabet.contains(char): + sum_code = Alphabet.order(char) - self.key + return Alphabet.getchar(sum_code % Alphabet.size()) + else: + return char + + + diff --git a/encode.py b/encode.py new file mode 100644 index 0000000..9185405 --- /dev/null +++ b/encode.py @@ -0,0 +1,24 @@ +from caesar import CaesarEncoder, CaesarDecoder +from vigenere import VigenereEncoder, VigenereDecoder + + +def encode(cipher: str, key, text: str) -> str: + + if cipher == 'vigenere': + encoder = VigenereEncoder(key) + elif cipher == 'caesar': + encoder = CaesarEncoder(key) + + return encoder.encode(text) + + + +def decode(cipher: str, key, text: str) -> str: + + if cipher == 'vigenere': + decoder = VigenereDecoder(key) + elif cipher == 'caesar': + decoder = CaesarDecoder(key) + + return decoder.encode(text) + diff --git a/encoder_abstract.py b/encoder_abstract.py new file mode 100644 index 0000000..f44b773 --- /dev/null +++ b/encoder_abstract.py @@ -0,0 +1,25 @@ +import abc + + +class Encoder(): + + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def __init__(self, key): + pass + + @abc.abstractmethod + def encode_char(self, char: str, pos: int) -> str: + pass + + def encode(self, text: str) -> str: + result = "" + position = 0 + for char in text: + result += self.encode_char(char, position) + position += 1 + return result + pass + + pass diff --git a/encryptor.py b/encryptor.py new file mode 100644 index 0000000..4c828b8 --- /dev/null +++ b/encryptor.py @@ -0,0 +1,22 @@ +from caesar import CaesarEncoder, CaesarDecoder +from vigenere import VigenereEncoder, VigenereDecoder + + +def encode(cipher: str, key, text: str) -> str: + + if cipher == 'vigenere': + encoder = VigenereEncoder(key) + elif cipher == 'caesar': + encoder = CaesarEncoder(key) + return encoder.encode(text) + pass + + +def decode(cipher: str, key, text: str) -> str: + if cipher == 'vigenere': + decoder = VigenereDecoder(key) + elif cipher == 'caesar': + decoder = CaesarDecoder(key) + + return decoder.encode(text) + pass diff --git a/example.txt b/example.txt new file mode 100644 index 0000000..6b24ba2 --- /dev/null +++ b/example.txt @@ -0,0 +1,3 @@ + +William Shakespeare +William Shakespeare (baptised 26 April 1564 – died 23 April 1616) was an English poet and playwright, widely regarded as the greatest writer in the English language and the world's pre-eminent dramatist. He is often called England's national poet and the "Bard of Avon" (or simply "The Bard"). His surviving works consist of 38 plays, 154 sonnets, two long narrative poems, and several other poems. His plays have been translated into every major living language, and are performed more often than those of any other playwright. Shakespeare was born and raised in Stratford-upon-Avon. At the age of 18 he married Anne Hathaway, who bore him three children: Susanna, and twins Hamnet and Judith. Between 1585 and 1592 he began a successful career in London as an actor, writer, and part owner of the playing company the Lord Chamberlain's Men, later known as the King's Men. He appears to have retired to Stratford around 1613, where he died three years later. Few records of Shakespeare's private life survive, and there has been considerable speculation about such matters as his sexuality, religious beliefs, and whether the works attributed to him were written by others. Shakespeare produced most of his known work between 1590 and 1613. His early plays were mainly comedies and histories, genres he raised to the peak of sophistication and artistry by the end of the sixteenth century. Next he wrote mainly tragedies until about 1608, including Hamlet, King Lear, and Macbeth, considered some of the finest examples in the English language. In his last phase, he wrote tragicomedies, also known as romances, and collaborated with other playwrights. Many of his plays were published in editions of varying quality and accuracy during his lifetime, and in 1623 two of his former theatrical colleagues published the First Folio, a collected edition of his dramatic works that included all but two of the plays now recognised as Shakespeare's. Shakespeare was a respected poet and playwright in his own day, but his reputation did not rise to its present heights until the nineteenth century. The Romantics, in particular, acclaimed Shakespeare's genius, and the Victorians hero-worshipped Shakespeare with a reverence that George Bernard Shaw called "bardolatry". In the twentieth century, his work was repeatedly adopted and rediscovered by new movements in scholarship and performance. His plays remain highly popular today and are consistently performed and reinterpreted in diverse cultural and political contexts throughout the world. Source: Wikipedia diff --git a/main.py b/main.py new file mode 100644 index 0000000..27179e3 --- /dev/null +++ b/main.py @@ -0,0 +1,78 @@ +import argparse +import json +import sys +from encode import encode, decode + + +def parse_args(): + + parser = argparse.ArgumentParser() + + subparsers = parser.add_subparsers() + + # Encoding + encode_parser = subparsers.add_parser('encode') + encode_parser.set_defaults(mode='encode') + encode_parser.add_argument('--cipher', choices=['caesar', 'vigenere'], help='Type of cipher', required=True) + encode_parser.add_argument('--key', help='Key to encode', required=True) + encode_parser.add_argument('--input', type=argparse.FileType('r'), help='Input file', required=True) + encode_parser.add_argument('--output', type=argparse.FileType('w'), help='Output file', required=True) + + # Decoding + decode_parser = subparsers.add_parser('decode') + decode_parser.set_defaults(mode='decode') + decode_parser.add_argument('--cipher', choices=['caesar', 'vigenere'], help='Type of cipher', required=True) + decode_parser.add_argument('--key', help='Key to decode', required=True) + decode_parser.add_argument('--input', type=argparse.FileType('r'), help='Input file', required=True) + decode_parser.add_argument('--output', type=argparse.FileType('w'), help='Output file', required=True) + + + return parser.parse_args() + + + +def input(args) -> dict: + + args = parse_args() + + if args.input: + args.text = args.input.read() + else: + args.text = sys.stdin.read() + + if args.mode == 'encode': + return { + "mode": args.mode, + "cipher": args.cipher, + "key": args.key, + "text": args.text + } + elif args.mode == 'decode': + return { + "mode": args.mode, + "cipher": args.cipher, + "key": args.key, + "text": args.text + } + + + +def output(args, result: str): + + + if args.output: + args.output.write(result) + else: + sys.stdout.write(result) + + + + +shell_args = parse_args() +args = input(shell_args) + +if args['mode'] == 'encode': + result = encode(cipher=args['cipher'], key=args['key'], text=args['text']) +elif args['mode'] == 'decode': + result = decode(cipher=args['cipher'], key=args['key'], text=args['text']) +output(shell_args, result) diff --git a/vigenere.py b/vigenere.py new file mode 100644 index 0000000..c6dffff --- /dev/null +++ b/vigenere.py @@ -0,0 +1,38 @@ +import abc +from alphabet import Alphabet +from encoder_abstract import Encoder + + +class VigenereEncoder(Encoder): + def __init__(self, key: str): + if key == "": + raise Exception("Empty string") + self.key = key + pass + + def encode_char(self, char: str, pos: int) -> str: + if Alphabet.contains(char): + sum_code = Alphabet.order(char) + Alphabet.order(self.key[pos % len(self.key)]) + return Alphabet.getchar(sum_code % Alphabet.size()) + else: + return char + pass + + pass + + +class VigenereDecoder(Encoder): + + def __init__(self, key: str): + if key == "": + raise Exception("Empty string") + self.key = key + pass + + def encode_char(self, char: str, pos: int) -> str: + if Alphabet.contains(char): + sum_code = Alphabet.order(char) - Alphabet.order(self.key[pos % len(self.key)]) + return Alphabet.getchar(sum_code % Alphabet.size()) + else: + return char + pass From 3b283806cdea1478e30348e97ad344a67559cd96 Mon Sep 17 00:00:00 2001 From: LilPetia Date: Wed, 7 Apr 2021 02:44:16 +0700 Subject: [PATCH 2/4] fix README.md --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f95e6b5..fb81f4f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # encryptor Encruptor Python MIPT 2021 -Aplication runnig by comand : python3 encryptor.py[command][parameters] +Aplication runnig by comand : +python3 encryptor.py[command][parameters] + + Commands: encode - ecoding text --cipher [type of cipher] || Cipher type @@ -9,7 +12,7 @@ encode - ecoding text --output [file path] || Path to output file -dencode - decoding text +decode - decoding text --cipher [type of cipher] || Cipher type --key [key] || key to encrypt --input [file path] || Path to input file From c45765fd60434b01070defee81398e3f531aa5e1 Mon Sep 17 00:00:00 2001 From: LilPetia Date: Wed, 7 Apr 2021 02:45:08 +0700 Subject: [PATCH 3/4] fix README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fb81f4f..b988490 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # encryptor Encruptor Python MIPT 2021 Aplication runnig by comand : + python3 encryptor.py[command][parameters] From 873d76b8c3aaec833c91e25e1cc5180f55494980 Mon Sep 17 00:00:00 2001 From: LilPetia Date: Sun, 18 Apr 2021 03:10:46 +0700 Subject: [PATCH 4/4] Add cipher vernam and module to huck caesar cipher. Bad arguments chesk in argparse. --- ASCI.py | 43 +++++++++++++++++++++++++++++ README.md | 19 ++++++++++--- encode.py | 24 +++++++++++++--- encryptor.py | 22 --------------- hacker.py | 65 +++++++++++++++++++++++++++++++++++++++++++ main.py | 78 +++++++++++++++++++++++++++++++++++++++++++--------- train.py | 59 +++++++++++++++++++++++++++++++++++++++ vernam.py | 32 +++++++++++++++++++++ 8 files changed, 299 insertions(+), 43 deletions(-) create mode 100644 ASCI.py delete mode 100644 encryptor.py create mode 100644 hacker.py create mode 100644 train.py create mode 100644 vernam.py diff --git a/ASCI.py b/ASCI.py new file mode 100644 index 0000000..46e1bd7 --- /dev/null +++ b/ASCI.py @@ -0,0 +1,43 @@ +class ASCI: + num_to_char = dict() + char_to_num = dict() + + for num_char in range(0,128): + char = chr(num_char) + pos = len(char_to_num) + num_to_char[pos] = char + char_to_num[char] = pos + + @staticmethod + def contains(char: str) -> bool: + return (char in ASCI.char_to_num) + pass + + @staticmethod + def isalpha(char: str) -> bool: + return (contains(char) and char.isalpha()) + pass + + @staticmethod + def lower(char: str) -> str: + + return char.lower() + + @staticmethod + def order(char: str) -> int: + + return ASCI.char_to_num[char] + pass + + @staticmethod + def getchar(num: int) -> str: + + return ASCI.num_to_char[num] + pass + + @staticmethod + def size() -> int: + return len(ASCI.num_to_char) + pass + + pass diff --git a/README.md b/README.md index b988490..296e5bf 100644 --- a/README.md +++ b/README.md @@ -6,18 +6,29 @@ python3 encryptor.py[command][parameters] Commands: -encode - ecoding text +encode - encoding text --cipher [type of cipher] || Cipher type ---key [key] || key to encrypt +--key [key] || Key to encrypt --input [file path] || Path to input file --output [file path] || Path to output file decode - decoding text --cipher [type of cipher] || Cipher type ---key [key] || key to encrypt +--key [key] || Key to encrypt --input [file path] || Path to input file --output [file path] || Path to output file -Example: python3 main.py encode --cipher caesar --key 3 --input ./example.txt --output ./encrypted.txt \ No newline at end of file +train - training model +--input [file path] || Text to analyze +--output [file path] || Output model + + +hack - hack caesar cipher +--input [file path] || File to hack +--output [file path] || Hacked file +--model [file path] || Model file + +Example: python3 main.py encode --cipher caesar --key 231 --input ./example.txt --output ./encrypted.txt + python3 main.py encode --cipher vernam --key 231 --input ./example.txt --output ./encrypted.txt \ No newline at end of file diff --git a/encode.py b/encode.py index 9185405..d87467d 100644 --- a/encode.py +++ b/encode.py @@ -1,6 +1,8 @@ from caesar import CaesarEncoder, CaesarDecoder from vigenere import VigenereEncoder, VigenereDecoder - +from vernam import VernamDecoder, VernamEncoder +from hacker import CaesarHacker +from train import FrequencyTrainer def encode(cipher: str, key, text: str) -> str: @@ -8,17 +10,31 @@ def encode(cipher: str, key, text: str) -> str: encoder = VigenereEncoder(key) elif cipher == 'caesar': encoder = CaesarEncoder(key) - + elif cipher == 'vernam': + encoder = VernamEncoder(key) return encoder.encode(text) def decode(cipher: str, key, text: str) -> str: - if cipher == 'vigenere': decoder = VigenereDecoder(key) elif cipher == 'caesar': decoder = CaesarDecoder(key) - + elif cipher == 'vernam': + decoder = VernamDecoder(key) return decoder.encode(text) + +def train(text: str) -> str: + trainer = FrequencyTrainer() + trainer.train(text) + return trainer.get_json_model() + pass + + +def hack(model: dict, text: str) -> str: + hacker = CaesarHacker(model) + hacker_decoder = CaesarDecoder(hacker.hack_key(text)) + return hacker_decoder.encode(text) + pass \ No newline at end of file diff --git a/encryptor.py b/encryptor.py deleted file mode 100644 index 4c828b8..0000000 --- a/encryptor.py +++ /dev/null @@ -1,22 +0,0 @@ -from caesar import CaesarEncoder, CaesarDecoder -from vigenere import VigenereEncoder, VigenereDecoder - - -def encode(cipher: str, key, text: str) -> str: - - if cipher == 'vigenere': - encoder = VigenereEncoder(key) - elif cipher == 'caesar': - encoder = CaesarEncoder(key) - return encoder.encode(text) - pass - - -def decode(cipher: str, key, text: str) -> str: - if cipher == 'vigenere': - decoder = VigenereDecoder(key) - elif cipher == 'caesar': - decoder = CaesarDecoder(key) - - return decoder.encode(text) - pass diff --git a/hacker.py b/hacker.py new file mode 100644 index 0000000..411f460 --- /dev/null +++ b/hacker.py @@ -0,0 +1,65 @@ +from abc import ABCMeta, abstractmethod +from train import FrequencyTrainer +from alphabet import Alphabet + + +class Hacker: + + __metaclass__ = ABCMeta + + @abstractmethod + def __init__(self, model): + self.model = dict(model) + pass + + @abstractmethod + def hack_text(self, text: str) -> str: + pass + + pass + + +class CaesarHacker(Hacker): + + def __init__(self, model): + super().__init__(model) + pass + + def find_frequency(self, text: str): + + self.frequency = {} + + for char in text: + if not Alphabet.contains(char): + continue + if char not in self.frequency: + self.frequency[char] = 0 + self.frequency[char] += 1 + pass + + pass + + def hack_key(self, text: str): + + self.find_frequency(text) + + min_shift = 0 + min_delta = -1 + + for shift in range(0, Alphabet.size()): + delta = 0 + for (char, freq) in self.frequency.items(): + num_new_char = (Alphabet.order(char) + shift) % Alphabet.size() + new_char = Alphabet.getchar(num_new_char) + + delta += (self.frequency[char] if char in self.frequency else 0) ** 2 - \ + (self.model[new_char] if new_char in self.model else 0) ** 2 + + if min_delta == -1 or delta < min_delta: + min_delta = delta + min_shift = shift + + return Alphabet.size() - min_shift + pass + + pass diff --git a/main.py b/main.py index 27179e3..538ddc5 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,7 @@ import argparse import json import sys -from encode import encode, decode +from encode import encode, decode, train, hack def parse_args(): @@ -13,20 +13,47 @@ def parse_args(): # Encoding encode_parser = subparsers.add_parser('encode') encode_parser.set_defaults(mode='encode') - encode_parser.add_argument('--cipher', choices=['caesar', 'vigenere'], help='Type of cipher', required=True) - encode_parser.add_argument('--key', help='Key to encode', required=True) - encode_parser.add_argument('--input', type=argparse.FileType('r'), help='Input file', required=True) - encode_parser.add_argument('--output', type=argparse.FileType('w'), help='Output file', required=True) + encode_parser.add_argument('--cipher', + choices=['caesar', 'vigenere', 'vernam'], + help='Type of cipher', required=True) + encode_parser.add_argument('--key', help='Key to encode', + required=True) + encode_parser.add_argument('--input', type=argparse.FileType('r'), + help='Input file', required=True) + encode_parser.add_argument('--output', type=argparse.FileType('w'), + help='Output file', required=True) # Decoding decode_parser = subparsers.add_parser('decode') decode_parser.set_defaults(mode='decode') - decode_parser.add_argument('--cipher', choices=['caesar', 'vigenere'], help='Type of cipher', required=True) - decode_parser.add_argument('--key', help='Key to decode', required=True) - decode_parser.add_argument('--input', type=argparse.FileType('r'), help='Input file', required=True) - decode_parser.add_argument('--output', type=argparse.FileType('w'), help='Output file', required=True) - - + decode_parser.add_argument('--cipher', + choices=['caesar', 'vigenere', 'vernam'], + help='Type of cipher', required=True) + decode_parser.add_argument('--key', help='Key to decode', + required=True) + decode_parser.add_argument('--input', type=argparse.FileType('r'), + help='Input file', required=True) + decode_parser.add_argument('--output', type=argparse.FileType('w'), + help='Output file', required=True) + + # Train + train_parser = subparsers.add_parser('train') + train_parser.set_defaults(mode='train') + train_parser.add_argument('--input', type=argparse.FileType('r'), + help='File with text') + train_parser.add_argument('--output', type=argparse.FileType('w'), + help='File for model', required=True) + + # Hack + hack_parser = subparsers.add_parser('hack') + hack_parser.set_defaults(mode='hack') + hack_parser.add_argument('--input', type=argparse.FileType('r'), + help='Input file') + hack_parser.add_argument('--output', type=argparse.FileType('w'), + help='Output file') + hack_parser.add_argument('--model', type=argparse.FileType('r'), + help='File with model', required=True) + return parser.parse_args() @@ -54,6 +81,23 @@ def input(args) -> dict: "key": args.key, "text": args.text } + elif args.mode == 'train': + return { + "mode": args.mode, + "text": args.text + } + elif args.mode == 'hack': + try: + args.model = json.load(args.model) + except json.JSONDecodeError: + raise Exception('Model file is not in json format') + + return { + "mode": args.mode, + "text": args.text, + "model": args.model + } + @@ -72,7 +116,15 @@ def output(args, result: str): args = input(shell_args) if args['mode'] == 'encode': - result = encode(cipher=args['cipher'], key=args['key'], text=args['text']) + result = encode(cipher=args['cipher'], + key=args['key'], text=args['text']) elif args['mode'] == 'decode': - result = decode(cipher=args['cipher'], key=args['key'], text=args['text']) + result = decode(cipher=args['cipher'], + key=args['key'], text=args['text']) +elif args['mode'] == 'train': + result = train(text=args['text']) +elif args['mode'] == 'hack': + result = hack(model=args['model'], text=args['text']) + output(shell_args, result) + diff --git a/train.py b/train.py new file mode 100644 index 0000000..35d7639 --- /dev/null +++ b/train.py @@ -0,0 +1,59 @@ +from abc import ABCMeta, abstractmethod +import json +from alphabet import Alphabet + + +class Trainer: + + __metaclass__ = ABCMeta + + @abstractmethod + def __init__(self): + pass + + @abstractmethod + def train(self, text: str): + pass + + @abstractmethod + def clear(self): + pass + + @abstractmethod + def get_dict_model(self) -> dict: + pass + + @abstractmethod + def get_json_model(self) -> str: + pass + + pass + + +class FrequencyTrainer(Trainer): + + def __init__(self): + self.model = {} + + def train(self, text: str): + for char in text: + if not Alphabet.contains(char): + continue + if char not in self.model: + self.model[char] = 0 + self.model[char] += 1 + pass + + def clear(self): + self.model = {} + pass + + def get_dict_model(self) -> dict: + return self.model + pass + + def get_json_model(self) -> str: + return json.dumps(self.model) + pass + + pass diff --git a/vernam.py b/vernam.py new file mode 100644 index 0000000..b0e668f --- /dev/null +++ b/vernam.py @@ -0,0 +1,32 @@ +import abc + +from ASCI import ASCI +from encoder_abstract import Encoder + +class VernamEncoder(Encoder): + def __init__(self, key: int): + if (key == ""): + raise Exception("Empty string") + self.key = int(key) % ASCI.size() + pass + def encode_char(self, char:str, pos: int) -> str: + if (ASCI.contains(char)): + return ASCI.getchar((ASCI.order(char) ^ self.key) % ASCI.size()) + else: + return char + + +class VernamDecoder(Encoder): + + def __init__(self, key: str): + if key == "": + raise Exception("Empty string") + self.key = int(key) % ASCI.size() + pass + + def encode_char(self, char: str, pos: int) -> str: + if ASCI.contains(char): + return ASCI.getchar((ASCI.order(char) ^ self.key) % ASCI.size()) + else: + return char + pass \ No newline at end of file