64 lines
2.0 KiB
Python
64 lines
2.0 KiB
Python
# ============================================================+
|
|
# Identity IoT — AES-CBC ITEMS Envelope
|
|
# (c) Copyright : ae Aeonian Engineering Limited - Hong Kong
|
|
# (c) Copyright : WIDE di D. Papa - Naples - Italy
|
|
# ============================================================+
|
|
# AES-CBC encryption/decryption compatible with PHP ITEMSEncrypter.
|
|
# Uses ucryptolib (built-in on Pico W / ESP32).
|
|
# ============================================================+
|
|
|
|
import ucryptolib
|
|
import ubinascii
|
|
|
|
def _pkcs7_pad(data: bytes, block_size: int = 16) -> bytes:
|
|
pad_len = block_size - (len(data) % block_size)
|
|
return data + bytes([pad_len] * pad_len)
|
|
|
|
|
|
def _pkcs7_unpad(data: bytes) -> bytes:
|
|
pad_len = data[-1]
|
|
return data[:-pad_len]
|
|
|
|
|
|
def aes_cbc_encrypt(plaintext: str, key: bytes, iv: bytes) -> str:
|
|
"""
|
|
Encrypt plaintext string with AES-CBC.
|
|
Returns base64-encoded ciphertext.
|
|
Compatible with PHP ITEMSEncrypter::encryptAES().
|
|
"""
|
|
if isinstance(key, str):
|
|
key = key.encode()
|
|
if isinstance(iv, str):
|
|
iv = iv.encode()
|
|
if isinstance(plaintext, str):
|
|
plaintext = plaintext.encode('utf-8')
|
|
|
|
padded = _pkcs7_pad(plaintext)
|
|
cipher = ucryptolib.aes(key, 2, iv) # mode 2 = CBC
|
|
encrypted = cipher.encrypt(padded)
|
|
return ubinascii.b2a_base64(encrypted).decode().strip()
|
|
|
|
|
|
def aes_cbc_decrypt(ciphertext_b64: str, key: bytes, iv: bytes) -> str:
|
|
"""
|
|
Decrypt base64-encoded AES-CBC ciphertext.
|
|
Returns plaintext string.
|
|
Compatible with PHP ITEMSEncrypter::decryptAES().
|
|
"""
|
|
if isinstance(key, str):
|
|
key = key.encode()
|
|
if isinstance(iv, str):
|
|
iv = iv.encode()
|
|
|
|
# Normalize base64
|
|
s = ciphertext_b64.strip().replace('\n', '').replace('\r', '')
|
|
s = s.replace('-', '+').replace('_', '/')
|
|
pad = len(s) % 4
|
|
if pad:
|
|
s += '=' * (4 - pad)
|
|
|
|
encrypted = ubinascii.a2b_base64(s)
|
|
cipher = ucryptolib.aes(key, 2, iv) # mode 2 = CBC
|
|
decrypted = cipher.decrypt(encrypted)
|
|
return _pkcs7_unpad(decrypted).decode('utf-8')
|