""" On-device verification for the cross-compiled cryptography + cffi wheels. Run after installing: pip install pycparser-2.22-py2.py3-none-any.whl pip install cffi-2.1.1-cp312-cp312-android_24_.whl pip install cryptography-50.0.0-cp312-abi3-android_24_.whl Usage: python test_crypto_on_device.py [--quick] Exit code 0 = everything required PASSed. Sections marked [SKIP] are features the bundled OpenSSL 3.2.1 does not provide (e.g. post-quantum ML-KEM/ML-DSA) and are informational only. Generated by RIMI """ import sys import traceback RESULTS = [] def test(name, fn): try: fn() RESULTS.append((name, "PASS", None)) except NotImplementedError as exc: RESULTS.append((name, "SKIP", str(exc))) except Exception as exc: RESULTS.append((name, "FAIL", "%s: %s" % (type(exc).__name__, exc))) print(" ! %s -> %s: %s" % (name, type(exc).__name__, exc)) def section(title): print("=" * 60) print(title) print("=" * 60) # --------------------------------------------------------------------------- # 1. cryptography import surface # --------------------------------------------------------------------------- def imports_core(): import cryptography assert cryptography.__version__ == "50.0.0", cryptography.__version__ import cryptography.hazmat.bindings._rust # noqa: F401 (the .so extension) import cryptography.exceptions # noqa: F401 import cryptography.utils # noqa: F401 import cryptography.fernet # noqa: F401 import cryptography.cobblestone # noqa: F401 def imports_bindings(): import cryptography.hazmat.backends.openssl.backend # noqa: F401 import cryptography.hazmat.bindings.openssl._conditional # noqa: F401 import cryptography.hazmat.bindings.openssl.binding # noqa: F401 import cryptography.hazmat.bindings._rust # noqa: F401 def imports_primitives(): import cryptography.hazmat.primitives.hashes # noqa: F401 import cryptography.hazmat.primitives.hmac # noqa: F401 import cryptography.hazmat.primitives.cmac # noqa: F401 import cryptography.hazmat.primitives.ciphers # noqa: F401 import cryptography.hazmat.primitives.ciphers.aead # noqa: F401 import cryptography.hazmat.primitives.padding # noqa: F401 import cryptography.hazmat.primitives.constant_time # noqa: F401 import cryptography.hazmat.primitives.keywrap # noqa: F401 import cryptography.hazmat.primitives.poly1305 # noqa: F401 import cryptography.hazmat.primitives.hpke # noqa: F401 import cryptography.hazmat.primitives.kdf.hkdf # noqa: F401 import cryptography.hazmat.primitives.kdf.pbkdf2 # noqa: F401 import cryptography.hazmat.primitives.kdf.scrypt # noqa: F401 import cryptography.hazmat.primitives.kdf.kbkdf # noqa: F401 import cryptography.hazmat.primitives.kdf.concatkdf # noqa: F401 import cryptography.hazmat.primitives.kdf.x963kdf # noqa: F401 import cryptography.hazmat.primitives.serialization # noqa: F401 import cryptography.hazmat.primitives.serialization.pkcs7 # noqa: F401 import cryptography.hazmat.primitives.serialization.pkcs12 # noqa: F401 import cryptography.hazmat.primitives.serialization.ssh # noqa: F401 import cryptography.hazmat.primitives.twofactor.hotp # noqa: F401 import cryptography.hazmat.primitives.twofactor.totp # noqa: F401 def imports_asymmetric(): import cryptography.hazmat.primitives.asymmetric.rsa # noqa: F401 import cryptography.hazmat.primitives.asymmetric.ec # noqa: F401 import cryptography.hazmat.primitives.asymmetric.dsa # noqa: F401 import cryptography.hazmat.primitives.asymmetric.dh # noqa: F401 import cryptography.hazmat.primitives.asymmetric.ed25519 # noqa: F401 import cryptography.hazmat.primitives.asymmetric.ed448 # noqa: F401 import cryptography.hazmat.primitives.asymmetric.x25519 # noqa: F401 import cryptography.hazmat.primitives.asymmetric.x448 # noqa: F401 import cryptography.hazmat.primitives.asymmetric.padding # noqa: F401 import cryptography.hazmat.primitives.asymmetric.utils # noqa: F401 def imports_x509(): import cryptography.x509 # noqa: F401 import cryptography.x509.base # noqa: F401 import cryptography.x509.extensions # noqa: F401 import cryptography.x509.general_name # noqa: F401 import cryptography.x509.name # noqa: F401 import cryptography.x509.ocsp # noqa: F401 import cryptography.x509.oid # noqa: F401 import cryptography.x509.verification # noqa: F401 import cryptography.x509.certificate_transparency # noqa: F401 # --------------------------------------------------------------------------- # 2. hashes / hmac / cmac # --------------------------------------------------------------------------- def hashes(): from cryptography.hazmat.primitives import hashes d = hashes.Hash(hashes.SHA256()) d.update(b"hello cryptography") assert d.finalize().hex() == ( "a301dc0ba3af7e81df0d99b6b37c4ffca866cb2ee44058bb76158db3b59d02b9" ), "SHA256 mismatch" def sha512(): from cryptography.hazmat.primitives import hashes d = hashes.Hash(hashes.SHA512()) d.update(b"abc") h = d.finalize().hex() assert h.startswith("ddaf35a193617aba"), "SHA512 mismatch" def sha3_and_blake2(): from cryptography.hazmat.primitives import hashes for cls in (hashes.SHA3_256, hashes.SHA3_512): d = hashes.Hash(cls()) d.update(b"data") assert len(d.finalize()) == d.algorithm.digest_size d = hashes.Hash(hashes.BLAKE2b(digest_size=64)) d.update(b"data") assert len(d.finalize()) == 64 d = hashes.Hash(hashes.SHA256()) d.update(b"x") assert len(d.finalize()) == 32 def hmac_(): from cryptography.hazmat.primitives import hashes, hmac h = hmac.HMAC(b"key", hashes.SHA256()) h.update(b"message") assert len(h.finalize()) == 32 def cmac(): from cryptography.hazmat.primitives import cmac from cryptography.hazmat.primitives.ciphers import algorithms c = cmac.CMAC(algorithms.AES(b"0" * 16)) c.update(b"msg") assert len(c.finalize()) == 16 # --------------------------------------------------------------------------- # 3. symmetric ciphers # --------------------------------------------------------------------------- def aes_gcm(): from cryptography.hazmat.primitives.ciphers.aead import AESGCM key = bytes(range(32)) nonce = b"123456789012" ct = AESGCM(key).encrypt(nonce, b"secret message", b"aad") pt = AESGCM(key).decrypt(nonce, ct, b"aad") assert pt == b"secret message" def aes_cbc(): from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes cipher = Cipher(algorithms.AES(b"0" * 16), modes.CBC(b"0" * 16)) enc = cipher.encryptor() ct = enc.update(b"0123456789abcdef") + enc.finalize() cipher2 = Cipher(algorithms.AES(b"0" * 16), modes.CBC(b"0" * 16)) dec = cipher2.decryptor() assert dec.update(ct) + dec.finalize() == b"0123456789abcdef" def chacha20_poly1305(): from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 key = bytes(range(32)) nonce = b"123456789012" ct = ChaCha20Poly1305(key).encrypt(nonce, b"hello", None) assert ChaCha20Poly1305(key).decrypt(nonce, ct, None) == b"hello" def padding_(): from cryptography.hazmat.primitives import padding p = padding.PKCS7(128).padder() data = p.update(b"1234") + p.finalize() u = padding.PKCS7(128).unpadder() assert u.update(data) + u.finalize() == b"1234" def keywrap_(): from cryptography.hazmat.primitives.keywrap import aes_key_wrap, aes_key_unwrap kek = b"0" * 16 wrapped = aes_key_wrap(kek, b"1" * 24) assert aes_key_unwrap(kek, wrapped) == b"1" * 24 # --------------------------------------------------------------------------- # 4. asymmetric # --------------------------------------------------------------------------- def rsa_sign_verify(): from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding, rsa key = rsa.generate_private_key(public_exponent=65537, key_size=2048) sig = key.sign(b"data", padding.PKCS1v15(), hashes.SHA256()) key.public_key().verify(sig, b"data", padding.PKCS1v15(), hashes.SHA256()) pub_pem = key.public_key().public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo, ) loaded = serialization.load_pem_public_key(pub_pem) loaded.verify(sig, b"data", padding.PKCS1v15(), hashes.SHA256()) priv_pem = key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.NoEncryption(), ) assert serialization.load_pem_private_key(priv_pem, None) is not None def rsa_oaep(): from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding, rsa key = rsa.generate_private_key(public_exponent=65537, key_size=2048) ct = key.public_key().encrypt( b"top secret", padding.OAEP(mgf=padding.MGF1(hashes.SHA256()), algorithm=hashes.SHA256(), label=None) ) assert key.decrypt(ct, padding.OAEP(mgf=padding.MGF1(hashes.SHA256()), algorithm=hashes.SHA256(), label=None)) == b"top secret" def ec_(): from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec key = ec.generate_private_key(ec.SECP256R1()) sig = key.sign(b"msg", ec.ECDSA(hashes.SHA256())) key.public_key().verify(sig, b"msg", ec.ECDSA(hashes.SHA256())) def ed25519_(): from cryptography.hazmat.primitives.asymmetric import ed25519 key = ed25519.Ed25519PrivateKey.generate() sig = key.sign(b"msg") key.public_key().verify(sig, b"msg") def ed448_(): from cryptography.hazmat.primitives.asymmetric import ed448 key = ed448.Ed448PrivateKey.generate() sig = key.sign(b"msg") key.public_key().verify(sig, b"msg") def x25519_(): from cryptography.hazmat.primitives.asymmetric import x25519 a = x25519.X25519PrivateKey.generate() b = x25519.X25519PrivateKey.generate() sa = a.exchange(b.public_key()) sb = b.exchange(a.public_key()) assert sa == sb def x448_(): from cryptography.hazmat.primitives.asymmetric import x448 a = x448.X448PrivateKey.generate() b = x448.X448PrivateKey.generate() assert a.exchange(b.public_key()) == b.exchange(a.public_key()) def dh_(): from cryptography.hazmat.primitives.asymmetric import dh params = dh.generate_parameters(generator=2, key_size=2048) a = params.generate_private_key() b = params.generate_private_key() assert a.exchange(b.public_key()) == b.exchange(a.public_key()) def dsa_(): from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import dsa key = dsa.generate_private_key(key_size=2048) sig = key.sign(b"m", hashes.SHA256()) key.public_key().verify(sig, b"m", hashes.SHA256()) # --------------------------------------------------------------------------- # 5. KDF / password hashing # --------------------------------------------------------------------------- def hkdf_(): from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.hkdf import HKDF k = HKDF(algorithm=hashes.SHA256(), length=32, salt=None, info=b"i").derive(b"pw") assert len(k) == 32 def pbkdf2_(): from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC k = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=b"s", iterations=1000).derive(b"pw") assert len(k) == 32 def scrypt_(): from cryptography.hazmat.primitives.kdf.scrypt import Scrypt k = Scrypt(salt=b"salt", length=32, n=2**14, r=8, p=1).derive(b"pw") assert len(k) == 32 # --------------------------------------------------------------------------- # 6. x509 # --------------------------------------------------------------------------- def x509_selfsigned(): import datetime from cryptography import x509 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec from cryptography.x509.oid import NameOID key = ec.generate_private_key(ec.SECP256R1()) name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "test")]) now = datetime.datetime.now(datetime.timezone.utc) cert = ( x509.CertificateBuilder() .subject_name(name) .issuer_name(name) .public_key(key.public_key()) .serial_number(x509.random_serial_number()) .not_valid_before(now - datetime.timedelta(days=1)) .not_valid_after(now + datetime.timedelta(days=1)) .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True) .sign(key, hashes.SHA256()) ) pem = cert.public_bytes(serialization.Encoding.PEM) loaded = x509.load_pem_x509_certificate(pem) assert loaded.subject == name loaded.public_key().verify( cert.signature, cert.tbs_certificate_bytes, ec.ECDSA(hashes.SHA256()), ) # --------------------------------------------------------------------------- # 7. fernet # --------------------------------------------------------------------------- def fernet_(): from cryptography.fernet import Fernet f = Fernet(Fernet.generate_key()) t = f.encrypt(b"payload") assert f.decrypt(t) == b"payload" # --------------------------------------------------------------------------- # 8. cffi # --------------------------------------------------------------------------- def cffi_import(): import _cffi_backend import cffi assert cffi.__version__ == "2.1.1", cffi.__version__ # backend version must match the cffi package version assert getattr(_cffi_backend, "__version__", None) in (None, "2.1.1") def _open_libc(ffi): import ctypes.util for name in ("libc.so", "libc.so.6", "c"): try: return ffi.dlopen(name) except OSError: continue found = ctypes.util.find_library("c") if found: return ffi.dlopen(found) raise NotImplementedError("no libc available on this platform") def cffi_libc_call(): import cffi ffi = cffi.FFI() ffi.cdef("size_t strlen(const char *s);") lib = _open_libc(ffi) s = ffi.new("char[]", b"android-cffi") n = lib.strlen(s) assert n == 12, n def cffi_abi_mode(): import cffi ffi = cffi.FFI() ffi.cdef("int abs(int x);") lib = _open_libc(ffi) assert lib.abs(-5) == 5 def cffi_struct(): import cffi ffi = cffi.FFI() ffi.cdef( "typedef struct { int x; int y; } point_t; " "point_t make_point(int x, int y);" ) # use ABI mode call into a trivial function we link ourselves via ctypes-free # path is not available, so just validate the type machinery and FFI().new() p = ffi.new("point_t*") p.x = 3 p.y = 4 assert p.x == 3 and p.y == 4 def cffi_callback(): import cffi ffi = cffi.FFI() ffi.cdef("typedef int (*cb_t)(int);") calls = [] cb = ffi.callback("int(int)", lambda n: (calls.append(n), n + 1)[1]) assert cb(10) == 11 assert calls == [10] # --------------------------------------------------------------------------- def main(): quick = "--quick" in sys.argv section("1. cryptography imports (full surface)") test("import core (version + _rust.so + fernet)", imports_core) test("import bindings/backends", imports_bindings) test("import primitives", imports_primitives) test("import asymmetric", imports_asymmetric) test("import x509", imports_x509) section("2. hashes / hmac / cmac") test("SHA256", hashes) test("SHA512", sha512) test("SHA3 / BLAKE2", sha3_and_blake2) test("HMAC", hmac_) test("CMAC", cmac) section("3. symmetric ciphers") test("AES-GCM", aes_gcm) test("AES-CBC", aes_cbc) test("ChaCha20-Poly1305", chacha20_poly1305) test("PKCS7 padding", padding_) test("AES keywrap", keywrap_) section("4. asymmetric") test("RSA sign/verify + PEM roundtrip", rsa_sign_verify) test("RSA-OAEP", rsa_oaep) test("EC P-256", ec_) test("Ed25519", ed25519_) test("Ed448", ed448_) test("X25519 key exchange", x25519_) test("X448 key exchange", x448_) test("DH", dh_) test("DSA", dsa_) section("5. KDF") test("HKDF", hkdf_) test("PBKDF2", pbkdf2_) test("scrypt", scrypt_) section("6. X.509") test("self-signed cert build+parse+verify", x509_selfsigned) section("7. Fernet") test("Fernet encrypt/decrypt", fernet_) section("8. cffi") test("cffi import (backend version match)", cffi_import) test("cffi dlopen libc + strlen", cffi_libc_call) test("cffi ABI mode abs()", cffi_abi_mode) test("cffi struct", cffi_struct) test("cffi callback", cffi_callback) print() print("=" * 60) print("SUMMARY") print("=" * 60) fails = 0 skips = 0 for name, status, why in RESULTS: mark = " OK" if status == "PASS" else (" SKIP" if status == "SKIP" else "FAIL") print("%s %s" % (mark, name)) if why: print(" -> %s" % why) if status == "FAIL": fails += 1 elif status == "SKIP": skips += 1 print() passed = len(RESULTS) - fails - skips print("passed=%d skipped=%d failed=%d" % (passed, skips, fails)) if fails: print("RESULT: FAILED") elif skips and not quick: print("RESULT: PASSED (with informational skips)") else: print("RESULT: PASSED") sys.exit(1 if fails else 0) if __name__ == "__main__": main()