Email me

Guide · 4 min read

Filed under iOS and Swift App development Guides

How to fix "MAC verification failed during PKCS12 import" (the legacy .p12 fix)

Steps
5
Hands-on
10 min
Reading
4 min
Checked
Oct 26

The short version

"MAC verification failed during PKCS12 import (wrong password?)" almost never means a wrong password. OpenSSL 3 writes .p12 files with AES-256 and PBKDF2 by default, and macOS security import cannot read them. Re-export the file in legacy format (3DES and SHA-1) with openssl pkcs12 -export -legacy, and iOS code signing on a CI runner imports cleanly.

Title card: MAC verification failed during PKCS12 import. Error says wrong password. Real cause: AES-256 plus PBKDF2 .p12, the OpenSSL 3 default. macOS security wants a 3DES plus SHA-1 legacy .p12. Fix: openssl pkcs12 -export -legacy. Or in Python: PBESv1SHA1And3KeyTripleDESCBC.
The error blames your password. The file format is the problem. Diagram by Tensorust.

What you need

  • Your signing certificate and private key as a .p12 (or .pem files)
  • OpenSSL 3 or Python with the cryptography package
  • A macOS CI job, such as a GitHub Actions macOS runner

Part of the article Build an iOS app on Windows: how 21 apps shipped with no Mac. This page answers one question from it in depth.

On this page · 10 sections
  1. Key facts
  2. Confirm it is the format
  3. Re-export as legacy
  4. Or export legacy from Python
  5. Store it as a GitHub secret
  6. Import on the runner
  7. What goes wrong
  8. Related
  9. FAQ
  10. Sources

This error cost us real macOS runs while we were shipping iOS apps from Windows. Apple's tooling lags a format change the rest of the world made years ago, and the error message sends you chasing the one thing that is not broken. Here is the fix that has held across every app since.

Comparison card: OpenSSL 3 default .p12 uses AES-256-CBC with PBKDF2 and a SHA-256 MAC; -legacy uses 3DES for the key, RC2 or 3DES for the certificate and a SHA-1 MAC; macOS security import reads legacy and fails on the default. Same certificate, two wrappers. Diagram by Tensorust from the OpenSSL 3.0 pkcs12 manual.

Key facts

What Fact Source (checked Oct 26 2026)
OpenSSL 3 default .p12 encryption AES-256-CBC with PBKDF2, cert and key OpenSSL 3.0 pkcs12 manual
OpenSSL 3 default MAC SHA-256, 2048 iterations OpenSSL 3.0 pkcs12 manual
-legacy key encryption 3DES-CBC OpenSSL 3.0 pkcs12 manual
Python legacy option PBES.PBESv1SHA1And3KeyTripleDESCBC cryptography serialization docs
Our signing script Exports a legacy .p12, pipes it to gh secret set Our iOS-on-Windows write-up

Step 01: Confirm it is the format

openssl pkcs12 -info -in cert.p12 -noout

If OpenSSL accepts your password and prints PBES2, PBKDF2, AES-256-CBC, your password is right. The file is just in a wrapper security import will not open.

Step 02: Re-export as legacy

openssl pkcs12 -in cert.p12 -nodes -out tmp.pem
openssl pkcs12 -export -legacy -in tmp.pem -out cert-legacy.p12
rm tmp.pem

Set a long random password. If OpenSSL says the legacy provider is missing, point OPENSSL_MODULES at your install's ossl-modules folder, as the manual notes.

Step 03: Or export legacy from Python

This is what our signing script does, so the certificate is created and wrapped in one go:

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.serialization import pkcs12, PrivateFormat
enc = (PrivateFormat.PKCS12.encryption_builder()
       .key_cert_algorithm(pkcs12.PBES.PBESv1SHA1And3KeyTripleDESCBC)
       .hmac_hash(hashes.SHA1())
       .build(P12_PASSWORD.encode()))
p12 = pkcs12.serialize_key_and_certificates(b"dist", key, cert, None, enc)

Step 04: Store it as a GitHub secret

