| """
|
| 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_<ABI>.whl
|
| pip install cryptography-50.0.0-cp312-abi3-android_24_<ABI>.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)
|
|
|
|
|
|
|
|
|
|
|
| def imports_core():
|
| import cryptography
|
| assert cryptography.__version__ == "50.0.0", cryptography.__version__
|
| import cryptography.hazmat.bindings._rust
|
| import cryptography.exceptions
|
| import cryptography.utils
|
| import cryptography.fernet
|
| import cryptography.cobblestone
|
|
|
|
|
| def imports_bindings():
|
| import cryptography.hazmat.backends.openssl.backend
|
| import cryptography.hazmat.bindings.openssl._conditional
|
| import cryptography.hazmat.bindings.openssl.binding
|
| import cryptography.hazmat.bindings._rust
|
|
|
|
|
| def imports_primitives():
|
| import cryptography.hazmat.primitives.hashes
|
| import cryptography.hazmat.primitives.hmac
|
| import cryptography.hazmat.primitives.cmac
|
| import cryptography.hazmat.primitives.ciphers
|
| import cryptography.hazmat.primitives.ciphers.aead
|
| import cryptography.hazmat.primitives.padding
|
| import cryptography.hazmat.primitives.constant_time
|
| import cryptography.hazmat.primitives.keywrap
|
| import cryptography.hazmat.primitives.poly1305
|
| import cryptography.hazmat.primitives.hpke
|
| import cryptography.hazmat.primitives.kdf.hkdf
|
| import cryptography.hazmat.primitives.kdf.pbkdf2
|
| import cryptography.hazmat.primitives.kdf.scrypt
|
| import cryptography.hazmat.primitives.kdf.kbkdf
|
| import cryptography.hazmat.primitives.kdf.concatkdf
|
| import cryptography.hazmat.primitives.kdf.x963kdf
|
| import cryptography.hazmat.primitives.serialization
|
| import cryptography.hazmat.primitives.serialization.pkcs7
|
| import cryptography.hazmat.primitives.serialization.pkcs12
|
| import cryptography.hazmat.primitives.serialization.ssh
|
| import cryptography.hazmat.primitives.twofactor.hotp
|
| import cryptography.hazmat.primitives.twofactor.totp
|
|
|
|
|
| def imports_asymmetric():
|
| import cryptography.hazmat.primitives.asymmetric.rsa
|
| import cryptography.hazmat.primitives.asymmetric.ec
|
| import cryptography.hazmat.primitives.asymmetric.dsa
|
| import cryptography.hazmat.primitives.asymmetric.dh
|
| import cryptography.hazmat.primitives.asymmetric.ed25519
|
| import cryptography.hazmat.primitives.asymmetric.ed448
|
| import cryptography.hazmat.primitives.asymmetric.x25519
|
| import cryptography.hazmat.primitives.asymmetric.x448
|
| import cryptography.hazmat.primitives.asymmetric.padding
|
| import cryptography.hazmat.primitives.asymmetric.utils
|
|
|
|
|
| def imports_x509():
|
| import cryptography.x509
|
| import cryptography.x509.base
|
| import cryptography.x509.extensions
|
| import cryptography.x509.general_name
|
| import cryptography.x509.name
|
| import cryptography.x509.ocsp
|
| import cryptography.x509.oid
|
| import cryptography.x509.verification
|
| import cryptography.x509.certificate_transparency
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
| 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())
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
| 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()),
|
| )
|
|
|
|
|
|
|
|
|
|
|
| def fernet_():
|
| from cryptography.fernet import Fernet
|
| f = Fernet(Fernet.generate_key())
|
| t = f.encrypt(b"payload")
|
| assert f.decrypt(t) == b"payload"
|
|
|
|
|
|
|
|
|
|
|
| def cffi_import():
|
| import _cffi_backend
|
| import cffi
|
| assert cffi.__version__ == "2.1.1", cffi.__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);"
|
| )
|
|
|
|
|
| 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()
|
|
|