iOS code signing written in Python. Re-sign .ipa archives, .app bundles,
frameworks, dylibs and bare Mach-O executables with a .p12 identity and a
.mobileprovision profile, producing a signature that iOS and Apple's
codesign accept.
Via PyPI:
pip install ipasignWith uv:
uv add ipasignRequires Python 3.10 or later. The only dependencies are cryptography and
asn1crypto, both installed automatically.
import ipasign
key = ipasign.Key("identity.p12", "profile.mobileprovision", "password")
app = ipasign.App("input.ipa")
out = app.sign(key)
print(f"Successfully signed: {out.output_path}")
print(f"{out.app_name} {out.app_version} ({out.bundle_id})")App accepts anything signable. An .ipa archive gets a default output named
after the input, so input.ipa becomes input-signed.ipa. Pass output to
name the target yourself:
out = ipasign.App("input.ipa").sign(key, output="build/signed.ipa")A bundle folder, a framework, a dylib and a bare Mach-O executable are signed
where they are, so output is refused for them:
ipasign.App("Payload/MyApp.app").sign(key) # signed in place
ipasign.App("libX.dylib").sign(key) # signed in placeA folder that is not itself a bundle is searched for the .app/.appex
inside it, so an unzip output can be handed over as it is:
app = ipasign.App("unzipped")
print(app.path) # unzipped/Payload/MyApp.app
app.sign(key)sign() returns a result object, not a boolean:
| Attribute | Meaning |
|---|---|
output_path |
Where the signed artifact actually landed |
bundle_id |
The identifier sealed into the CodeDirectory |
signed_count |
How many Mach-O files were signed |
app_name |
The app's display name, read from Info.plist |
app_version |
The release version, read from Info.plist |
Failures raise. Every exception derives from ipasign.IpasignError.
No credentials needed:
ipasign.App("input.ipa").sign(ipasign.Key(adhoc=True))Release notes are available in CHANGELOG.md.
This Project under MIT License
Copyright (c) 2026 Senophyx