# ============================================================+ # 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')