Last active
July 24, 2019 19:06
-
-
Save Sam-Belliveau/7b10e4dedcfe3c5e41bc38961ebb2be5 to your computer and use it in GitHub Desktop.
An encryption algorithm that uses my ChaCha library to calculate the blocks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from ChaCha import ChaCha | |
| from hashlib import blake2s | |
| import os.path as path | |
| from numpy import uint32 | |
| from numpy import uint64 | |
| import numpy | |
| class ChaCha: | |
| class ByteSizeError(ValueError): | |
| pass | |
| class EndianError(ValueError): | |
| pass | |
| class LookupError(IndexError): | |
| pass | |
| def __init__(self, key, nonce=0, pos=0, constant=b"expand 32-byte k", | |
| stream_size=16, rounds=20, endian='big'): | |
| self._state = [uint32(0)] * 16 | |
| self._stream_size = int(stream_size) | |
| self._rounds = int(rounds) | |
| self._endian = endian.lower() | |
| self.reset(key, nonce, pos, constant) | |
| def _check_for_valid_inputs(self): | |
| # Check if key is correct length | |
| if len(self._key) > 32: | |
| raise self.ByteSizeError("Length of 'key' is too big! ({} > 32)" | |
| .format(len(self._key))) | |
| # Check if constant is correct length | |
| if len(self._constant) > 16: | |
| raise self.ByteSizeError("Length of 'constant' is too big! ({} > 16)" | |
| .format(len(self._constant))) | |
| # Check if constant is correct length | |
| if self._stream_size > 64: | |
| raise self.ByteSizeError("Setting 'stream_size' is to big! ({} > 64)" | |
| .format(len(self._stream))) | |
| if self._stream_size % 4 != 0: | |
| raise self.ByteSizeError("Setting 'stream_size' is not a multiple of 4!") | |
| # Check if endian is valid | |
| if not self._endian.lower() in ['big', 'little']: | |
| raise self.EndianError("Invalid setting for 'endian'! ({})" | |
| .format(self._endian)) | |
| # Check if endian is valid | |
| if not isinstance(self._rounds, int): | |
| raise TypeError("Invalid type for setting 'rounds'!") | |
| # Check if endian is valid | |
| if not isinstance(self._stream_size, int): | |
| raise TypeError("Invalid type for setting 'rounds'!") | |
| def reset(self, key=None, nonce=None, pos=0, constant=None): | |
| # Set all of the state settings | |
| if key is not None: | |
| self._key = bytearray(key) | |
| self._key += bytearray([0] * (32 - len(self._key))) | |
| if nonce is not None: | |
| self._nonce = uint64(nonce) | |
| if constant is not None: | |
| self._constant = bytearray(constant) | |
| self._key += bytearray([0] * (16 - len(self._key))) | |
| # Check inputs for size errors | |
| self._check_for_valid_inputs() | |
| # Constant Row | |
| for i in range(0, 4): | |
| self._state[i + 0] = uint32( | |
| int.from_bytes(self._constant[i*4:i*4 + 4], byteorder=self._endian) | |
| ) | |
| # Key Row | |
| for i in range(0, 8): | |
| self._state[i + 4] = uint32( | |
| int.from_bytes(self._key[i*4:i*4 + 4], byteorder=self._endian) | |
| ) | |
| # Update Position | |
| self.set_pos(pos) | |
| # Nonce Words | |
| if self._endian == 'little': | |
| self._state[14] = uint32((self._nonce >> uint64(00)) & uint64(0xffffffff)) | |
| self._state[15] = uint32((self._nonce >> uint64(32)) & uint64(0xffffffff)) | |
| elif self._endian == 'big': | |
| self._state[14] = uint32((self._nonce >> uint64(32)) * uint64(0xffffffff)) | |
| self._state[15] = uint32((self._nonce >> uint64(00)) & uint64(0xffffffff)) | |
| def set_key(self, key): | |
| self.reset(key=key) | |
| def set_nonce(self, nonce): | |
| self.reset(nonce=nonce) | |
| def set_constant(self, constant): | |
| self.reset(constant=constant) | |
| def set_pos(self, pos=None): | |
| if pos is not None: | |
| self._pos = uint64(pos) | |
| if self._endian == 'little': | |
| self._state[12] = uint32((self._pos >> uint64(00)) & uint64(0xffffffff)) | |
| self._state[13] = uint32((self._pos >> uint64(32)) & uint64(0xffffffff)) | |
| elif self._endian == 'big': | |
| self._state[12] = uint32((self._pos >> uint64(32)) * uint64(0xffffffff)) | |
| self._state[13] = uint32((self._pos >> uint64(00)) & uint64(0xffffffff)) | |
| def get(self): | |
| return { | |
| 'state': self._state, 'key': self._key, | |
| 'nonce': self._nonce, 'constant': self._constant, | |
| 'pos': self._pos, 'rounds': self._rounds, | |
| 'stream_size': self._stream_size, 'endian': self._endian | |
| } | |
| # Quarter Round, mixes up indexs | |
| def _quarter_round(self, ia, ib, ic, id): | |
| def left_rotate(x, r): | |
| return ((uint32(x) << uint32(r)) | | |
| (uint32(x) >> uint32(32 - r))) | |
| self._state[ia] += self._state[ib] | |
| self._state[id] ^= self._state[ia] | |
| self._state[id] = left_rotate(self._state[id], 16) | |
| self._state[ic] += self._state[id] | |
| self._state[ib] ^= self._state[ic] | |
| self._state[ib] = left_rotate(self._state[ib], 12) | |
| self._state[ia] += self._state[ib] | |
| self._state[id] ^= self._state[ia] | |
| self._state[id] = left_rotate(self._state[id], 8) | |
| self._state[ic] += self._state[id] | |
| self._state[ib] ^= self._state[ic] | |
| self._state[ib] = left_rotate(self._state[ib], 7) | |
| def _cha_cha_state(self): | |
| over_err = numpy.geterr()['over'] | |
| numpy.seterr(over='ignore') | |
| self.set_pos() | |
| for i in range(0, self._rounds): | |
| if (i & 1) == 0: | |
| self._quarter_round(0, 4, 8, 12) | |
| self._quarter_round(1, 5, 9, 13) | |
| self._quarter_round(2, 6, 10, 14) | |
| self._quarter_round(3, 7, 11, 15) | |
| else: | |
| self._quarter_round(0, 5, 10, 15) | |
| self._quarter_round(1, 6, 11, 12) | |
| self._quarter_round(2, 7, 8, 13) | |
| self._quarter_round(3, 4, 9, 14) | |
| self._pos += uint64(1) | |
| numpy.seterr(over=over_err) | |
| def next_stream(self, stream_size=None): | |
| def word_to_bytes(word, endian): | |
| out = bytearray() | |
| for i in range(0, 4): | |
| out += bytearray([(uint32(word) >> uint32(i * 8)) & uint32(0xff)]) | |
| if endian == 'big': | |
| return bytearray(reversed(out)) | |
| elif endian == 'little': | |
| return out | |
| if stream_size is None: | |
| stream_size = self._stream_size | |
| if stream_size % 4 != 0: | |
| raise self.ByteSizeError("Setting 'stream_size' is not a multiple of 4!") | |
| if self._stream_size > 64: | |
| raise self.ByteSizeError("Setting 'stream_size' is to big! ({} > 64)" | |
| .format(len(self._stream))) | |
| self._cha_cha_state() | |
| output = bytearray() | |
| for i in range(0, stream_size // 4): | |
| index = (10 + 7 * i) & 0xf | |
| output += word_to_bytes(self._state[index], self._endian) | |
| return bytes(output) | |
| NONCE = 0xd145b414bb462ad3 | |
| ROUNDS = 20 # Normal amount for ChaCha is 20, but being overkill is fine... | |
| BLOCK_SIZE = 16 | |
| def encrypt_file(filename, password): | |
| stream = ChaCha(key=blake2s(password.encode('utf-8')).digest(), nonce=NONCE, rounds=ROUNDS, stream_size=BLOCK_SIZE) | |
| if filename[len(filename)-4:len(filename)] == '.cha': | |
| target_filename = filename[:len(filename)-4] | |
| new_filename = target_filename | |
| new_number = 0 | |
| while path.exists(new_filename): | |
| slash_index = target_filename.rfind('/') + 1 | |
| new_filename = target_filename[:slash_index] | |
| new_filename += '(' + str(new_number) + ') ' | |
| new_filename += target_filename[slash_index:] | |
| new_number += 1 | |
| else: | |
| new_filename = filename + '.cha' | |
| if not path.exists(filename): | |
| raise FileExistsError("{} Does Not Exist!".format(filename)) | |
| with open(filename, 'rb') as in_file, open(new_filename, 'wb') as out_file: | |
| while True: | |
| raw_bytes = bytearray(in_file.read(BLOCK_SIZE)) | |
| if raw_bytes == bytearray(): | |
| break; | |
| stream_block = stream.next_stream() | |
| for i in range(0, len(raw_bytes)): | |
| raw_bytes[i] ^= stream_block[i] | |
| out_file.write((raw_bytes)) | |
| filename = input("Input File Name\n>>> ") | |
| password = input("\nInput Password\n>>> ") | |
| encrypt_file(filename, password) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment