Skip to content

Commit

Permalink
Major updates and security improvements.
Browse files Browse the repository at this point in the history
  • Loading branch information
hg0428 committed Nov 21, 2023
1 parent ac49242 commit 4bac856
Show file tree
Hide file tree
Showing 15 changed files with 269 additions and 182 deletions.
92 changes: 26 additions & 66 deletions PCSS/__init__.py
Original file line number Diff line number Diff line change
@@ -1,66 +1,43 @@
from passlib.hash import pbkdf2_sha512
from passlib.utils.binary import ab64_decode
from uuid import uuid4, uuid5, uuid1
import hashlib
import random
import time
from bitarray import bitarray
from typing import Union
from .util import to_bitarray

makeid = lambda: hashlib.sha256(
str(
random.randint(-2**512, 2**512 + time.time())
).encode()).hexdigest() + hashlib.sha512(str(uuid1()).encode()).hexdigest(
) + str(uuid4()) + str(uuid5(uuid4(), str(uuid1())))
str(random.randint(-2**512, 2**512 + time.time())).encode()).hexdigest(
) + hashlib.sha512(str(uuid1()).encode()).hexdigest() + str(uuid4()) + str(
uuid5(uuid4(), str(uuid1())))


def fill(data: bitarray, length: int) -> bitarray:
data.extend([0] * (length - len(data)))
return data


