Skip to content

Instantly share code, notes, and snippets.

@Sam-Belliveau
Last active July 24, 2019 19:06
Show Gist options
  • Select an option

  • Save Sam-Belliveau/7b10e4dedcfe3c5e41bc38961ebb2be5 to your computer and use it in GitHub Desktop.

Select an option

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
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