Files
twofauth-rs/tests/crypto_interop.py
T
wt cf25e28c28
Rust verification / test (push) Successful in 4m10s
initial commit
2026-10-28 15:59:54 +07:00

70 lines
3.2 KiB
Python

#!/usr/bin/env python3
"""Generate independent Laravel CBC envelopes and optionally verify using PHP OpenSSL.
Run: python tests/crypto_interop.py [--check-php]
Requires Python cryptography. The optional PHP check requires PHP's openssl extension.
Fixtures exercise Laravel's encryptString envelope, not the entire Laravel framework.
"""
import argparse
import base64
import hashlib
import hmac
import json
from pathlib import Path
import shutil
import subprocess
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.padding import PKCS7
def generate():
fixtures = []
for key in (b"0123456789abcdef", b"0123456789abcdef0123456789abcdef"):
for plaintext in ("", "Laravel encrypted field", "Unicode 🔐 café"):
iv = bytes(range(16))
padder = PKCS7(128).padder()
padded = padder.update(plaintext.encode()) + padder.finalize()
encryptor = Cipher(algorithms.AES(key), modes.CBC(iv)).encryptor()
value = base64.b64encode(encryptor.update(padded) + encryptor.finalize()).decode()
encoded_iv = base64.b64encode(iv).decode()
mac = hmac.new(key, (encoded_iv + value).encode(), hashlib.sha256).hexdigest()
payload = dict(iv=encoded_iv, value=value, mac=mac, tag="")
envelope = base64.b64encode(json.dumps(payload, separators=(",", ":")).encode()).decode()
fixtures.append(dict(cipher=f"AES-{len(key)*8}-CBC", app_key="base64:" + base64.b64encode(key).decode(), plaintext=plaintext, envelope=envelope))
return fixtures
def check_php(fixtures):
php = shutil.which("php")
if not php:
print("SKIP PHP OpenSSL: php executable unavailable")
return
code = r'''
$rows = json_decode(stream_get_contents(STDIN), true, 512, JSON_THROW_ON_ERROR);
foreach ($rows as $row) {
$key = base64_decode(substr($row['app_key'], 7), true);
$payload = json_decode(base64_decode($row['envelope']), true);
$mac = hash_hmac('sha256', $payload['iv'].$payload['value'], $key);
if (!hash_equals($mac, $payload['mac'])) { throw new Exception('MAC mismatch'); }
$plain = openssl_decrypt($payload['value'], $row['cipher'], $key, 0, base64_decode($payload['iv']));
if ($plain !== $row['plaintext']) { throw new Exception('decrypt mismatch'); }
$encrypted = openssl_encrypt($row['plaintext'], $row['cipher'], $key, 0, base64_decode($payload['iv']));
if ($encrypted !== $payload['value']) { throw new Exception('encrypt mismatch'); }
}
echo 'PASS PHP OpenSSL: '.count($rows).' fixtures'.PHP_EOL;
'''
subprocess.run([php, "-r", code], input=json.dumps(fixtures), text=True, check=True)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--check-php", action="store_true")
args = parser.parse_args()
rows = generate()
target = Path(__file__).parent / "fixtures" / "laravel_cbc.json"
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(json.dumps(rows, indent=2, ensure_ascii=False) + "\n")
print(f"Generated {len(rows)} independent Python AES CBC / HMAC fixtures: {target}")
if args.check_php:
check_php(rows)