# Unit 02 — Profile Generator ## Goal Cryptographic credential generation (IMSI, Ki, OPc, ICCID) and Phase 1 mock eSIM profile. Real pySim integration is deferred to Unit 06. ## File - `server/profile_generator.py` --- ## Functions ### `generate_imsi(mcc: str, mnc: str) -> str` - Concatenate MCC + MNC + 10-digit random MSIN - MSIN generated with `secrets.randbelow(10**10)` zero-padded to 10 digits - Result must be exactly 15 digits - Validates format before returning ### `generate_ki() -> bytes` - `secrets.token_bytes(16)` — 16 cryptographically random bytes ### `generate_iccid() -> str` - Industry prefix: `8910` (Telecom, country code 10) - Append MCC+MNC from config - Fill remaining digits randomly - Total 18 digits + 1 Luhn check digit = 19 digits - Luhn algorithm: ```python def luhn_checksum(number: str) -> int: digits = [int(d) for d in number] odd_digits = digits[-1::-2] even_digits = digits[-2::-2] total = sum(odd_digits) for d in even_digits: total += sum(divmod(d * 2, 10)) return (10 - (total % 10)) % 10 ``` ### `derive_opc(ki: bytes, op: bytes) -> bytes` Milenage OPc derivation — XOR of OP with AES_Ki(OP): ```python from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend def derive_opc(ki: bytes, op: bytes) -> bytes: cipher = Cipher(algorithms.AES(ki), modes.ECB(), backend=default_backend()) enc = cipher.encryptor() encrypted_op = enc.update(op) + enc.finalize() return bytes(a ^ b for a, b in zip(encrypted_op, op)) ``` ### `create_esim_profile(imsi, ki, opc, iccid, profile_name, carrier_name) -> bytes` **Phase 1 (mock):** Returns JSON-encoded bytes with all credential fields: ```python import json def create_esim_profile(imsi, ki, opc, iccid, profile_name, carrier_name): profile = { "version": "1.0", "type": "mock", "iccid": iccid, "imsi": imsi, "ki": ki.hex() if isinstance(ki, bytes) else ki, "opc": opc.hex() if isinstance(opc, bytes) else opc, "profile_name": profile_name, "carrier_name": carrier_name, } return json.dumps(profile).encode("utf-8") ``` **Phase 2 (Unit 06):** Replace with real pySim SGP.22 profile generation. --- ## Known Test Vectors (for Unit 10 tests) From 3GPP TS 35.208: - Ki = `000102030405060708090a0b0c0d0e0f` - OP = `63bfa50ee6523365ff14c1f45f88737d` - OPc = compute at test time: `AES_Ki(OP) XOR OP` --- ## Verification ```bash python -c " from server.profile_generator import generate_imsi, generate_ki, generate_iccid, derive_opc, create_esim_profile import binascii imsi = generate_imsi('302', '720') assert len(imsi) == 15 and imsi.startswith('302720'), f'bad IMSI: {imsi}' ki = generate_ki() assert len(ki) == 16 iccid = generate_iccid() assert len(iccid) == 19, f'bad ICCID length: {len(iccid)}' op = bytes.fromhex('63bfa50ee6523365ff14c1f45f88737d') opc = derive_opc(ki, op) assert len(opc) == 16 profile = create_esim_profile(imsi, ki, opc, iccid, 'Emergency 5G', 'Ad-Hoc Network') assert len(profile) > 0 print('All checks passed') " ```