9779baa468
Move all application source under src/pygui/ and rewire imports, build config, and QML module path to match. - Relocate backend/, serialcomm/, and the ISC QML module into src/pygui/; convert main.py into pygui/__main__.py with a main() entry point (run via `python -m pygui` or the new `isc` script) - Rewrite absolute imports: backend.* -> pygui.backend.*, serialcomm.* -> pygui.serialcomm.* (source + tests) - Move app icons (isc.ico/icns) into packaging/ - Update README and ISC.qmlproject to the new paths
91 lines
3.1 KiB
Python
91 lines
3.1 KiB
Python
import secrets
|
|
from pathlib import Path
|
|
from typing import Union
|
|
|
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
from cryptography.hazmat.backends import default_backend
|
|
|
|
|
|
# ===== Crypto Utilities =====
|
|
class CryptoHelper:
|
|
"""Cryptographic utilities for wafer data encryption/decryption."""
|
|
|
|
# ===== Hex/Byte Conversion =====
|
|
@staticmethod
|
|
def hex_string_to_bytearray(hex_str: str) -> bytes:
|
|
"""Converts a hex string to bytes (e.g., '48656C6C6F' → b'Hello')."""
|
|
if len(hex_str) % 2 != 0:
|
|
raise ValueError("Hex string must have even length")
|
|
try:
|
|
return bytes.fromhex(hex_str)
|
|
except ValueError as e:
|
|
raise ValueError(f"Invalid hex string: {e}")
|
|
|
|
@staticmethod
|
|
def bytearray_to_hex_string(data: Union[bytes, bytearray]) -> str:
|
|
"""Converts bytes to lowercase hex string."""
|
|
return data.hex()
|
|
|
|
# ===== Random Data =====
|
|
@staticmethod
|
|
def generate_random_bytes(length: int) -> bytes:
|
|
"""Generates cryptographically secure random bytes."""
|
|
if length < 0:
|
|
raise ValueError("Length must be non-negative")
|
|
return secrets.token_bytes(length)
|
|
|
|
# ===== AES Decryption =====
|
|
@staticmethod
|
|
def decrypt_aes(data: bytes, key: bytes, iv: bytes) -> bytes:
|
|
"""
|
|
Decrypts AES-128-CBC data with PKCS7 padding.
|
|
|
|
Args:
|
|
data: Encrypted ciphertext (must be multiple of 16 bytes)
|
|
key: 16-byte AES key
|
|
iv: 16-byte IV
|
|
|
|
Returns:
|
|
Decrypted plaintext
|
|
|
|
Raises:
|
|
ValueError: If inputs are invalid
|
|
cryptography.exceptions.InvalidSignature: On decryption failure
|
|
"""
|
|
if len(key) != 16:
|
|
raise ValueError("Key must be 16 bytes (AES-128)")
|
|
if len(iv) != 16:
|
|
raise ValueError("IV must be 16 bytes")
|
|
if len(data) == 0:
|
|
return b""
|
|
if len(data) % 16 != 0:
|
|
raise ValueError("Ciphertext length must be multiple of 16 bytes")
|
|
|
|
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
|
|
decrypt_ctx = cipher.decryptor()
|
|
plaintext_padded = decrypt_ctx.update(data) + decrypt_ctx.finalize()
|
|
|
|
# Remove PKCS7 padding
|
|
pad_len = plaintext_padded[-1]
|
|
if pad_len > 16 or pad_len == 0:
|
|
raise ValueError("Invalid padding")
|
|
return plaintext_padded[:-pad_len]
|
|
|
|
# ===== Binary File I/O =====
|
|
@staticmethod
|
|
def save_to_binary_file(file_path: Union[str, Path], data: bytes) -> None:
|
|
"""Writes bytes to a binary file."""
|
|
Path(file_path).write_bytes(data)
|
|
|
|
@staticmethod
|
|
def read_from_binary_file(file_path: Union[str, Path]) -> bytes:
|
|
"""Reads bytes from a binary file."""
|
|
return Path(file_path).read_bytes()
|
|
|
|
# ===== Convenience Helpers =====
|
|
@classmethod
|
|
def read_hex_string_from_binary_file(cls, file_path: Union[str, Path]) -> str:
|
|
"""Reads binary file and returns hex string."""
|
|
data = cls.read_from_binary_file(file_path)
|
|
return cls.bytearray_to_hex_string(data)
|