pyhcrypt

A simple and secure password-based encryption & decryption algorithm based on hash functions, implemented solely based on python.

Usage

Python

Main API

encrypt(input, password, use_rand=True)

Encrypt input (either bytes or an opened file object) with password (either bytes or str), return the encrypted result (bytes). If input is an opened file object, this function behaves as a generator and encrypts in a 64 bytes by 64 bytes manner to be memory friendly.

If use_rand is set to True (default), 64 random bytes will be encrypted before processing input, and the encryption result is also 64 bytes longer than input. This helps protect the password, especially for cases when the input starts with a specific pattern.

decrypt(input, passwd, use_rand=True)

Decrypt input (either bytes or an opened file object) with password (either bytes or str), return the decryption result (bytes). use_rand shall be consistent with the setting of encrypt.

Example

Encrypt and decrypt bytes.

from pyhcrypt import encrypt, decrypt
from os import urandom

bytes_ori = urandom(64)
passwd = "a_password"

bytes_enc = encrypt(bytes_ori, passwd)
bytes_dec = decrypt(bytes_enc, passwd)

print(bytes_ori == bytes_dec)

Encrypt or decrypt a file.

from pyhcrypt import encrypt, decrypt

def handle(cmd, passwd, srcf, rsf):

	func = decrypt if cmd.find("d") >= 0 else encrypt
	with open(srcf, "rb") as rf, open(rsf, "wb") as wf:
		for _ in func(rf, passwd):
			wf.write(_)

Command Line Interface

Execute either

python -m pyhcrypt action password input_file output_file

or

pyhcrypt action password input_file output_file

where action can be either e (for encryption) or d (for decryption), password is the password, input_file and output_file are the corresponding input and output file respectively.

Performance

Test with CPython 3.9.7, using a single Intel Core M3-7Y30 CPU thread. Even though python is slower than C, this library still achieves encryption/decryption speeds of 25.6 MB/s.

A puzzle

We encrypt a UTF-8 encoded message with the encrypt function for 16 individual times with the below code.

from gzip import compress
from pyhcrypt import encrypt

def puzzle(msg, n):

	gz_msg = compress(msg)
	for i in range(n):
		print(encrypt(gz_msg, msg, use_rand=True))

Following are the encryption results.

