Sometimes the lesser-known libraries are the ones that solve the problem with the least friction.
Why look for something else?
While working on a Python script, I ran into a very ordinary need: I just wanted a simple way to encrypt a string.
The usual search results mostly point to libraries like pycryptodome or cryptography. I had used that kind of stack before in a demo project for encrypted transmission, so it was not unfamiliar. Still, every time I look at those libraries, they feel heavier than what I actually need. It is not that they are impossibly complicated, but they expose too many choices at once: AES, RSA, various modes, extra parameters, IV handling, and so on.
A lot of the time, I do not want to stop and think about which algorithm makes sense for a tiny utility script. I do not want to weigh modes or deal with a pile of cryptographic options just to encrypt some content and later decrypt it again. I just want a straightforward tool.
Stumbling onto tinyaes
That is what reminded me of something I had seen before. When using PyInstaller with its key-related option, it asks for a library called tinyaes. I searched for it and found almost no useful writeups online, so the only real option was to go straight to the GitHub repository.
The repository itself was not especially helpful at first glance. There were only a few lines of explanation and no example showing how to use it. From there I learned that it is basically a thin Python wrapper around a C library called tiny-AES-c. But the C project was not exactly overflowing with usage examples either.
What finally made it click was the test script in the tinyaes repository, plus one line from the C library documentation: “Same function for encrypting as for decrypting in CTR mode”. That was the key detail.
So tinyaes uses a single method for both encryption and decryption. That is why the method is called xcrypt: like XOR-style processing, you run the same operation in either direction. Internally it uses AES-128-CTR, which also means I do not have to spend time deciding which algorithm to pick. For my use case, that is exactly the appeal: one clear method, very little ceremony, and no extra decision-making.
It is a shame the library is not more widely known, because it fits this kind of task very well.
The one-line usage
Using tinyaes is about as simple as it gets. Encryption and decryption use exactly the same code:
__import__("tinyaes").AES(b"十六位的密码", b"十六位的向量,可以不要").CTR_xcrypt_buffer(b"需要加密/解密的内容")
That is pleasantly direct. The one obvious limitation is that the key must be 16 bytes long.
If you want to accept passwords of arbitrary length, an easy workaround is to hash them first. Since an MD5 digest is 16 bytes, it can be used as the AES key material here.
Turning it into a tiny file encryptor
Once the interface is that simple, writing a file encryption script becomes almost trivial. I estimated it would take fewer than ten lines, and it did. With a larger crypto library, I doubt I would get something this compact.
Here is the script:
import hashlib, tinyaes, sys
if not len(sys.argv) == 3:
exit(f"Usage: {sys.argv[0]} [filepath] [key]")
key = tinyaes.AES(hashlib.md5(sys.argv[2].encode()).digest())
with open(sys.argv[1], 'rb') as orig:
with open(sys.argv[1] + ".xc", 'wb') as enc:
for byte_block in iter(lambda: orig.read(4096), b''):
enc.write(key.CTR_xcrypt_buffer(byte_block))
The usage is straightforward. Suppose the script is named encrypt_file.py. To encrypt a file, run:
python3 encrypt_file.py 要加密的文件.txt 密码
After it finishes, it will create a new file with the .xc suffix.
Decrypting works the same way, because the operation is symmetric:
python3 encrypt_file.py 要加密的文件.txt.xc 密码
There is one small inconvenience: the script does not try to detect whether a file is already encrypted, so running it again simply adds another .xc suffix. If you are decrypting, you will have to remove that extra suffix manually afterward.
Another interesting detail is what happens with the wrong password. Since the same transformation is being applied again with a different key, the result is effectively just another layer of encryption. To get back to the original content, you would need to reverse the process in the correct order.
Why this feels more natural
What I like about this approach is how little mental overhead it creates. It feels much more in line with the kind of direct, practical scripting I want from Python.
By comparison, using pycryptodome or cryptography for something this small can feel bulky, almost like writing a Java-style program full of setup and configuration. Those libraries absolutely have their place, but when the goal is simply “encrypt this data with minimal fuss,” a tiny wrapper with one obvious method is much easier to appreciate.