# pyhcrypt

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

## Usage

Install with `pip install pyhcrypt`.

### 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. `-` for standard input/output.

## Performance

Test with CPython 3.10.4, using a single Intel Core M3-7Y30 CPU thread. Even though python is slower than C, this library still achieves encryption/decryption speeds of 31.3 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'\xdaz>\x9b\xf2s\x84\x08\xc5O\xa7\xe28{\xbe|\xc4\xd77\xac\x98\x96\xb7\xb9\xf8\xcc@\x94\x1b\x06\xbb\xae\x84\xbc\x9e\xcaN\xd6\xc4\xc9\xca\x07 \xa2\xca4\xa4\xf82\xa4*q\x114\xbcW\xe6\x10\xd6\xf8/H\xc0t\xfb\x1d3\xa2\x9d\xa9H\n\xbf\xf8\x90{\x0f\xf3/b|\xe9\x9b\x95\xbd\xb2\x11Ri$)7\xb4\xd8\xdc\x0e+\x9fL<\x1cH\xb4\x01;\x03\xfb{\x1bj\x00gC\xd3\x91\xb4\xd4M\x85\xc72P\x9aI\x86\tlV\xd7\x0e["\xea\xef\xfb\xe5\x8fl 8s\xbc\xf5\xdc?k9\xdd\x96\xe7\xa9QE,6\xfa\x1d\x06\\g(\xa7\x1ew\xddv\xa06\xb6\xf3\xf3\xf2\xbb\x9a@\x0c\x0c\xe9\xaf\xb1;\xed'
b'\xb9C\xca""\x15\x94\xbb\x80\xb0\xc0\xb3\x04h\x1au\xbe?\x16)\x883;\xb8\xa8h\xf2u\xc4\x8a\xd6\x94@\n\xd0Z\xedPu\x045\x87\x91\x9eO\x8c\xb7\xf3O9\xdcW\xeay\x84d\x91v\xeb\x99\xea\x10f\xcd\xc7 k\x91\x8a\xaf)\xe6\xb5\xdd\xf8r76\x9fb\x1d\x8c\x0c\xf6\xc7\x19p\xa1i\x03NA=\xd2&\xff>1a\xf6\x18Wx>p\xf4\xf06=L\xadg\xb5\xfe\x08T,c6]\xf0\x8er_-\x92\x0e\x964\xa1\xf8b\xee\x04\xe4\xf6\xafHM\xf5(-L{K\xa0E\xd9\x87EN\xf3,e\x05\x82E\xe2\xd7p\x08\xabS;U\xfa\xb0\xdd9\x0c\x86D\xb3\xec_\xec\xe1\x15pj.\xe9'
b"x\x8e\x9f\x91\\\x0b\xf2\xc4G,\xe4\x073\x8e\xa3 _\xf0\x87W\xb7R\x19\xe6f\xb9d\xf2\xac\xe4N\\&\x02\x8e$\x1dZ@\xcf\xcb\xe7\xccd\xe8\xcb\x88\x10\xd5v\x08\xa2/\xd2\xf5y\x18_Sz)\xdc>^NM\x92'd\x1c\xb8\xa3|\x00e\xa5\x1fe\xf5\xef,!\xaf2+\xc1\xfcL\xd7_\xd2/\x93\xa3@3|\x0c\xa1\xec\xde\xae\x18\xefY\xabcF\xb9z4 \xba\xf3\x10/\x8f\x85\x04\x18*\x9ay\xb0\xa4\x1e\x96P~[\xc6\xd7\x864\xbf\xdf\xbb\xd4g\x88w\xcd\xf6\x9b\xa4c\x98*E\x9a@\x11\x84p\x1f\x9f\xfb<^g7v\x9c\xd96\x0e\xf6J\x01\x93Ou\xb8\t\x15\xcd\xe9\xed<\x99y\xdc"
b'\x9f\x14\xebRn\xdb\xfe\x8b@(\xdf\x1e&\xada\x80\x91\xba\xa3+\xb7g\x8b!\xe0$\xe3\xd3\xa2\x12c6\x04h\xd6\x03\x83\x89\xb0\xf5;(\xddz\xe5\xfeR\xaf,\xb40\xc8\\TX!(\xd9\x92\xc0\xa0\x9dW\'3\x19n-;\x1c\xdb\xee\x0e\xe3\xb0\x91L9\xbd\x1e\xef\xe9\xb3\xc5\xb6+\xf1\xd26&\xceK\xaa\xa4l\x9e^\xb3!\x0f\x91N,\xb0\x11b"\x85\xc0\xd3\x136xU\x8c\'X\xc5+\xef\xb0~\xc0\xc1\xa3\xe1g/\xb7q\x99M?\x9d\xe0p\xfe\x85\x13\x9e4\x84\xca-\x81\xa4\xd9^\x1a\xfb\xa1\xaa\\\x05z\xc9\x0b\x98\xe1\xfdi\x88\x8fu\xef\xea\xf5\xef\x8f\xf7\xf5bf\x9f\x14\x98r\x8f\xe0\xa4P\xe8'
b"vA\x8f\xf9m8\xa8;\x8bn\xaa\x03\xd6h\xe2\xc5\xd2\xf7\x8dt)J\x80\x1bq\xc2GC\xdfa\xdf\x81\xea\xc5'\xdfU\xf7i\x1f:-b \xc6\x1d\xf5X\x9a\xe7\xa1\xf0\xb8\x10\xd0\xe8e\xda\x9aJ[\x01x\x06\xd3F\xb5\n\xca\xf7\x85uV\xb1*(\xf1\x04B\x14\x0c\xa5\xe3\x1e\x0ek\xbf\xee\x03K\xf7Z\x93\x84\xa5\x05\xd0\xd89\x8e\x18K\x01X\x8b\x8f\xf3f\xad\xb6\xaf\xf9O\x7f\x91\x8f@\xd6!\xd2&J\x01\x00h\xe7\xe7\xf2\x8dS\xa6|\x01_s5X\xb5\xa0\t\xfa<\xb4\xf9\xac\xa0\x8f?\x97\xa2g\xca\x91\xectDt\x88V\xa8\xa6r+\x7f\xb1\xb7\x95\xb5\x82\xf4\xa2\xe2\xa6\x1e\xdf7\xed\x15\xc2\xca;\x87"
b"\xc1\xa7EV\xfd\xfa\xb2z)T\x9f\xa2\x90\xf0gv\x1a\xd0\x0f\x96Uu\xd1b;\xfc\xe7\xd3dlu\xb0\x90\xab\xfd\xb5$\x0f\xe0\x80vK\x95\xb0\x80\x9a\xf5\xd2\xfc\xf6\xf5!7/7\x1bp[C-?\x92O\x00\x120\xe4BHH\xe3R\xe5\x9a\xe9\xfe\x18\xd0\n\x0e.\x14)'\xe5f\xaa\xa6\xb6\x08\xc6B6\xfb\x82?\x9d\xda\xa2\xf0\x0b\xfb\xb7{\xf5d\xb4\xd7\x91\xfc\x9dF\x1f\xaa\xd3t\xb3\x8c5\x93o\x9e\xad\xd3}@\xb7\xd1\xcc\xc9?\xda\xc5bN\xe81o\x06\xf4\x98'\x1a\xe4\x13\x19eWS.Z\xea\xe3\xeaQ\xf2-6\x1a\x00\xb2\x19\x03\xcc\xac\xa6\xca?\xd7\xa7\xce-U\x9c\xa4% w\xbd\xabi\x86"
b"\xfd\x1a?\xd0\x8d\xfb\xa0{h\x85{3\rK\x85\xaa'\xfe&\x9c\xe0p\x94\x15\xa6\xdc}\x9d\x18\x08i\x84s\xf8\x0c{\xe3\xd1\xdb=\x11g\x8e\x95\xa1\xe2,p\xf3Q\xc6\xfd\xae\xb5\x90&3\xcf\xe8\xb3\x04\xe8z\xd5\x91\xfb9\rTj\xb0\xf5 A\xee\xf6\x0b\x92\x9f\xb3\xbed\xaa\x08e\xa9?\xe6\xfe\xc6\xa5\xda\xa3j\xde\xc7q\xbc\xaa\x00\x95\xbc\x11\xb6C\xe0\xfeg\xd7'\xe6\xaa\xa7\xa2\x9a\xdf\xc5\xf0\xe2Q\xb3\x89\x0fn\x98?\r\x84\x8eo\xe4\x1ek\xb0\xee\x04\xa4\xb6Z\x7f\x9f\xefi\x15c\x93\x1cvv\xfc)\xed*\xa5\x0fN65\xccO\x15wP'\x8a\xbb(\xdb\x15t\x03\x17\xbc\x9aT\x93\xd8\x05\xec\x82\xa6\x07"
b'\xe1\x9a\x7f\xcd\xec$\xaf\xf1\x8dE?bB\xc7\xfb\x80\xfa\x8a\x15\x18\xfc`\xa0C\x1e\xdd\xab\x19wL\x1046p~\x9b\xd1f\xde\xea\x01n\'\xfc=\xaa\xbdJ\xa2<y\xfcyU\xd2\x8e\xc5\x19\xe2;\x84\xd2\xad\xcc<\x96\xec:\xc7\xf0\x0cA\xa9\xc7\x1fM\x15\xeb\xb6\x92\x10\x98Fz\x8fi~\xb1\xcb\x9f\x1d\xd5R\x8f\xdela\xe1G5\xd24\xe7\xfa\xb9\xef B\xd5\xeen\xba\xe5\xe7E\xe5\x9b#\xd0\x18\xac\xf1\xbc"\x98\xff\xfaL\x8elG\xf3V\x1b\xd4\xbbu\xc2\xd7\x01V\xa1\x0e/C\xfaH\xad\x1cS\x10a\xff\xaa\x06\xe1=d!\xdc\x82\n9\xf2\xd0\x03\xb6\xfb]\xa3\xf2\xec\x9e8\x89U\x9fWr\xff\x87\xf6'
b'\x03\xf5j\x0fU\xfc\x83r\xce\x10\xf2.\x9f\x8f\\\xa6\xf4\x02\x00,\x04)\xdcR\x1d\xb95\xbe\x03\x144+\xb0\xa1\xa9\xd0\x10\xa9{\xad\x01)/\xd8f\xc5\xc3GZ\x0c\xb0D\xae\xfb\xbc\x1b0"h\xe6\xd1\xc1\x8a+\x99/\xdbY\xe7S\xf2\xc7"s\xec\x8d\xb6\xc9\x9b\x0f\xa31b\xe8\xc5\xef\x07\x12v\xf6[\xb44q\xe0j\x1d\xb0\x8a\x9dn\xeb:\xcb|\xfe\xd3\x8a\x00\xb7;\xdd\xe4\xd7\xaf\x9b\xdc\x95\xf1\x8bHx\xfe\xd3@\xbe\x92\x18\x0c[\x94\xea\'\xaf:\xe9\xcf\xd2\x0c@\xe4qH\x97\x07\x05D\xc7k\x07`\xe8\th\xcd\x02c\x1cG\xd8N\x8fL\xc26>s+\xfe\x03\x8b\xa8$ttu\xe3\xe2\x82\xb7\x8b\x08'
b'=\x8bq\x83>\xa9k\xc8\xc1\xc4\x1c\xc5\x9cs\x9b\xe8\xcdM\xa9v\xa5$q\x8fR\xe9\x12<\xb9e{\x1a\xe7J*\xa0\xc7@>\xc9\xaeg^m\xf2\xdd.\xae\xfa\x87\x80\x8a\x96\xd3?\x9f\x0b\x9f\xd3R\xb1\xf4@\x1f\xbc\x80\x15\xf9\xa2\x05\xc5]\xa4M\xbc\\\xd1\x85\x8c\xc6\xd2EVXW?s\x80_\x04\x89\x01\xa9\x8d\xbdJ_\x88Z\xe3\xe4\xd1pfC=\r\x84|\xfa\\(\xf3-\xe5\x9f9\xf9(R\xa6p\x90_ \x9e\xce\x1c\x97\xe1\xea\xb3/\x99\xb5X\xeeYN\\\xb7\x99H\r\x05\x0cS\xa3l\x9a\\:F\xe2\xe3\x07\xc5H\x1f\xd167pd\xe2\x9b\x85\xd1\xca|O\xe0\x0e\x8eV\x04O\x87\xd1\xd9V\x08'
b'\xba3B\x95\x07O\x82Z-\x7f\xfd}\xa0G\xd5N&\x90\xf0z\xdc\x8aW\xc5S\x82\xe9\xe1\x95\xfa3H=D\xf1\x0f\x17\x10t\x1dr\x08B\xf7.\x89\xac\xa5\\\xa9\xb5dR<\\3W\x96,6d.\x84\xbbT\x89l\xdf\x96\x04\xebc\xe4\xaa\x1a\x80W\xf1\xed\xfc\x19\x03`\x9b\xb5,6[\x96s;\xf2k\xe5\xbe\x047\x90l\x80\xa4\xc3.\x0b\xdci\x89\xad\xf0\xe3\x1bf\xa9\xcdI*< \x88\x12U\xb0N\x91+2\xe6Q\xffD\xe4&\xaf7\xb3\x06\xacd\xd8\xcaW\x08\xa7z\x8b3\x18\x9c\x81ieO\xb5\xeb\xfe\x7f\xee\xb9\xbei\xa9g\xe3\xdca\xa4*\xeb\xc3\x91\x14\xadCW\xa2\xb1l\xd8\xa8\x13\xc8\xfe'
b'R\xa6\xbd\x85\x82q4\xaau@X\xed;\x12\x0cQP\x0f\xcb\xf6P\x1bt\xb5)\x03p"Vh\xf5\xfe\x0c\xa3\x981\xcf\x8e\\1\xaeG\xf0P\xb9\xd6\xf5\xad$!\x10\xf1\xe0\x9d\xa7\xc1\xf0\xb3\xcdD\xf6\xc6sz\xc2\xa5i\x976.F\xf7?2\xd0n\xab>F\xa7f\xd1a\x84\xee\x8c9\x00@\xea\xdb\xe6\xc4w\x86\x15\xc0B\xd1\xc2\xe2\x0c7\xa0\xc7\xdb\xaeZ\xdbL\xeb<\xb1T\x97n\xd9\x89B\x12tf\x85\x08`*n\x12l\xea\xaesY\xa8\xee\xf9`\xca\xd9\xac\xb4\x11\xda\x8c\xb4\xad\xfd\x06:^\xf6^\xb9\xffY\x9b\xba\x9f\xb0M\xff\x07y\xc9\xaa\x85 \x1d^\x1eU,\x075#\xa9\xf9#4\x07Qe'
b'\xff\x1e\xd4!\xa9@\x04\xff\x1a\xa0\xacU\xaf\xbe\xa0T\xa2\xad\xfd\xa9\xb9\xe9hix\xc7\x18\x98E\xe9\xccD\xc1a\x05\xa2\xf8\x1e/\xa1\xb36EN\xe2\x15dN\x05\xb2\xe7\x84C\xaa\xa2\xd0\xec\xb4\xee6\x0eg\xfa|\xab}\xda\xcd\xfc\x9e\xd1\xf9V:e\xe8\xf6(\x95\xf4N?\n\xc3\xcd\x08-a\xb1\x11\x15K\xde\xc6h\xd7\x82\xa5\x9e\x86|lrL\xf3\xe6\xef\xd2\xeaR\x813S\xe3a8\x07K\x02;\x8c\x18:\xe3\xc9\xd6\x12\xe4\xc30\xa8E\x02O(J7\t\x816o\x8d\x00\xc5\xf1b\xc7iZ\x17\xbd~s\xb7}\xa6\x0e>o+j \xbe-I\x82\x89\x01\xe0\x88\x0b\xbe\xca\x7f\x07\x13VtDM\xf4\xcf'
b'\xc2Z\xc6o\x9dS_\xd1F\x1f*\xd5\x94 &\xd6_\x15\xa1\x855\x85\x98\x0c&R\xc4\nAL\xed\xb2|\xaa\xefH\xd7%\x90_\x99Cg\xbcc\xc8J/tJ\x96P\xf2y\xf3\xe82\xe3kl)h\xaf\xcf\xd6\xe0\x85\x0b~|\xac\xc8\xfb\xd1\x9a\xb1\\\x94\xc8\x81\n\x909\xe2,\xc6\xeaH\x9a\xac#\xc18"@\xf9\x10\xe7\tQ\x96\xf2#K\xa7\x86-\xfe\xa2\x94\x81\xf4+\xb4\xdd\x1e\xbb\x8c\x1d\x04\x81\xe6E\xda-\xac)\x9en\xdc\xff\x0e$\xf6E|#\x13\x175\x1fg\x9b\xda\xcd\xf1{\xda1G\x13\xf6>R\x1b\xd9<"U\xbe\xdf\x17\xdd\xbd\x88\xc4\rF\x95\xffJ\x82\xb36\xa8\x8bl\xfft\x84\xbc\x8a'
b"\xb2\x1cH\xd3O\x81\x06\xf3\xaf\x16'=[\xc4\x00d>\x80\xa7\xc12\xfc$\xf8-\x98\x17\xe9\xb83\x99%\xa2\xb6\x9260c?\xf0\xc4=\xb4\xa9\xc4jJ\x8e\xf3\x08\xd1\xc3\xdcP#\xc7\x08\xad\x8d\xdb[j\x8f\x9a<}\xae\xb6\xfe\x07\xc2\xbc\x0b:\x98\xae\x1b,\xb2\x9a6\xd1\x842_,^,D\x0bE&\xd3\xee\xae\xe9Os\xc5\x8b0c\xad\xef\x12\xd0\x8a\xcb\xcb\xf0\xffA\x1f\xcb\x80F\xb1\xd9\x94\x9e\xe9\x92,N\xcd'\xc4Y\x9f\x94\x1c\r\xe20\xdcS\x9d\\\xc1\x0b\x8c{l3\x18wM\xe6\x03\x8d\xf8\x86\xc4\x07;e!\x1e\x13\x92\x13A\xe7\xb9Ia\xef\x0e\xa6~\x86\xb4\xa6\xa1\x96\xa3fKm6W\xf3"
b's`X\x1ct\x17\x04\x8b\xd3\xd1\x89{\x98o\xe4x\xab\xa6\x9a3G\x06\xa3\xcc\x87Sc\xb3W\x0f\xd3g\xd6\xc4\xbc\xa5;\xae\x0e1s\xe9\xc5\xe8\xd9\xa88\xfd\x11\xe9\\TEW]\xfe9N9\xde\x96\x0eI\xe8\x0b\xaa\x0b\xdb\xe6\xa8w\xe1\x1dctI;H_-\xc0I\xbc\x89z\x80\x19\x80F\xec\xb1\xc47A\xdf\xf5\xc9\xb0\xcc\xc2a"5\x1e]\x16wS\x8f\x83gC\x87\xd6+&\xc4\x8eQ\x87!8\xda^\xe9\x95zO\xde\xc4\xad\xbe$\x18\x1b\xfb\xf7_\x82\xe7.\x9eP!@AG\x1c\xb6\xef6v\xb6\x0f\x7fi\xa9\x01\xfeW,3\x8a]y!\xa9&\x0f\xca\xe9l\x14==;0w7\xf6\x90\x98'
```

Can you figure out the original message?