b"\xde]\xdf\x91+\xfc\xef\x99=\xc6\xbe\x19\xb0\x168\xc7i\xc9\xc5\xb1\x87\xdb\x9b'(\xfb\x13\xfdo\xb6{[A\xb8*\xd2\xb7\x05\x8b>7o\x0f\xd8>F\x9e\xbb`\xf1( \xbc\x9f\xd7fa\x98\x94\xeb\xb9\x84le9\xde\xf2\x1f#\x95\x82\x08\xa2^\x98\xd8\xce\x9a\xf0JJ\xe8 ]\x90\x96\x9d\x03(\x12\x92\x91\x04\x07\x8a\xd9Nk\xc2\x85\xe8G\x14Z\x82\xc8\xc3*k\x98y\xb8\xe6\xb9\x03iu4\x15\xad\xc0_c\tqm\x94,\xacmkk\xb5\x1c\xdaA\r\xde\x84\x9b\xd9\x19h\x96qFZc1\x7f\xc2w\x83\xdc<\xda\x9e,v\xe1\xfc\x1b\xc9Erd\xa1\xc6[\xd2\x07\x86\xa0\xe4H\r\x13\xec\xc4D\x8f["
b'\xb2\xbb\r\x93\x0f\x04+\x8f\xc2\xcf\x10\x7f\xcc1\xfc\x1b;\xfcj\xff9T\x17\xc9\x18J^cWo$4\x968m\x8d\x9b@\xf1\xeeE\xfe\x1f\x8f\xbc\x9fas*\\\xc1\xe33Y\xbb\x82\xe5\xae\x11\xaa)\xa2MM\xefXyX\x87\xe8\xcfj\x81\x8dAB\x08?\x08\xe0\x1e\xed:\xaewQ\x8d+&:\xd2\xcb\x1d9\xeb\xd6]\xe17\xc6`v\xac\x1c\xbe\xf5\x97\t\x9c\xd9C\xbc$1\xab\xdcy\x9a\x9b\xa8\xf6\xd2\xb9N\x8cl\x0f\xdc\xc9\x8e\xdd\xe9fR\xb7o2A\xe7rr\xde;\xe7_\x07\xe8\x14\xe3\x89\xed\xebQ\\\n8\t\xd7\xaf6k\x80\xc3\xa7 ,\xf9\n\x1c0a\x81.\n\x80\xb7\xa6\xbb\xd7\x90\x9d\xa3'
b']7\xb7|<\xbc\xa2\xaf\x9dvo\xaef\xeb2\x187\xdb\x05\xb8M\x89\xcd\xf1\x11\nA%\x9b]Il5w\x08\x8c\xc6\x13\xa0\xef\xb2/\xbb\x98|\x98@7P\xac\xf8\xe7\xf5wo\xeau#2\x12\n\xb5u\xa3\xcaT\x1f4k&\x96\x963"4\xb7cV\x19^\x83\xb2\xcdB\xf3\x04,s\xa8\xb7\xa8\'\x9f\xc4q\x93m\x97\x06\xdc\x93\x02\x07H\xcbPC^\x88\x06\xfe3\xa3A\xa8\xf0\x08Q2\xae\xd5\x0f+\x8bD\xb2vBn0\xbf\xbb\xf4\xc9\xcb\x07\xf2\xd4Q\x16y\x84&p\xa0\xbf\xc6\xc5o\xebg\t#\xbds0\xad\xb7\xb3\xa4\xf7\x85\x0e\xb2\xce m\x93<\x0017c\xb98\xd6b\xd2\xf4S\x1e\xaf'
b"\xf7\xf4\xb5\xb7\x7f\xe5E'\xb9\x87l\xf4\xe7\xca\xd3Mz\xf5\xea\x1a\x1f\xda\\\xaa\x157\xdeD\xfbTq\xbc\x16U|0\x9b\x0bG\xd1~\x08n\x07rd\xe0>\xbc\xdc\x1a\xc4'6\xb4q\x85j=R\x81\xfcd\x07\xb0\x19\xba\xc2S\xffk\x07\x86p\x9f\x85\x1a\x03=\x1bE\xb6\x8b+\xaf\xdd\x1d;U\xe6\x1a\xb7\xc1\x90\xb8\x97;Fl\xb3\x00\x8f\xe8\xeb`\xd8\r\xc5M\xf4\xcc\x95U\xce$:\x88\x9d\xe1\xa1V\xe89\x1b\x9c\x07\xfep\xdau\x1aA\x14\xba\xe7 9\x14\xfe\xae\x1f,{$\xa7\x1f\x8e,?tD{\r4\x18\xed\xef\xb6vl%\x04\xfb\xa0J\x14\x81\xcca\xcc)\xcfR\xe3{2\x85[\x89\x06\x82}"
b'3\x86\x96\xe1\x86R\xf2!\x19K\x03\xf1\x87*\xd6J\x1f\xc6<%\x89\x91\x84$j\x96\xae\x83\x99\xa8*2\xca\xb9\x19\xd8]\xfbD4i\x93\xa6\xb6\x86A\xc8Z\xdb<{R\x00\xcbE\x15\x14\xa2]\xd0Q\xd8\r\xdc\xc5\xaa\xad/&-\x08\xf9>\xa2d\x9e\xfb\x03F=^\x15\x86\xb9\xbc\x99\x1bG9\x9ag\xb3\xa6\xf6;\x90N\xee\xc2\xd1!\xad\xc4\xd1c\x91\xc7\t\\\xeao"g\x84\xbb\xa0G\x0eDV\xb5\x85\xeeKC+8gY\x8b]\xb1D\xbf\xa1\xe9\xcd\xbe\xa7\x17<#\xb0\x1c\x93\xdc\x8c\xa7\x9e\xbf\xb4}\xd6R\x83\xc9\xdd\x81:u\x16*\x82\x1c\xa2\xd2]Bm\xe4\xba\xf8#\x90\xda\xb2)U\x82\xd1\xean'
b',\x99\xda\xbe\t\tI\xa3\xbe\n\x8b\x7f\x9b\xb7\xb3+\xab\x82m(}\xe9g\xc6\xad\xc2\xbe=\x03Yu!@"\xef\xc7\xc3F!\xf4\x0b=\xb1t\x84\xfc\xac?N*\xbb\xb5\xfeTs\xea\xba\xc55F5\xd0V\xe7\x05\x80\x1e\xb8\x8f\xfa\xf0\xd7\r\xcc\x19\x8b>\x8f\x9f\xad\xbb\xe6I]M\x01\x1f\xc2\xb6\xf7#\x93\x1d&M\xa8\r;\x8e\xb6\xe9\r]\xd5\x12\xa4)\xda\xac6xS\x98\x08\xd6\xb7!\xec3N\xb3\x11\x96\xf9\x14\x99\xdeG\xb2\xe2\x00\xf2\r\x94\xda\x13F\xb8!\xde\x15\xbbs\x9e\xfao\xc9\xf5\rkF\x16\x8a\xd0`\xd33\xb15d\xd8M\x1bP\xbb\x80\x1b8\x91\x7f]>\xd3\xfe\'z6\xff\x9f\xa7S\xbd'
b"\xd8\x9a\x94\xa9\x0e\xf6\xae/\x0e\x85\xcd\x8f\x7f0.\xabI/\xd3\x1a\xf5\x8dyk\xd83-\x83\xd7\xea\xbfy\xecc\xa3\xad\x13\x0c\x81u\xc8\x08\xe3\x9e\xf6'}\xde-0\xc3u\xef\x92\xeay)-w\xd0\x06\xd9\x9d\xa7\xd3j\xa5\x8a\xe4\xc2.\xb4\xca>\xaa\xab\x99\x92S \x90\xc8\xfc\x11\xf3\xc3\x0b\xcd;\xb5\x8f\xd2g\xb2\x01g\xfa\xaa\xc8r\xe8\x1b\xe7+V\xb1\xbdU\xcb\xadI\x91\xc2\x0c\xc7M\xf2$\xbbds\xbd\x18F\xc5\xf9\xf9\xe7\xad\xed\xa4\x0f\x81TO\x84b\x7f\xe1kY\xf8a\xaf\x89\xa0\xfd*1\xab\xd3\xc3\x9e= ]\xa3 \xc8\x9e\xcb[o4\xa7\xb3\xe6@{\xbf\x84\x13\xb79\x80\xba%\xf5\xb4\x9d/p"
b'/\xd6\xf7h\xf8|\xa5\xe7\xe5\x1cx5\xa2\x94\xdd\xa9M\xb1R\xbeq\xf0\xe9VO\x08GEc4\xf4Q\xe8L\xf8W\xcf\x9e\x0b\xf9\r\xd0&\xcc-lo\x97\x9c1"~h\x85\x19@\xc2\x94\xa7^\xeb\xfa\x94\xc3\xce?\xbeu\xdf\x8a\xb04`\x06\x98\xc8\xef?\x1e\xf3?#\x08Cu\xd9\x01\xf90\x9b\x19gB\xae\x96s.%\xf9A\xe5yl\x83G8:\xa6\xb9\xecE\xf9g`\xe1\xc3\xf7|j\x99\xa0\xd8\xa6B\xf6x\xb8[\xda\xe5\xa0\xe4Su*\xf2\xadc\x1a\xa9d\xf9\xc3\xd7\x84\x15\xfe\xaf2\x7f=S\xb5\xe6\xed\xc3\r\xf4 \x8d{G~o;p\x16\xf6\x8a3\xc3z3`\xdf\x85\x19\x95\xf0\x18\x8e+'
b'\x12\xacm\xff?\x17\xc1\xbf\xfcCo\x1b\xf2~~\xf1\xf1\xd4\x9e:T\x04=R9\xadSV\xeb\x06\x90\xe8F\xcd\x82C\xef\xad\x01W\xfd!\xdc%\x1c\x82\x08>|\x17\xc4\xa6x\xa7\x99\xc0\xc1\x15bR\x00\xb3\xdd\xb8\xd7:\x1e\xf0\x13 \xfc"\x91\xe8\xcb\xc6\xc6\xdd\xdf[\xdb\xa7\xe4\xdf\xd9K\xae64-\x0c\xbcM\xa9f\xbe\xe5\xae\xd4\xd6\xa8\xc4\xcf\xeb\x0e\xfb,\xfd\x06\xaca\xa7\xae\xa4\x17\xda\xbb$-\xc4\xb6\xc9l|\x03G\xdd\xe1\xcd`w\x84\x88\xb6\x89^{Lw\xe3#\xfe\x83\xdf\x8c\xc81\xde\x1e\xffFDP\xd9\xd0.%/\xeaE\x81\x1e\xcdm\x8a\xac\xb8?w!\x17j\x9b\xbf/#\xe8\xdby7a\x19'
b'\xdd\xcc\xe0f<\xfd@\xbav\x90\x11\xd8.T#\x9d\xec#\xbeSh\xbbzE\x15\x18\x9d\xa1V\x80\x82\xb0\x84\xea\x04r@\x95\x94\x1c\xcd\x0b\r\x97\xe6\xb24\xfb:\x8eP\x87\xf5\x0c\xc6\n\xd7\xb7\xc9\x16\x81\x02\x84GV{\x1ci\xbe\x15\xeb\xc0\x00\xf3J\xe6\x96\xcbC\x84\xe1\x90\xb1\x0e\x92\xd1\x99y\tY\xdc\x1c\xa4%\xad\x17\xdb\xa7\xbb\x922\x8a\xe7\xf1\xfe_\xbf\xca\xc4P\x14v\x90\xe5\xd2\x7f\xb2To:``/\x0bVp<\x00h\x8f\x05\xa4\x82s4T\xd6#\x18\x91\x8a\xe1\xe9z\xdd-\xaa\xcd\x04\x16\xcb\x05x\xdbL\xc3\x97Ve\xe4\x83\x952%\xfc\xaa\x86`\xf4M\xa5\x97\xae\r&\x8e\xc9\x1ec|\xba.'
b'\x99k\x83\x9e\xbc\xe8\x08\x8cG\xf2o{\xa1\x19}\x88\xed\xb5HM\xe6\x80\xcb\xc4\xf0"$\x93>\xc7C\xdfvW\xf6\xf7\xc1\xa4\xfc\x91\xa5\xd5l\xd0@f\xf2\x86\x1b\')\x14\xcci\xc5\xb2\'\xb6\xda\xfe?\xbd\xb5\xba\x12,\xc7\x00\xee@\x96\x9bd\xb4\xe0\x01vKT\xbb\xb1\x9d\xc0\x0cv\x9a\\m\x93I\xd0\xd3\xc5\xe6Pm\xbf}\xd2\x9e\xbb\xb5\x9d"\x17\xb8c\x7f\xa1\xbe\t\xca`"O\x0c\x85\xf5I\xea>\x99\xe3\x11\xb4\xcf4\xf6\xbeK\x1f#M\xc3\x96Y\xc8\x0c\xd2\xf6\x85\xad\xbb\xd6y\x96\x92yy\xb7\xd4\xc2Qu\xeb\xfe\x1f\xbf\xd7\x1eG\xbd"\xef\xdfR?\xf0U\x0e"_\x98\xea\xf5cP\xa73\x97\xe5\xe6'
b'\x1a\xf2m"\x10f\'\x16hKL\x02\xe9\xf4P\xb3"c\x86\x98Y0hg4\x8b!\xe7\xed=\xae&R\xca\x1aU\x13\xb6\x96\xe0\x7f\xedJ=\xccy8\x9c\xe3\xc1\xc8\x8c\xf5]\x9f\x9a>\xe2\xe7\xd2\x94\x9c?\x93\x8d\x0f\xd2E]N\xbd\x15l8\x81\x0c\x9a\x8aA\xe8\xd7\xcah\x18+\xaao\x80X\xca\x1a\x0b3"\xdb\xbd\xdaZ\xbd\xe3\x8f\xfe\xd5\x93\xcd\x0e\xca\x08\xcc\x90\xd2\x1bAA\xa0\x12\xdd\xe4\x1e\x96\x15\x80y\x91X\xb7\xb8IH1o\xd4d\x99\x8b\x12\xfe\xb9\x04\xc8K>\xccCJ8I\xb6\x99\xe2V\xa2\xa1\xdb\xf4r\x9c\x88\xa4mP/\xe1`\x97\xe9\xe9V\x83Z\xbd\xa8\xd3*\xd4b-\x19\x8e\x0c\x9b]'
b'\xf6\xa8\xcdx9Q\xd5R\xc1\r\xd9=;\xc6\xf52l\xc4H~{\xe6\xd75\x8c\xb5u}\t\xc4\x96\xf1\xb8p\xa4] \xe2gE\xf1\x14r\x08\x9f\xdbxIJ\x96\x18g\x9f6Q\x9e/h\xd8.\xbd\xe6\xb4\xad\x90%\xcb\xae\x9b\x17\xef\xafI\xca\xc8\xd8\x99\xff:~\xdeiu\xc2\x86\xb4j\x19j\x8a\xe8\xaex\xd6x\x0c\xb41L\x14Q\x10\xf9n\xea\xb6\x1a"\xb8\xeaD\xb6\xfaj\x0f\x0c\xe3H\x84\xbc\x8b~jP\xd7j;\xe8\x8b\x8c\xac\x1aO\xa0\x89\xb9\x15V\x0c\x10n\xc2\xad\xeb\x15\xf5\xe8\x8c.\x1eXBXg\xcd\xa1\x0bT\xa9\xcc\x96qnk\xa9\xa8&\xb0#E\xc7\xe8\x81x\xdf}<\x82\x837j\xef'
b"\x8eyv)\xb1\x19\x04\x90\x1e\xb9\xa8ri\xe3\x83\x15\xba\xf0\x83-\x07\xc1$<\x80\x04\x90\xb0d.\x16\x91\x15\xdbsj\xf4H\xa2/\xd3\x85\xde\x02\xf3\xb8f\x06\xd9\x93B\x85\xfbl\xa6\xef\xa1\x19+G\\\xb3\\\x9aG\xd9p\xc6\xf0\xc7\x18\x10\xb3\xd3@mt\xc7\xbd\x1a\xf2\x05\xef\r>h\x112\xa6+\xfe\xef\xb4hKa\xe0\x1e\x08W\xda\xa4'\xe2[\xaer\xd1\xf2\xe9\x8b\xc9\xdf\xb0\xef\xe1A\xceyu\xa5<\xb7\x07\xd17\xc3s8\x03\xb3\xf3U\x19\x16\xfb\xbdrrNh{\xadB\xe9G\xaf8\x0c\x9a\xfc\xada\xef\x05\xce\xd9`,&\xedI\x89>0\xe0\xdf\x90=i\xe0\xfeD\x01\x89\xc3b#+\xa6\xda\x10"
b"\x05\x89k_\xfd\x89|\\Q\xeb\x98\xde\x80\xbek\xbf\x1f\xca\x0e\x04\xd9_\xf8L\x1dM[\x147\x0epb\xc2&\xd4\xf8$\xeea\xdd+_\xfca\x8f\xc3\xc4\x0e\xc2\xa8'\x15\xaf\xb1\xf9\xf4\xa59\x131\xc8\x9e;\x17[\xd6\x9e\x0f\x8c\x86\xb6\xb8?\xc6\xcf\xe6\x8c\xb6=\xd7t\x860\xf0\xe0\xe2J\x1cH\xc0\xa7\xbf_\xf3ae\xc8\xafJs\xb1\xe2\x9d\xf6\xae\x92\xf7\xfd\xa4\xadf\xa8\x8d%id\x14K\x81\xa3\xf2\n4T\xb7\xb9Hr\x04\xdd\x93U7'\xff\x89\xd5\x90?MS\x14L\\\xcb+\x92o:\xa8!Q;\xb7\xd6\x06\xbe\x8c\x14h\xc4\xddS\xfb6\x932\x93(\xe95&\x96\xddzU\x05\xa1\xc3+\x15\xd5"
b'=\x05\xc9\xf7@\x84\xb7\xae\x8d\xac^\xcbtm\x81+\xb7Yf\xb4uh\xffq\xd1+q\xb6Zb\x9a\x07\xddky\xd7\x85\xdf\x1d\xd1J\x9d\xdb\xa9\xa5qH"Z&[\xdc\x14Q~\xff\xe4;-\x17+\xdb\x1e\x1a\x80=F\xbfO\r\xbd\xc2x\xa5E0,8\x15\x1ee\xa5YS\x84\xeb\x84\x84\x91\xca\xde\xe34Qa\x1e\xf3\x1d\xd1+S\xfd6\xfcH\xdf\xa9\x88\xae\x1b\xa4AI\xadL\x99D\xe4v3;\xc7\xee\xfa\x1a\x1948\xb9(\xe3\xfa\xcd\':=\'\xe5Y\xec\x83\xde\xe2u6\x1aN\xd5\x82\xe3\x913Q1+\xc7\x98V\xa7\xd4G\xdd0\xc2\x0b\xbe\xb72;\xa3.c~T\x15\xaa\xa7\xdah\xe5w\xb6'

Can you figure out the original message?