PyAES: Pure Python AES Implementation
PyAES provides a pure-Python implementation of the Advanced Encryption Standard (AES) block cipher, along with common modes of operation like CBC, CTR, and OFB. Currently at version 1.6.1, it offers a lightweight cryptographic option for environments where C extensions are not feasible or desired. Its release cadence is low, with updates typically addressing bug fixes or minor enhancements.
Warnings
- gotcha AES key lengths must be 16, 24, or 32 bytes (for AES-128, AES-192, AES-256 respectively). Initialization Vector (IV) for CBC mode must be 16 bytes. Providing incorrect lengths will result in errors or insecure operation.
- breaking The stream API (e.g., `encrypt_stream`, `decrypt_stream`) was broken in version 1.5.0 and fixed in 1.6.0. Users upgrading from 1.5.0 to 1.6.0 will find their stream operations now function correctly, which might be a 'breaking change' if their code had workarounds for the 1.5.0 bug.
- gotcha PyAES uses simple zero-padding by default when input data is not a multiple of the AES block size (16 bytes). This can lead to security vulnerabilities (e.g., padding oracle attacks) or data loss if the original plaintext legitimately ends with null bytes, as zero-padding cannot distinguish between actual nulls and padding nulls.
- gotcha Reusing the same Initialization Vector (IV) with the same key for multiple encryptions in CBC mode significantly compromises confidentiality, making the encryption vulnerable to attacks. A unique, unpredictable IV is crucial for each encryption operation.
Install
-
pip install pyaes
Imports
- AESModeOfOperationCBC
from pyaes import AESModeOfOperationCBC
- AESModeOfOperationCTR
from pyaes import AESModeOfOperationCTR
- AES
from pyaes import AES
Quickstart
import pyaes
import os
# AES in CBC mode (with default zero-padding)
key = os.urandom(32) # 256-bit key
iv = os.urandom(16) # 128-bit Initialization Vector
aes_cbc_encrypt = pyaes.AESModeOfOperationCBC(key, iv=iv)
plaintext = b"This is some plaintext to encrypt. It will be zero-padded if not a multiple of 16 bytes."
ciphertext = aes_cbc_encrypt.encrypt(plaintext)
# Decrypt using a new AES object with the same key and IV
aes_cbc_decrypt = pyaes.AESModeOfOperationCBC(key, iv=iv)
decrypted_text = aes_cbc_decrypt.decrypt(ciphertext)
print(f"Original: {plaintext}")
print(f"Encrypted: {ciphertext.hex()}")
print(f"Decrypted: {decrypted_text}")
assert decrypted_text == plaintext