base64 -w0 cert-legacy.p12 | gh secret set DIST_P12
gh secret set DIST_P12_PASSWORD

Nothing lands in the repo. Create the certificate on your own machine, not in CI: a certificate made on a runner has its private key deleted with the runner. The App Store Connect API key guide covers the key that creates it.

Step 05: Import on the runner

Flow: 1 your machine makes key and cert and exports a legacy .p12; 2 GitHub secret holds base64 .p12 and password; 3 runner creates a throwaway keychain; 4 security import; 5 set-key-partition-list or it hangs; 6 sign with an App Store profile. The whole signing path. Diagram by Tensorust.

echo "$DIST_P12" | base64 --decode > cert.p12
security create-keychain -p "$KC_PW" ci.keychain
security default-keychain -s ci.keychain
security unlock-keychain -p "$KC_PW" ci.keychain
security import cert.p12 -k ci.keychain -P "$DIST_P12_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "$KC_PW" ci.keychain
Troubleshooting

What goes wrong

  • Still "wrong password?" after re-export: the secret has a trailing newline or was base64-wrapped at 76 columns. Use base64 -w0 and set it again.
  • Build hangs at codesign: you skipped set-key-partition-list.
  • "Could not find the newly generated certificate installed": a tool made a fresh certificate on the runner. Import yours instead, and revoke the orphans.
  • Each retry costs money: on a private repo every failed attempt is billed. See what a macOS runner costs.

FAQ

What does MAC verification failed during PKCS12 import mean?

The tool could not check the .p12 file's integrity code. Usually the password is fine: macOS security import cannot read the AES-256 and PBKDF2 format that OpenSSL 3 writes by default, and reports it as a possible wrong password.

How do I create a legacy .p12 with OpenSSL 3?

Add -legacy to the export: openssl pkcs12 -export -legacy -in cert.pem -inkey key.pem -out cert.p12. In legacy mode OpenSSL uses 3DES for the private key and RC2 or 3DES for the certificate.

Why does it work on my Mac but not in CI?

If you exported from Keychain Access, macOS wrote a format it can read. A .p12 made on Linux or Windows with OpenSSL 3 or modern Python libraries uses AES-256 by default.

Is a legacy .p12 insecure?

3DES and SHA-1 are older, so protect the file with a long random password and keep it only in encrypted CI secrets. The wrapper protects the file at rest; the code signature itself is unchanged.

Why does codesign hang after a successful import?

Without security set-key-partition-list, macOS waits for a permission prompt nobody can click on a CI runner. Run it after the import with the keychain password.

Sources

All checked October 26, 2026.

Do this next

Every number on this page links to its source and was checked on Oct 26, 2026. Spot an error or a newer figure? Email matt@tensorust.com and it gets fixed.

Read next

Back to The Manual →   All articles →

Get the next one by email.

What my AI agents tried this week, with the real numbers: what worked, what flopped, what made money. One email when there is something worth reading.

No spam, never sold. Every email has a one-click unsubscribe.

Up next

The article this answer comes fromBuild an iOS app on Windows: how 21 apps shipped with no MacHow to build an iOS app on Windows with no Mac: XcodeGen, a GitHub Actions macOS runner, signing via the App Store Connect API and fastlane. Real code and runs.Read it next →

Art: Nicholas Roerich, Depsang Plains (1925-26). Public domain, via Wikimedia Commons.

Get the receipts by email.

What my AI agents tried this week, with the real numbers: what worked, what flopped, what made money. One email when there is something worth reading.

No spam, never sold. Every email has a one-click unsubscribe.

Did this answer your question?

Popular in the shop

All 124 tools →

Browse: Bundles and kitsHome, family and schoolMoney and businessAI toolsSports and fitnessMaking and hobbiesFood and drinkIndie dev and launchTrading and quant

Free: Landing page teardown iPhone app cost calculator LLM API cost calculator Paid online: $49 Launch Check App builds Articles FAQ

What are people saying about this? Read and post on The Floor →