def encrypt(data: Union[str, bytes, bitarray],
key: Union[str, bytes, bitarray, None] = None,
def encrypt(data: Union[str, bytes, bitarray, int],
key: Union[str, bytes, bitarray, int, None] = None,
final_key: Union[bitarray, None] = None,
rounds=None,
salt=None) -> bitarray:
"""
Encrypts data using an advanced method including XOR encrytion and byte and bit reversal.
`salt` will defualt to the key.
"""
if type(data) == bytes:
x = bitarray()
x.frombytes(data)
data = x
elif type(data) == str:
x = bitarray()
x.frombytes(data.encode())
data = x
elif type(data) != bitarray:
raise TypeError("`data` must be a bitarray or bytes-like object.")
data = to_bitarray(data)
if final_key == None:
if key == None:
raise ValueError("Either key OR final_key must be provided.")
if type(key) == bytes:
x = bitarray()
x.frombytes(key)
key = x
elif type(key) == str:
x = bitarray()
x.frombytes(key.encode())
key = x
elif type(key) != bitarray:
raise TypeError("`key` must be a bitarray or bytes-like object.")
else:
key = to_bitarray(key)
final_key = bitarray()
if rounds == None:
rounds = (int(key[:15].to01(), 2) + 100) * 2
final_key.frombytes(
pbkdf2_sha512.hash(key.tobytes(),
rounds=rounds,
salt=salt if salt else key.tobytes()).encode())
x = pbkdf2_sha512.hash(key.tobytes(),
rounds=rounds,
salt=salt if salt else key.tobytes()).split('$')[-1]
final_key.frombytes(ab64_decode(x))
if len(final_key) > len(data):
final_key = final_key[-len(data):]
#TODO: CHAGE THIS BECAUSE THIS DISREGARDS THE END, which is the most important. The rounds only seems to effect the end of it.
# final_key = final_key[:len(data)]
while len(final_key) < len(data):
final_key += final_key[:max(len(data) - len(final_key), len(final_key))]
data ^= final_key
Expand All @@ -69,51 +46,31 @@ def encrypt(data: Union[str, bytes, bitarray],
return data


def decrypt(encrypted_data: Union[str, bytes, bitarray],
key: Union[str, bytes, bitarray, None] = None,
def decrypt(encrypted_data: Union[str, bytes, bitarray, int],
key: Union[str, bytes, bitarray, int, None] = None,
final_key: Union[bitarray, None] = None,
rounds=None,
salt=None) -> bitarray:
"""
Decrypts data that was encrypted using the PCSS.encrypt function. Parameters that do not match will generate an incorrect output.
"""
if type(encrypted_data) == bytes:
x = bitarray()
x.frombytes(encrypted_data)
encrypted_data = x
elif type(encrypted_data) == str:
x = bitarray()
x.frombytes(encrypted_data.encode())
encrypted_data = x
elif type(encrypted_data) != bitarray:
raise TypeError(
"`encrypted_data` must be a bitarray or bytes-like object.")
encrypted_data = to_bitarray(encrypted_data)
if final_key == None:
if key == None:
raise ValueError("Either key OR final_key must be provided.")
if type(key) == bytes:
x = bitarray()
x.frombytes(key)
key = x
elif type(key) == str:
x = bitarray()
x.frombytes(key.encode())
key = x
elif type(key) != bitarray:
raise TypeError("`key` must be a bitarray or bytes-like object.")
else:
key = to_bitarray(key)
if rounds == None:
rounds = (int(key[:15].to01(), 2) + 100) * 2
final_key = bitarray()
final_key.frombytes(
pbkdf2_sha512.hash(key.tobytes(),
rounds=rounds,
salt=salt if salt else key.tobytes()).encode())
x = pbkdf2_sha512.hash(key.tobytes(),
rounds=rounds,
salt=salt if salt else key.tobytes()).split('$')[-1]
final_key.frombytes(ab64_decode(x))
encrypted_data.reverse()
encrypted_data.bytereverse()
if len(final_key) > len(encrypted_data):
final_key = final_key[-len(encrypted_data):]
#TODO: CHAGE THIS BECAUSE THIS DISREGARDS THE END, which is the most important. The rounds only seems to effect the end of it.
# final_key = final_key[:len(encrypted_data)]
while len(final_key) < len(encrypted_data):
final_key += final_key[:max(
len(encrypted_data) - len(final_key), len(final_key))]
Expand All @@ -122,6 +79,9 @@ def decrypt(encrypted_data: Union[str, bytes, bitarray],


def bitarray_to_text(data: bitarray) -> str:
"""
Converts a bitarray to a string.
"""
return data.tobytes().rstrip(b'\x00').decode()


Expand Down
Binary file modified PCSS/__pycache__/__init__.cpython-310.pyc
Binary file not shown.
Binary file modified PCSS/__pycache__/hash.cpython-310.pyc
Binary file not shown.
Binary file added PCSS/__pycache__/util.cpython-310.pyc
Binary file not shown.
90 changes: 41 additions & 49 deletions PCSS/hash.py
Original file line number Diff line number Diff line change
@@ -1,67 +1,59 @@
from bitarray import bitarray
from bitarray.util import int2ba
from hashlib import sha256
from random import shuffle, seed


def pad_bitarray_left(ba, size=4):
# Calculate the number of bits to pad
pad_size = size - len(ba)

# Create a new bitarray for padding
pad = bitarray(pad_size)
pad.setall(0)

# Prepend the padding to the original bitarray
ba[:0] = pad
return ba


def merge_bitarrays(*bitarrays):
merged_bitarray = bitarray()

# Extend the result bitarray with each bitarray in the list
for ba in bitarrays:
merged_bitarray.extend(ba)
return merged_bitarray


def split_bitarray(ba, block_size=4):
return [
pad_bitarray_left(ba[i:i + block_size], block_size)
for i in range(0, len(ba), block_size)
]


def hash(data: str, length: int = 256, block_size: int = 4) -> str:
from random import shuffle, seed, randint
import base64
from typing import Union
from .util import to_bitarray, merge_bitarrays, pad_bitarray_left, split_bitarray
import math


def hash(data: Union[str, int, bitarray, bytes],
length: int = 256,
block_size: int = 2,
affected_area: int = 0.5,
output: str = "hex") -> Union[bitarray, str, int, bytes]:
"""
Hashes data using the hashing PCSS algorithm.
The length of the hash is supplied as the length arguemnt and it must be divisible by the block size (which defaults to 4).
An increased block size results in increased randomness.
The length of the hash is supplied as the length arguemnt and it must be divisible by the block size (which defaults to 4).
An increased block size results in increased randomness.
`affected_area` is the number of input blocks used in the output block calculation. If it is an integer, it represents a concrete value, and if it is between 0 and 1 it is proportional to the number of blocks. This value is very particular, be careful!
Output can be "hex", "bitarray", "bytes", "base64", "base64-bytes", or "int".
"""
if length % block_size != 0:
raise ValueError("Length must be a multiple of block_size")
encoded = bitarray()
encoded.frombytes(data.encode('utf-8'))
hash_data = [
encoded,
int2ba(len(data)),
int2ba(length),
int2ba(encoded.count(1))
]
encoded = to_bitarray(data)
hash_data = [encoded, int2ba(length), int2ba(encoded.count(1))]
blocks = []
for x in hash_data:
blocks = blocks + split_bitarray(x, block_size)
emptyblocks = [bitarray(block_size) for _ in range(length)]
seed((length + block_size + len(data)) * int(blocks[0].to01(), 2))
emptyblocks = [bitarray(block_size) for _ in range(int(length / block_size))]
seed((affected_area + (length + block_size) * int(blocks[0].to01(), 2)) *
int(blocks[-1].to01(), 2) + int(encoded.to01(), 2))
if affected_area > 0 and affected_area < 1:
affected_area = math.floor(affected_area * len(blocks))
for index, i in enumerate(emptyblocks):
i.setall(0)
shuffle(blocks)
for block in blocks:
x = randint(0, len(blocks) - affected_area - 1)
for block in blocks[x:x + affected_area]:
i ^= block
x = bitarray(blocks[min(index, len(blocks) - 1)])
x.reverse()
i ^= ~x
emptyblocks[index] = ~i
return ''.join('{:x}'.format(int(i.to01(), 2))
for i in split_bitarray(merge_bitarrays(*emptyblocks), 4))
merged = merge_bitarrays(*emptyblocks)
if output == "hex":
return ''.join('{:x}'.format(int(i.to01(), 2))
for i in split_bitarray(merged, 4))
elif output == 'bitarray':
return merged
elif output == 'bytes':
return merged.tobytes()
elif output == 'base64':
return base64.b64encode(merged.tobytes()).decode('utf-8')
elif output == 'base64-bytes':
return base64.b64encode(merged.tobytes())
elif output == 'int':
return int(merged.to01(), 2)
52 changes: 52 additions & 0 deletions PCSS/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from bitarray import bitarray
from typing import Union
from bitarray.util import int2ba


def to_bitarray(data: Union[str, bytes, bitarray, int]) -> bitarray:
"""
Converts a string, bytes-like object, int, or bitarray to a bitarray.
"""
if type(data) == bytes:
x = bitarray()
x.frombytes(data)
return x
elif type(data) == str:
x = bitarray()
x.frombytes(data.encode())
return x
elif type(data) == int:
return int2ba(data)
elif type(data) == bitarray:
return data
elif type(data) != bitarray:
raise TypeError("`data` must be a bitarray or bytes-like object.")


def pad_bitarray_left(ba, size=4):
# Calculate the number of bits to pad
pad_size = size - len(ba)

# Create a new bitarray for padding
pad = bitarray(pad_size)
pad.setall(0)

# Prepend the padding to the original bitarray
ba[:0] = pad
return ba


def merge_bitarrays(*bitarrays):
merged_bitarray = bitarray()

# Extend the result bitarray with each bitarray in the list
for ba in bitarrays:
merged_bitarray.extend(ba)
return merged_bitarray


def split_bitarray(ba, block_size=4):
return [
pad_bitarray_left(ba[i:i + block_size], block_size)
for i in range(0, len(ba), block_size)
]
48 changes: 31 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,32 @@ You can install it with pip.

# Usage

## Encyrpt and Decrypt
## Encrypt and Decrypt
```py
from PCSS import encrypt, decrypt, makeid, bitarray_to_text
from PCSS.hash import hash

print(f"""
Hash of Hi!: {hash("Hi!")}
Hash of Hello_World!: {hash("Hello_World!")}
Hash of Hello_World! : {hash("Hello_World! ")}
Hash of Hello World: {hash("Hello World")}
Hash of Password#123: {hash("Password#123")}
Hash of AAAAAAAAAAAA: {hash("AAAAAAAAAAAA")}
Hash of !#!#!#!#!#!#!#!#: {hash("!#!#!#!#!#!#!#!#")}
Hash of Hi!: {hash("Hi!", 256)}
Hash of Hello_World!: {hash("Hello_World!", length=256)}
Hash of Hello_World! : {hash("Hello_World! ", length=256)}
Hash of Hello World: {hash("Hello World", length=256)}
Hash of Password#123: {hash("Password#123", length=256)}
Hash of AAAAAAAAAAAA: {hash("AAAAAAAAAAAA", length=256)}
Hash of AAAAAAAAAAAB: {hash("AAAAAAAAAAAB", length=256)}
Hash of !#!#!#!#!#!#!#!#: {hash("!#!#!#!#!#!#!#!#", length=256)}
""")

# Output:
"""
Hash of Hi!: 32aa253325323f2f7a22f52313272b77335b11b25327aab12772a3522522bf77322223233227222ff2ff3b33332a27252b3ff323127b2335ba752f2a1f3b22122b2b23522522af3f22f522a735f273112332f722b227f122b22111f22222532f75327172332271377a2ffb3322232ba22f3bf2223322f73ab12f52f52a1ab332
Hash of Hello_World!: 0c1c55cb50099c5305199d9500932925530517975755d00b20cc222909550305039275050005905205d7770530c5c0953d50cd9559155729c39101352395c7cc5971b700d925d3d5570500d1c725992c957d500b555950352521dd7b3c505d759d057377005550252b552335d0171071092dd5000959005d53c75170520c5297
Hash of Hello_World! : 117eb51b771a2557be7fbd710b2231e1e1775227ff13b217e551137e7f55977ddee73e70b0922232a21e773777bb1e155170150172b9713f7f273a3d32737db5ee29af755f75711fb7bf59729efadb12eff722177777e2725e2be7129055075050b55535270302e252213ef752d10b7772b7ba27e5772773732f1f7579777107
Hash of Hello World: d9ed49a56eea69bc42ce69e89c25bc828bee5d7ece576e6bbb76b94eee779e575daba4be8ece2e8899e2e2bec978b87d7d79a972225e246b687ede897bb556a7e6e8abaebaee6ee282eb45beee4ee5858a8e2e2bcbdbcb84ceee845ee565769cde5ee99958a296e9eee87d774ce82beb8eeaeedceeeb8ec4b8d929e88eebcbea
Hash of Password#123: acae4c46684ec4cefccce6c61c2e6c330fe4fcc8c12cec2004a4acaec3ea2c0448e8ee464ca2ceee6ca2eea62acaa230e6feccaeadcefeec2ee318ccc382ec6c4ecdee46e2cc664ece2eeaeee424c1eee6cece68e2e0e64ee46dfee8ceccc6eccfc26eecc6eeeceee28aa2a41c1f22ac63ee46660ec8c48c2e4ce6aecea4aeea
Hash of AAAAAAAAAAAA: b0a0aa00abba00a80a0aaa0a0b089a0aa8ba080aaaaa0bb00b00909a0aaa08ba0800aa0a000aa0a0baa8a00a8b0a000a80ab0a0aaa0aa09a08a00a8a98aa0000aa0a0abbaa0a080aaaba0b000a0aaa90aaa0ab00aaaaab8a9a90aaa080a0aaaaaa0aa88ab0aaa09a90aa088aa0a0a0aa0a00aa00baaab0aaa808a0aba000a008
Hash of !#!#!#!#!#!#!#!#: beab626ee6e6b66e666bbea622ae26622efafaee2e2b6ae66eeeeeee22ae6626aea62beeee26eefb222ee622ea6eee66ea66e2ee22e6a66e2ae2666f6ee6fee6e2eeb6226622e2a6e2ebf666ee62a2bb2eeeee6a26eee6e2eeeee262ae2e6afa66e2eee6e66e62e626e2aa22ebbea6ee26e6a2abbe26eee62226eeee6a6b6e62
Hash of Hi!: 293bcd0b497216b999a7e81e2b7cd7530017f44dd53472710d2b44bfea670227
Hash of Hello_World!: 23c5fc4b5f59a9ebb20c3a88de653214fba07b382c4e0acfe09b76e6b0073cf3
Hash of Hello_World! : 74d6ae99037683397fadda7da65afa72c0560d6e64319d0c86abe2301b46146d
Hash of Hello World: cc65180fbca2636fd269c3830dfc3903679f394d10a233ceae9bf9ac5c26a177
Hash of Password#123: 491d11b7e30323855c9c8fe742c947ee9c4f718f602b0275620bf9c4937dbd1a
Hash of AAAAAAAAAAAA: 4f14559045890891b74ec030af9d4530097aedd29ed850444b74bc2559730180
Hash of AAAAAAAAAAAB: 54314d9b51864195a0d4c7f2e0c3b1d623554a6c68d0a40593430b526e982b29
Hash of !#!#!#!#!#!#!#!#: b3ff1bf0a9f3411e4ce8b95cabbb352d34eb42a7640b67a366f2044daee68235
"""
x = 'Hello Everyone!'
print('Hash works?', hash(x) == hash(x)) # True
Expand All @@ -49,7 +52,6 @@ key = "key"
# Encrypt the text
encrypted_bitarray = encrypt(text, key)
print(encrypted_bitarray)
print(bitarray_to_text(encrypted_bitarray))

# Decrypt the bitarray
decrypted_bitarray = decrypt(encrypted_bitarray, key)
Expand Down Expand Up @@ -88,5 +90,17 @@ Generates a long and secure id.
These ids are garunteed to be unique; the chance of collision is about 1 / (2.42721841x10^229).
For reference, the number of atoms in the universe is estimated to be 10^78.

### `bitarray_to_text(data: bitarray) -> str`
Converts a bitarray to a string.

## PCSS.hash
### `hash(data: str, length: int = 256, block_size: int = 4) -> str`
### `hash(data: Union[str, int, bitarray, bytes], length: int = 256, block_size: int = 2, affected_area: int = 0.5, output: str = "hex") -> Union[bitarray, str, int, bytes]`
Hashes data using the hashing PCSS algorithm.
The length of the hash is supplied as the length arguemnt and it must be divisible by the block size (which defaults to 4).
An increased block size results in increased randomness.

`affected_area` is the number of input blocks used in the output block calculation. If it is an integer, it represents a concrete value, and if it is between 0 and 1 it is proportional to the number of blocks. This value is very particular, be careful!

Output can be "hex", "bitarray", "bytes", "base64", "base64-bytes", or "int".

##
2 changes: 1 addition & 1 deletion build/lib/PCSS/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

makeid = lambda: hashlib.sha256(
str(
random.randint(-999999999999999999999, 999999999999999999999 + time.time())
random.randint(-2**512, 2**512 + time.time())
).encode()).hexdigest() + hashlib.sha512(str(uuid1()).encode()).hexdigest(
) + str(uuid4()) + str(uuid5(uuid4(), str(uuid1())))

Expand Down
Loading

0 comments on commit 4bac856

Please sign in to comment.