In life, sometimes we need to encrypt some important files,Python provides easy-to-use cryptographic libraries such as hashlib, base64, etc.
But we can implement a simple file encryption program with the help of heterodyne operations
Basic knowledge
In Python, the iso-or operator is ^, or XOR, which means that the same value is 0 and a different value is 1. Specifically, there are four possibilities: 0 ^ 0 = 0, 0 ^ 1 = 1, 1 ^ 0 = 1, 1 ^ 1 = 0. We can also summarize the rule (where A is 0 or 1): 0 and A iso-or is A itself; 1 and A iso-or is A inverse.
Let us consider the properties satisfied by a binary number:
-
A binary number is a 0 to itself
b ^ b = 0
-
Heterogeneous operations satisfy the exchange law
a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c
-
The difference between 0 and a is a
(a ^ b) ^ b = a ^ (b ^ b) = a ^ 0 = a
Encryption Operations:
First convert the file into a binary number, then generate a random key of the same length as the binary number, and then perform an alias operation between the binary number and the key to obtain the encrypted binary number.
Decryption operations:
The original binary number is obtained by performing an alias operation between the encrypted binary program and the key, and finally the original binary number is restored to a text file.
Generate a random key:
The secrets library is a pseudo-random number module introduced in Python 3.6, suitable for generating random keys. token_bytes function accepts an int argument specifying the length of a random byte string. int.from_bytes converts the byte string to int, which is the binary number we need.
from secrets import token_bytes
def random_key(length):
key = token_bytes(nbytes=length)
key_int = int.from_bytes(key, 'big')
return key_int
Encryption unit:
The encrypt function takes a str object and returns a tuple (int, int). With the encode method we encode the string into a byte string. int.from_bytes converts the byte string into an int object. Finally, the encrypted text is obtained by performing an alias operation on the binary object and a random key.
def encrypt(raw):
raw_bytes = raw.encode()
raw_int = int.from_bytes(raw_bytes, 'big')
key_int = random_key(len(raw_bytes))
return raw_int ^ key_int, key_int
Decryption unit:
decrypt takes two int objects, the encrypted text and the random key. The decrypted.bit_length function gets the number of bits of the binary number and divides it by 8 to get the size of the bits. To prevent the binary numbers from 1 to 7 bits from dividing by 8 to get 0, add 7 and then divide by 8. Use the int.to_bytes function to convert the decrypted int object to bytes object. Finally, use the decode method to convert the bytes to strings.
def decrypt(encrypted, key_int):
decrypted = encrypted ^ key_int
length = (decrypted.bit_length() + 7) // 8
decrypted_bytes = int.to_bytes(decrypted, length, 'big')
return decrypted_bytes.decode()
Using the above functions, we can easily encrypt and decrypt text files.
>>> raw = 'demo'
>>> encrypted = encrypt(raw)
>>> encrypted
(217447100157746604585...,
9697901906831571319...)
>>> decrypt(*encrypted)
'demo'
Encrypt text files
path is the address of the file to be encrypted, if no key address is specified, then new directories and files will be created in that directory.
import json
from pathlib import Path
def encrypt_file(path, key_path=None, *, encoding='utf-8'):
path = Path(path)
cwd = path.cwd() / path.name.split('.')[0]
path_encrypted = cwd / path.name
if key_path is None:
key_path = cwd / 'key'
if not cwd.exists():
cwd.mkdir()
path_encrypted.touch()
key_path.touch()
with path.open('rt', encoding=encoding) as f1, \
path_encrypted.open('wt', encoding=encoding) as f2, \
key_path.open('wt', encoding=encoding) as f3:
encrypted, key = encrypt(f1.read())
json.dump(encrypted, f2)
json.dump(key, f3)
Decrypted files
def decrypt_file(path_encrypted, key_path=None, *, encoding='utf-8'):
path_encrypted = Path(path_encrypted)
cwd = path_encrypted.cwd()
path_decrypted = cwd / 'decrypted'
if not path_decrypted.exists():
path_decrypted.mkdir()
path_decrypted /= path_encrypted.name
path_decrypted.touch()
if key_path is None:
key_path = cwd / 'key'
with path_encrypted.open('rt', encoding=encoding) as f1, \
key_path.open('rt', encoding=encoding) as f2, \
path_decrypted.open('wt', encoding=encoding) as f3:
decrypted = decrypt(json.load(f1), json.load(f2))
f3.write(decrypted)
After executing the encryption and decryption operations, the decrypted file will be the same as the original file, as shown in the following diagram: