@mmerterden/multi-agent-pipeline 20.6.0 → 20.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +328 -329
- package/package.json +2 -2
- package/pipeline/multi-agent-refs/features/design-conformance.md +62 -64
- package/pipeline/scripts/_notices.mjs +12 -1
- package/pipeline/scripts/gen-skills-index.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +106 -106
- package/pipeline/skills/shared/README.md +71 -71
- package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +1 -0
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
- package/pipeline/skills/shared/external/android-architecture/SKILL.md +2 -0
- package/pipeline/skills/shared/external/android-performance/SKILL.md +2 -0
- package/pipeline/skills/shared/external/android-security/SKILL.md +2 -0
- package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
- package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
- package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
- package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
- package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
- package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
- package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
- package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
- package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
- package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
- package/pipeline/skills/shared/external/backlog/BACKLOG.md +1 -1
- package/pipeline/skills/shared/external/backlog/SKILL.md +56 -33
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
- package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +1 -0
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
- package/pipeline/skills/shared/external/compose-components/SKILL.md +2 -0
- package/pipeline/skills/shared/external/compose-navigation/SKILL.md +3 -2
- package/pipeline/skills/shared/external/compose-testing/SKILL.md +2 -0
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
- package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
- package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
- package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
- package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
- package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
- package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
- package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
- package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
- package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
- package/pipeline/skills/shared/external/council/SKILL.md +1 -0
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
- package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
- package/pipeline/skills/shared/external/css-modern/SKILL.md +1 -0
- package/pipeline/skills/shared/external/database-patterns/SKILL.md +1 -0
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
- package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
- package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
- package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
- package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
- package/pipeline/skills/shared/external/evidence-github/SKILL.md +2 -0
- package/pipeline/skills/shared/external/evidence-registry/SKILL.md +2 -0
- package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +2 -0
- package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
- package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
- package/pipeline/skills/shared/external/html-semantic/SKILL.md +1 -0
- package/pipeline/skills/shared/external/humanizer/SKILL.md +1 -0
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
- package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
- package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +1 -0
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
- package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +1 -0
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
- package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
- package/pipeline/skills/shared/external/ios-security/SKILL.md +2 -0
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
- package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
- package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +91 -283
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +119 -151
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +60 -90
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +119 -156
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +726 -787
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +253 -288
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +243 -304
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +88 -104
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +181 -235
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +198 -263
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +461 -466
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +145 -151
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +123 -141
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +146 -157
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +22 -19
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +156 -140
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
- package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
- package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
- package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +1 -0
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
- package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
- package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
- package/pipeline/skills/shared/external/play-store-review/SKILL.md +2 -0
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
- package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
- package/pipeline/skills/shared/external/python-patterns/SKILL.md +1 -0
- package/pipeline/skills/shared/external/react-best-practices/SKILL.md +1 -0
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
- package/pipeline/skills/shared/external/rest-api-design/SKILL.md +1 -0
- package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +2 -0
- package/pipeline/skills/shared/external/room-database/SKILL.md +2 -0
- package/pipeline/skills/shared/external/search-first/SKILL.md +1 -0
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
- package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
- package/pipeline/skills/shared/external/signal-community/SKILL.md +2 -0
- package/pipeline/skills/shared/external/skill-creator/SKILL.md +80 -41
- package/pipeline/skills/shared/external/skill-creator/audit.md +63 -59
- package/pipeline/skills/shared/external/skill-creator/checklist.md +28 -20
- package/pipeline/skills/shared/external/skill-creator/examples.md +40 -40
- package/pipeline/skills/shared/external/skill-creator/label-check.md +48 -36
- package/pipeline/skills/shared/external/skill-creator/scripts/audit-panel.js +91 -100
- package/pipeline/skills/shared/external/skill-creator/template.md +51 -39
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
- package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
- package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
- package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
- package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
- package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
- package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
- package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
- package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
- package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
- package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
- package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
- package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
- package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
- package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
- package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
- package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
- package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
- package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
- package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
- package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
- package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
- package/pipeline/skills/shared/external/tailwind-css/SKILL.md +1 -0
- package/pipeline/skills/shared/external/testing-backend/SKILL.md +1 -0
- package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +1 -0
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
- package/pipeline/skills/shared/external/vue-composition/SKILL.md +1 -0
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/web-accessibility/SKILL.md +1 -0
- package/pipeline/skills/shared/external/web-performance/SKILL.md +1 -0
- package/pipeline/skills/shared/external/web-testing/SKILL.md +1 -0
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -1,530 +1,429 @@
|
|
|
1
|
-
# CryptoKit Symmetric
|
|
1
|
+
# CryptoKit: Hashing, MACs and Symmetric Encryption
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
> **Cross-references:** [secure-enclave.md] for hardware-backed asymmetric keys · [cryptokit-public-key.md] for ECDSA/ECDH/HPKE · [credential-storage-patterns.md] for key storage in Keychain · [common-anti-patterns.md] for the top-5 AI mistakes including hardcoded keys and nonce reuse
|
|
3
|
+
This file covers the symmetric half of CryptoKit: digests (SHA-2 and SHA-3), HMAC,
|
|
4
|
+
the two AEAD ciphers (AES-GCM and ChaChaPoly), `SymmetricKey`, nonce handling, key
|
|
5
|
+
derivation with HKDF and PBKDF2, and moving old CommonCrypto code over. The baseline
|
|
6
|
+
is iOS 13; HKDF as a standalone type needs iOS 14; SHA-3 needs iOS 26.
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
Types you will meet below: `SHA256`, `SHA384`, `SHA512`, `SHA3_256`, `HMAC`,
|
|
9
|
+
`AES.GCM.seal` / `AES.GCM.open`, `ChaChaPoly.seal` / `ChaChaPoly.open`,
|
|
10
|
+
`SymmetricKey`, `AES.GCM.Nonce`, `HKDF`, and the per-cipher `SealedBox`.
|
|
10
11
|
|
|
11
12
|
## Contents
|
|
12
13
|
|
|
13
|
-
- [
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- [
|
|
19
|
-
- [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- [
|
|
24
|
-
|
|
25
|
-
- [Streaming Encryption Limitation](#streaming-encryption-limitation)
|
|
26
|
-
- [SymmetricKey: Creation, Derivation, and Lifecycle](#symmetrickey-creation-derivation-and-lifecycle)
|
|
27
|
-
- [Random Key Generation](#random-key-generation)
|
|
28
|
-
- [Password-Based Key Derivation (PBKDF2 + HKDF)](#password-based-key-derivation-pbkdf2-hkdf)
|
|
29
|
-
- [HKDF for High-Entropy Key Derivation](#hkdf-for-high-entropy-key-derivation)
|
|
30
|
-
- [Key Storage and Hardcoding](#key-storage-and-hardcoding)
|
|
31
|
-
- [Migrating from CommonCrypto to CryptoKit](#migrating-from-commoncrypto-to-cryptokit)
|
|
32
|
-
- [Hashing: CC_SHA256 → SHA256](#hashing-ccsha256-sha256)
|
|
33
|
-
- [Encryption: CCCrypt (AES-CBC) → AES.GCM](#encryption-cccrypt-aes-cbc-aesgcm)
|
|
34
|
-
- [HMAC: CCHmac → HMAC](#hmac-cchmac-hmac)
|
|
35
|
-
- [What to Keep in CommonCrypto](#what-to-keep-in-commoncrypto)
|
|
36
|
-
- [AI Code Generator Mistakes](#ai-code-generator-mistakes)
|
|
37
|
-
- [Quantum Considerations for Symmetric Cryptography](#quantum-considerations-for-symmetric-cryptography)
|
|
38
|
-
- [OWASP Mapping](#owasp-mapping)
|
|
39
|
-
- [Testing Guidance](#testing-guidance)
|
|
40
|
-
- [WWDC and Reference Citations](#wwdc-and-reference-citations)
|
|
41
|
-
- [Conclusion](#conclusion)
|
|
42
|
-
- [Summary Checklist](#summary-checklist)
|
|
43
|
-
|
|
44
|
-
## Hashing: SHA-2 and SHA-3
|
|
45
|
-
|
|
46
|
-
CryptoKit's hash functions follow a unified `HashFunction` protocol. The SHA-2 family (`SHA256`, `SHA384`, `SHA512`) ships with iOS 13+. The SHA-3 family (`SHA3_256`, `SHA3_384`, `SHA3_512`) requires **iOS 26+ / macOS 26+** (added via apple/swift-crypto PR #397, tagged [WWDC25]).
|
|
47
|
-
|
|
48
|
-
The swift-crypto open-source package provides SHA-3 under `import Crypto` for older deployment targets. The Apple CryptoKit framework (`import CryptoKit`) requires iOS 26+ for SHA-3. Do not confuse package availability with framework availability.
|
|
49
|
-
|
|
50
|
-
All hash functions produce digest types that conform to `Sequence` (of `UInt8`), `ContiguousBytes`, `Hashable`, and `CustomStringConvertible`. Digest equality checks use **constant-time comparison** internally to prevent timing side-channels.
|
|
51
|
-
|
|
52
|
-
### One-Shot Hashing
|
|
53
|
-
|
|
54
|
-
**✅ Correct: SHA-256 hashing with hex output**
|
|
14
|
+
- [Digests](#digests)
|
|
15
|
+
- [HMAC](#hmac)
|
|
16
|
+
- [AES-GCM](#aes-gcm)
|
|
17
|
+
- [ChaChaPoly](#chachapoly)
|
|
18
|
+
- [SymmetricKey](#symmetrickey)
|
|
19
|
+
- [Moving off CommonCrypto](#moving-off-commoncrypto)
|
|
20
|
+
- [Frequent mistakes](#frequent-mistakes)
|
|
21
|
+
- [Post-quantum outlook](#post-quantum-outlook)
|
|
22
|
+
- [OWASP mapping](#owasp-mapping)
|
|
23
|
+
- [Tests worth writing](#tests-worth-writing)
|
|
24
|
+
- [Sources](#sources)
|
|
25
|
+
- [Checklist](#checklist)
|
|
55
26
|
|
|
56
|
-
|
|
57
|
-
import CryptoKit
|
|
27
|
+
## Digests
|
|
58
28
|
|
|
59
|
-
|
|
60
|
-
let digest = SHA256.hash(data: data)
|
|
29
|
+
Every hash type conforms to `HashFunction`.
|
|
61
30
|
|
|
62
|
-
|
|
63
|
-
|
|
31
|
+
| Family | Types | Availability |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| SHA-2 | `SHA256`, `SHA384`, `SHA512` | iOS 13+, macOS 10.15+ |
|
|
34
|
+
| SHA-3 | `SHA3_256`, `SHA3_384`, `SHA3_512` | iOS 26+, macOS 26+ (added alongside the WWDC25 release, mirrored in swift-crypto PR #397) |
|
|
64
35
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
```
|
|
36
|
+
The open-source swift-crypto package (`import Crypto`) ships SHA-3 for older
|
|
37
|
+
deployment targets, but that does not make it available in Apple's framework:
|
|
38
|
+
`import CryptoKit` still requires the iOS 26 SDK and runtime. Check which module
|
|
39
|
+
you import before assuming availability.
|
|
71
40
|
|
|
72
|
-
|
|
41
|
+
A digest value is a `Sequence` of `UInt8`, conforms to `ContiguousBytes`,
|
|
42
|
+
`Hashable` and `CustomStringConvertible`, and its `==` compares in constant time.
|
|
73
43
|
|
|
74
|
-
|
|
44
|
+
```swift
|
|
45
|
+
import CryptoKit
|
|
46
|
+
import Foundation
|
|
75
47
|
|
|
76
|
-
|
|
48
|
+
let manifest = Data("release-manifest-v4".utf8)
|
|
49
|
+
let fingerprint = SHA256.hash(data: manifest)
|
|
50
|
+
let hexFingerprint = fingerprint.map { String(format: "%02x", $0) }.joined()
|
|
77
51
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
let fileHandle = try FileHandle(forReadingFrom: fileURL)
|
|
81
|
-
while autoreleasepool(invoking: {
|
|
82
|
-
let chunk = fileHandle.readData(ofLength: 1_048_576) // 1 MB chunks
|
|
83
|
-
guard !chunk.isEmpty else { return false }
|
|
84
|
-
hasher.update(data: chunk)
|
|
85
|
-
return true
|
|
86
|
-
}) {}
|
|
87
|
-
let digest = hasher.finalize()
|
|
52
|
+
let expected = SHA256.hash(data: Data("release-manifest-v4".utf8))
|
|
53
|
+
let matches = fingerprint == expected
|
|
88
54
|
```
|
|
89
55
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
### SHA-3 with Availability Check
|
|
56
|
+
Build hex strings yourself as above. Do not rely on `.description`; Apple states
|
|
57
|
+
its format is not stable.
|
|
93
58
|
|
|
94
|
-
|
|
59
|
+
Large inputs should be streamed so the whole file never sits in memory. Read in
|
|
60
|
+
chunks (1 MB works well) and wrap each read in `autoreleasepool` so the
|
|
61
|
+
temporary `Data` buffers are released per iteration:
|
|
95
62
|
|
|
96
63
|
```swift
|
|
97
|
-
func
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
64
|
+
func digestOfFile(at url: URL) throws -> SHA256.Digest {
|
|
65
|
+
let handle = try FileHandle(forReadingFrom: url)
|
|
66
|
+
defer { try? handle.close() }
|
|
67
|
+
var hasher = SHA256()
|
|
68
|
+
var reachedEnd = false
|
|
69
|
+
while !reachedEnd {
|
|
70
|
+
try autoreleasepool {
|
|
71
|
+
let block = try handle.read(upToCount: 1_048_576) ?? Data()
|
|
72
|
+
if block.isEmpty {
|
|
73
|
+
reachedEnd = true
|
|
74
|
+
} else {
|
|
75
|
+
hasher.update(data: block)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
104
78
|
}
|
|
79
|
+
return hasher.finalize()
|
|
105
80
|
}
|
|
106
81
|
```
|
|
107
82
|
|
|
108
|
-
SHA-3 uses
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
**❌ Wrong: Using MD5 or SHA-1 for any security purpose**
|
|
83
|
+
SHA-3 uses the Keccak sponge construction rather than SHA-2's Merkle-Damgard
|
|
84
|
+
chaining, but the Swift API is the same. Reach for it when a compliance profile
|
|
85
|
+
names it or you want algorithm diversity. Gate it and fall back:
|
|
113
86
|
|
|
114
87
|
```swift
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
88
|
+
func contentTag(for payload: Data) -> Data {
|
|
89
|
+
if #available(iOS 26.0, macOS 26.0, *) {
|
|
90
|
+
return Data(SHA3_256.hash(data: payload))
|
|
91
|
+
}
|
|
92
|
+
return Data(SHA256.hash(data: payload))
|
|
93
|
+
}
|
|
120
94
|
```
|
|
121
95
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
96
|
+
Broken hashes live under `Insecure` on purpose. `Insecure.MD5` collisions take on
|
|
97
|
+
the order of 2^18 operations (seconds on a laptop), and `Insecure.SHA1` fell to a
|
|
98
|
+
practical chosen-prefix collision in 2020 costing roughly $45,000 of GPU time.
|
|
99
|
+
Neither belongs anywhere security matters; SHA-256 is the floor.
|
|
125
100
|
|
|
126
|
-
| Algorithm |
|
|
127
|
-
|
|
|
128
|
-
| SHA-256
|
|
129
|
-
| SHA-384
|
|
130
|
-
| SHA-512
|
|
131
|
-
| SHA3-256
|
|
132
|
-
| SHA3-384
|
|
133
|
-
| SHA3-512
|
|
134
|
-
| MD5
|
|
135
|
-
| SHA-1 | `Insecure.SHA1` | iOS 13+ | **Broken** | Legacy non-security checksums only |
|
|
101
|
+
| Algorithm | Pick it for |
|
|
102
|
+
| --- | --- |
|
|
103
|
+
| SHA-256 | Default: integrity checks, signatures, HMAC |
|
|
104
|
+
| SHA-384 | Certificate chains, extra margin |
|
|
105
|
+
| SHA-512 | Large data, faster on 64-bit cores |
|
|
106
|
+
| SHA3-256 | Compliance requirements naming SHA-3 |
|
|
107
|
+
| SHA3-384 | Longer-term margin |
|
|
108
|
+
| SHA3-512 | Highest-assurance profiles |
|
|
109
|
+
| MD5, SHA-1 | Broken. Only for legacy, non-security checksums |
|
|
136
110
|
|
|
137
|
-
|
|
111
|
+
## HMAC
|
|
138
112
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
HMAC combines a hash function with a secret key to produce an authentication code. CryptoKit's `HMAC<H>` is generic over any `HashFunction`, provides constant-time verification, and supports both one-shot and streaming patterns.
|
|
142
|
-
|
|
143
|
-
**✅ Correct: HMAC generation and verification**
|
|
113
|
+
`HMAC<H>` is generic over any `HashFunction`, supports one-shot and incremental
|
|
114
|
+
use, and verifies in constant time.
|
|
144
115
|
|
|
145
116
|
```swift
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
let key = SymmetricKey(size: .bits256)
|
|
149
|
-
let message = "Transfer $500 to account 12345".data(using: .utf8)!
|
|
117
|
+
let webhookKey = SymmetricKey(size: .bits256)
|
|
118
|
+
let body = Data(#"{"event":"invoice.paid"}"#.utf8)
|
|
150
119
|
|
|
151
|
-
|
|
152
|
-
let
|
|
120
|
+
let tag = HMAC<SHA256>.authenticationCode(for: body, using: webhookKey)
|
|
121
|
+
let tagBytes = Data(tag)
|
|
153
122
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
mac, authenticating: message, using: key
|
|
123
|
+
let authentic = HMAC<SHA256>.isValidAuthenticationCode(
|
|
124
|
+
tagBytes, authenticating: body, using: webhookKey
|
|
157
125
|
)
|
|
158
|
-
|
|
159
|
-
// Serialize MAC for transmission
|
|
160
|
-
let macData = Data(mac)
|
|
161
126
|
```
|
|
162
127
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
The return type `HMAC<SHA256>.MAC` (alias for `HashedAuthenticationCode<SHA256>`) conforms to `ContiguousBytes`, `Sequence`, `Hashable`, and `CustomStringConvertible`.
|
|
166
|
-
|
|
167
|
-
**Common HMAC use cases:** API request signing, webhook payload verification, data integrity in transit, token-based authentication schemes. HMAC proves authenticity and integrity - not confidentiality. For encryption, use AES-GCM or ChaChaPoly below.
|
|
168
|
-
|
|
169
|
-
---
|
|
128
|
+
Verification must go through `isValidAuthenticationCode(_:authenticating:using:)`,
|
|
129
|
+
which compares safely in constant time. Never compare MAC bytes with `==` on `Data`.
|
|
170
130
|
|
|
171
|
-
|
|
131
|
+
`HMAC<SHA256>.MAC` is a typealias for `HashedAuthenticationCode<SHA256>`, which is
|
|
132
|
+
`ContiguousBytes`, `Sequence`, `Hashable` and `CustomStringConvertible`.
|
|
172
133
|
|
|
173
|
-
|
|
134
|
+
Typical uses: signing API requests, verifying webhooks, integrity of data in transit,
|
|
135
|
+
token schemes. HMAC proves who produced the data and that it was not changed; it
|
|
136
|
+
does not hide the data.
|
|
174
137
|
|
|
175
|
-
|
|
138
|
+
## AES-GCM
|
|
176
139
|
|
|
177
|
-
|
|
140
|
+
AES-GCM is the cipher to use by default. It is an AEAD mode: one `seal()` call gives
|
|
141
|
+
confidentiality, integrity and authenticity, replacing the old AES-CBC plus
|
|
142
|
+
separate HMAC construction.
|
|
178
143
|
|
|
179
144
|
```swift
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
let key = SymmetricKey(size: .bits256)
|
|
183
|
-
let plaintext = "Sensitive data".data(using: .utf8)!
|
|
145
|
+
let vaultKey = SymmetricKey(size: .bits256)
|
|
146
|
+
let note = Data("door code 4411".utf8)
|
|
184
147
|
|
|
185
|
-
|
|
186
|
-
let
|
|
187
|
-
|
|
188
|
-
// Serialize for storage/transmission: nonce(12) || ciphertext || tag(16)
|
|
189
|
-
guard let combined = sealedBox.combined else {
|
|
190
|
-
fatalError("Combined representation unavailable (non-standard nonce size)")
|
|
191
|
-
}
|
|
148
|
+
let box = try AES.GCM.seal(note, using: vaultKey)
|
|
149
|
+
guard let stored = box.combined else { throw CryptoKitError.incorrectParameterSize }
|
|
192
150
|
|
|
193
|
-
|
|
194
|
-
let
|
|
195
|
-
let decrypted = try AES.GCM.open(restoredBox, using: key)
|
|
151
|
+
let reopened = try AES.GCM.SealedBox(combined: stored)
|
|
152
|
+
let recovered = try AES.GCM.open(reopened, using: vaultKey)
|
|
196
153
|
```
|
|
197
154
|
|
|
198
|
-
|
|
155
|
+
`seal` picks a fresh random 12-byte nonce when you do not pass one. A sealed box
|
|
156
|
+
holds three parts: the 12-byte nonce, ciphertext of the same length as the
|
|
157
|
+
plaintext, and a 16-byte tag. `combined` lays them out as nonce, ciphertext, tag.
|
|
199
158
|
|
|
200
|
-
|
|
159
|
+
`AES.GCM.SealedBox.combined` is `Data?`: it is nil when the box was built with a
|
|
160
|
+
non-standard nonce length. `ChaChaPoly.SealedBox.combined` is not optional.
|
|
201
161
|
|
|
202
|
-
|
|
162
|
+
### Additional authenticated data
|
|
203
163
|
|
|
204
|
-
|
|
205
|
-
let metadata = "user:42,action:payment".data(using: .utf8)!
|
|
206
|
-
let sealedBox = try AES.GCM.seal(plaintext, using: key, authenticating: metadata)
|
|
164
|
+
Pass context that must match on decryption but need not be secret:
|
|
207
165
|
|
|
208
|
-
|
|
209
|
-
let
|
|
166
|
+
```swift
|
|
167
|
+
let context = Data("owner=81923|doc=receipt-77|v=2".utf8)
|
|
168
|
+
let bound = try AES.GCM.seal(note, using: vaultKey, authenticating: context)
|
|
169
|
+
let plain = try AES.GCM.open(bound, using: vaultKey, authenticating: context)
|
|
210
170
|
```
|
|
211
171
|
|
|
212
|
-
|
|
172
|
+
AAD is authenticated but not encrypted. Binding a user id, resource id, version or
|
|
173
|
+
timestamp stops an attacker from moving a valid ciphertext into another record. A
|
|
174
|
+
mismatch makes `open` throw `CryptoKitError.authenticationFailure`.
|
|
213
175
|
|
|
214
|
-
###
|
|
176
|
+
### Nonce reuse
|
|
215
177
|
|
|
216
|
-
|
|
178
|
+
This is the classic mistake:
|
|
217
179
|
|
|
218
180
|
```swift
|
|
219
|
-
//
|
|
220
|
-
let
|
|
221
|
-
let
|
|
222
|
-
let
|
|
223
|
-
// With C1 and C2, attacker computes: C1 ⊕ C2 = P1 ⊕ P2
|
|
181
|
+
// Broken: the same fixed nonce for every message
|
|
182
|
+
let fixedNonce = try AES.GCM.Nonce(data: Data(count: 12))
|
|
183
|
+
let first = try AES.GCM.seal(Data("alpha".utf8), using: vaultKey, nonce: fixedNonce)
|
|
184
|
+
let second = try AES.GCM.seal(Data("bravo".utf8), using: vaultKey, nonce: fixedNonce)
|
|
224
185
|
```
|
|
225
186
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
187
|
+
Two messages under one key and one nonce share a keystream, so XOR of the
|
|
188
|
+
ciphertexts equals XOR of the plaintexts. Worse, the authentication subkey
|
|
189
|
+
H = AES_k(0^128) can be recovered by root finding (Cantor-Zassenhaus), after which
|
|
190
|
+
tags can be forged for any message. This is the "forbidden attack" described by
|
|
191
|
+
Joux in 2006, and a 2016 USENIX WOOT study found 184 HTTPS servers, some at
|
|
192
|
+
financial institutions, reusing GCM nonces in the wild.
|
|
232
193
|
|
|
233
|
-
|
|
194
|
+
The fix is to omit `nonce:`. With random 12-byte nonces the chance of any
|
|
195
|
+
collision stays below 2^-32 after 2^32 messages under one key. Supply an explicit
|
|
196
|
+
nonce only when an external protocol defines it, and then follow that protocol's
|
|
197
|
+
counter rules exactly.
|
|
234
198
|
|
|
235
|
-
|
|
199
|
+
## ChaChaPoly
|
|
236
200
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
ChaCha20-Poly1305 provides equivalent AEAD security with an identical API surface. It exists primarily for **software-only environments** where it delivers constant-time execution without hardware acceleration, eliminating cache-timing side channels that plague software AES implementations.
|
|
240
|
-
|
|
241
|
-
**✅ Correct: ChaChaPoly encryption**
|
|
201
|
+
ChaCha20-Poly1305 gives the same AEAD guarantees with the same API shape; switching
|
|
202
|
+
is a type-name change.
|
|
242
203
|
|
|
243
204
|
```swift
|
|
244
|
-
let
|
|
245
|
-
let
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
let combined = sealedBox.combined
|
|
249
|
-
|
|
250
|
-
// Decrypt
|
|
251
|
-
let restoredBox = try ChaChaPoly.SealedBox(combined: combined)
|
|
252
|
-
let decrypted = try ChaChaPoly.open(restoredBox, using: key)
|
|
205
|
+
let sealed = try ChaChaPoly.seal(note, using: vaultKey)
|
|
206
|
+
let wire = sealed.combined
|
|
207
|
+
let parsed = try ChaChaPoly.SealedBox(combined: wire)
|
|
208
|
+
let opened = try ChaChaPoly.open(parsed, using: vaultKey)
|
|
253
209
|
```
|
|
254
210
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
On all Apple Silicon (A-series since A7, all M-series), **AES-GCM is significantly faster** due to dedicated hardware AES instructions:
|
|
260
|
-
|
|
261
|
-
| Metric | AES-256-GCM | ChaChaPoly | Source |
|
|
262
|
-
| ------------------- | ------------------------------------------------------------- | ----------- | ----------------------- |
|
|
263
|
-
| Throughput (M2 Pro) | ~3-4 GB/s | ~1.5-2 GB/s | OpenSSL benchmarks |
|
|
264
|
-
| Relative speed | 134%-236% faster | Baseline | Ashvardanian (2025) |
|
|
265
|
-
| Apple internal use | Keychain encryption, file Data Protection, Watch↔iPhone comms | - | Platform Security Guide |
|
|
266
|
-
|
|
267
|
-
**Default to AES-GCM on Apple hardware.** Choose ChaChaPoly when: targeting platforms without hardware AES acceleration, requiring guaranteed constant-time behavior independent of hardware, or interoperating with ChaCha20-based protocols (WireGuard, some TLS configurations).
|
|
211
|
+
Every Apple chip since A7, and every M-series chip, has AES instructions. Published
|
|
212
|
+
2025 benchmarks on an M2 Pro (OpenSSL) put AES-256-GCM at roughly 3-4 GB/s and
|
|
213
|
+
ChaChaPoly at roughly 1.5-2 GB/s, AES being 134% to 236% faster depending on size.
|
|
214
|
+
Apple's own keychain, file Data Protection and Watch-to-iPhone links all use AES.
|
|
268
215
|
|
|
269
|
-
|
|
216
|
+
So: AES-GCM on Apple hardware. Choose ChaChaPoly when there is no hardware AES,
|
|
217
|
+
when you need guaranteed constant-time software (no table lookups, no cache-timing
|
|
218
|
+
side channel), or when a protocol such as WireGuard or a ChaCha20 TLS suite
|
|
219
|
+
specifies it.
|
|
270
220
|
|
|
271
|
-
Neither
|
|
221
|
+
Neither cipher streams. For large files, either split into chunks with a unique
|
|
222
|
+
nonce per chunk and the chunk index (monotonic) in the AAD so chunks cannot be
|
|
223
|
+
reordered or dropped silently, or lean on file Data Protection, which the hardware
|
|
224
|
+
engine applies with AES-XTS.
|
|
272
225
|
|
|
273
|
-
|
|
226
|
+
## SymmetricKey
|
|
274
227
|
|
|
275
|
-
|
|
228
|
+
`SymmetricKey` zeroes its memory when deallocated (WWDC 2019 session 709), exposes
|
|
229
|
+
no `Data` property (only `withUnsafeBytes`), and validates its size.
|
|
276
230
|
|
|
277
|
-
`SymmetricKey
|
|
231
|
+
`SymmetricKey(size: .bits256)` is 32 random bytes; `.bits128` and `.bits192` also
|
|
232
|
+
exist. Use 256 bits: Grover's algorithm halves effective strength, leaving AES-256
|
|
233
|
+
at 128 bits but AES-128 at 64 bits, which is not enough.
|
|
278
234
|
|
|
279
|
-
###
|
|
280
|
-
|
|
281
|
-
**✅ Correct: Cryptographically random key**
|
|
282
|
-
|
|
283
|
-
```swift
|
|
284
|
-
let key = SymmetricKey(size: .bits256) // 32 bytes, cryptographically random
|
|
285
|
-
// Also available: .bits128, .bits192
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
For quantum resilience, prefer `.bits256`. Grover's algorithm halves effective symmetric key strength - AES-256 retains 128-bit security against quantum adversaries, while AES-128 drops to 64-bit (insufficient).
|
|
289
|
-
|
|
290
|
-
### Password-Based Key Derivation (PBKDF2 + HKDF)
|
|
291
|
-
|
|
292
|
-
**❌ Wrong: Raw password as key material**
|
|
235
|
+
### Passwords are not keys
|
|
293
236
|
|
|
294
237
|
```swift
|
|
295
|
-
//
|
|
296
|
-
let
|
|
297
|
-
// Trivially brute-forceable via dictionary attack - no computational cost barrier, no salt
|
|
238
|
+
// Broken: a password is low-entropy, unsalted and free to guess
|
|
239
|
+
let weak = SymmetricKey(data: Data("summer2026".utf8))
|
|
298
240
|
```
|
|
299
241
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
242
|
+
A human password carries around 20-40 bits of entropy, and this has no salt and no
|
|
243
|
+
work factor. CryptoKit provides HKDF but not PBKDF2, so stretch the password with
|
|
244
|
+
CommonCrypto first, then optionally split it with HKDF:
|
|
303
245
|
|
|
304
246
|
```swift
|
|
305
247
|
import CommonCrypto
|
|
306
|
-
import CryptoKit
|
|
307
|
-
|
|
308
|
-
// Step 1: PBKDF2 stretches the low-entropy password
|
|
309
|
-
let password = "MyPassword123"
|
|
310
|
-
let salt = Data((0..<32).map { _ in UInt8.random(in: 0...255) })
|
|
311
|
-
var derivedBytes = [UInt8](repeating: 0, count: 32)
|
|
312
|
-
|
|
313
|
-
CCKeyDerivationPBKDF(
|
|
314
|
-
CCPBKDFAlgorithm(kCCPBKDF2),
|
|
315
|
-
password, password.utf8.count,
|
|
316
|
-
Array(salt), salt.count,
|
|
317
|
-
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256),
|
|
318
|
-
600_000, // OWASP 2023 recommended minimum for HMAC-SHA256
|
|
319
|
-
&derivedBytes, derivedBytes.count
|
|
320
|
-
)
|
|
321
248
|
|
|
322
|
-
|
|
323
|
-
let
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
)
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
**Critical distinction:** HKDF is designed for already-high-entropy input (shared secrets, master keys). It does **not** add computational cost. Never use HKDF alone for passwords - always PBKDF2 first.
|
|
339
|
-
|
|
340
|
-
### HKDF for High-Entropy Key Derivation
|
|
341
|
-
|
|
342
|
-
**✅ Correct: Deriving subkeys from a high-entropy master key**
|
|
343
|
-
|
|
344
|
-
```swift
|
|
345
|
-
// When input is already high-entropy (e.g., ECDH shared secret)
|
|
346
|
-
let inputKey = SymmetricKey(size: .bits256)
|
|
347
|
-
let derivedKey = HKDF<SHA256>.deriveKey(
|
|
348
|
-
inputKeyMaterial: inputKey,
|
|
349
|
-
salt: Data("app-specific-salt".utf8),
|
|
350
|
-
info: Data("aes-encryption-key-v1".utf8),
|
|
351
|
-
outputByteCount: 32
|
|
352
|
-
)
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
HKDF follows RFC 5869 and supports one-shot `deriveKey()` and two-phase `extract()` → `expand()`. Use distinct `info` strings for domain separation when deriving multiple subkeys from a single shared secret. Available since iOS 14+.
|
|
356
|
-
|
|
357
|
-
> **API note:** `HKDF.deriveKey()` does not throw - no `try` required despite some code examples showing it.
|
|
358
|
-
|
|
359
|
-
### Key Storage and Hardcoding
|
|
249
|
+
func stretch(passphrase: String, salt: Data, rounds: UInt32 = 600_000) -> SymmetricKey? {
|
|
250
|
+
let secret = Array(passphrase.utf8)
|
|
251
|
+
var output = [UInt8](repeating: 0, count: 32)
|
|
252
|
+
let status = salt.withUnsafeBytes { saltBytes in
|
|
253
|
+
CCKeyDerivationPBKDF(
|
|
254
|
+
CCPBKDFAlgorithm(kCCPBKDF2),
|
|
255
|
+
secret.map { Int8(bitPattern: $0) }, secret.count,
|
|
256
|
+
saltBytes.bindMemory(to: UInt8.self).baseAddress, salt.count,
|
|
257
|
+
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), rounds,
|
|
258
|
+
&output, output.count
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
guard status == kCCSuccess else { return nil }
|
|
262
|
+
return SymmetricKey(data: output)
|
|
263
|
+
}
|
|
360
264
|
|
|
361
|
-
|
|
265
|
+
struct PasswordKeys {
|
|
266
|
+
let salt: Data
|
|
267
|
+
let encryption: SymmetricKey
|
|
268
|
+
let authentication: SymmetricKey
|
|
269
|
+
}
|
|
362
270
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
271
|
+
func passwordKeys(for passphrase: String) -> PasswordKeys? {
|
|
272
|
+
var saltBytes = [UInt8](repeating: 0, count: 32)
|
|
273
|
+
guard SecRandomCopyBytes(kSecRandomDefault, saltBytes.count, &saltBytes) == errSecSuccess else { return nil }
|
|
274
|
+
let salt = Data(saltBytes)
|
|
275
|
+
guard let master = stretch(passphrase: passphrase, salt: salt) else { return nil }
|
|
276
|
+
return PasswordKeys(
|
|
277
|
+
salt: salt,
|
|
278
|
+
encryption: HKDF<SHA256>.deriveKey(
|
|
279
|
+
inputKeyMaterial: master, info: Data("encryption".utf8), outputByteCount: 32
|
|
280
|
+
),
|
|
281
|
+
authentication: HKDF<SHA256>.deriveKey(
|
|
282
|
+
inputKeyMaterial: master, info: Data("authentication".utf8), outputByteCount: 32
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
}
|
|
366
286
|
```
|
|
367
287
|
|
|
368
|
-
|
|
288
|
+
Keep the salt with the ciphertext: it is not secret, and deriving the same keys
|
|
289
|
+
again later needs it.
|
|
369
290
|
|
|
370
|
-
|
|
291
|
+
The OWASP Password Storage Cheat Sheet sets 600,000 PBKDF2-HMAC-SHA256 iterations
|
|
292
|
+
as the minimum; go lower only for a documented legacy interop requirement. Salts
|
|
293
|
+
must be random and at least 16 bytes (32 above).
|
|
371
294
|
|
|
372
|
-
|
|
295
|
+
For random bytes, `SecRandomCopyBytes` and `SymmetricKey(size:)` are the APIs to
|
|
296
|
+
use. On Apple platforms `arc4random_buf` and `SystemRandomNumberGenerator` also draw
|
|
297
|
+
from the kernel CSPRNG and are cryptographically secure, so they are not a
|
|
298
|
+
vulnerability; still prefer the Security and CryptoKit calls for key material so the
|
|
299
|
+
intent is explicit and the status is checked. What is never acceptable is a seeded
|
|
300
|
+
or non-cryptographic generator (`rand()`, `random()`, `srand48`, a custom LCG) or
|
|
301
|
+
`arc4random() % n` where modulo bias matters (use `arc4random_uniform`).
|
|
373
302
|
|
|
374
|
-
|
|
303
|
+
### HKDF
|
|
375
304
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
### Hashing: CC_SHA256 → SHA256
|
|
305
|
+
HKDF (RFC 5869) expands high-entropy input into subkeys. It adds no work factor, so
|
|
306
|
+
it is never enough on its own for a password.
|
|
379
307
|
|
|
380
308
|
```swift
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
// ✅ CryptoKit - one line, type-safe
|
|
389
|
-
import CryptoKit
|
|
390
|
-
let digest = SHA256.hash(data: data)
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
### Encryption: CCCrypt (AES-CBC) → AES.GCM
|
|
394
|
-
|
|
395
|
-
```swift
|
|
396
|
-
// ❌ Legacy CommonCrypto - AES-CBC, unauthenticated, manual IV, buffer math
|
|
397
|
-
import CommonCrypto
|
|
398
|
-
var outputBuffer = [UInt8](repeating: 0, count: data.count + kCCBlockSizeAES128)
|
|
399
|
-
var numBytesEncrypted = 0
|
|
400
|
-
let status = CCCrypt(
|
|
401
|
-
CCOperation(kCCEncrypt), CCAlgorithm(kCCAlgorithmAES),
|
|
402
|
-
CCOptions(kCCOptionPKCS7Padding),
|
|
403
|
-
keyBytes, kCCKeySizeAES256, ivBytes,
|
|
404
|
-
dataBytes, data.count,
|
|
405
|
-
&outputBuffer, outputBuffer.count, &numBytesEncrypted
|
|
309
|
+
let rootKey = SymmetricKey(size: .bits256)
|
|
310
|
+
let sessionKey = HKDF<SHA256>.deriveKey(
|
|
311
|
+
inputKeyMaterial: rootKey,
|
|
312
|
+
salt: Data("session-salt-01".utf8),
|
|
313
|
+
info: Data("chat-session/aes".utf8),
|
|
314
|
+
outputByteCount: 32
|
|
406
315
|
)
|
|
407
|
-
// ⚠️ Still need to add HMAC separately for integrity!
|
|
408
|
-
|
|
409
|
-
// ✅ CryptoKit - one line, authenticated, automatic nonce
|
|
410
|
-
import CryptoKit
|
|
411
|
-
let sealedBox = try AES.GCM.seal(data, using: key)
|
|
412
316
|
```
|
|
413
317
|
|
|
414
|
-
|
|
318
|
+
Use one-shot `deriveKey` or the two-step `extract` then `expand`. Give each subkey
|
|
319
|
+
its own `info` string for domain separation. The standalone `HKDF` type is iOS 14+.
|
|
320
|
+
`deriveKey` does not throw, so there is no `try`.
|
|
415
321
|
|
|
416
|
-
###
|
|
322
|
+
### Where keys live
|
|
417
323
|
|
|
418
324
|
```swift
|
|
419
|
-
//
|
|
420
|
-
|
|
421
|
-
var hmac = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
|
|
422
|
-
CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256),
|
|
423
|
-
keyBytes, keyData.count, dataBytes, data.count, &hmac)
|
|
424
|
-
|
|
425
|
-
// ✅ CryptoKit - generic, type-safe, constant-time verification built in
|
|
426
|
-
import CryptoKit
|
|
427
|
-
let mac = HMAC<SHA256>.authenticationCode(for: data, using: key)
|
|
428
|
-
let valid = HMAC<SHA256>.isValidAuthenticationCode(mac, authenticating: data, using: key)
|
|
325
|
+
// Broken: extractable from the binary with `strings`
|
|
326
|
+
let embedded = SymmetricKey(data: Data(base64Encoded: "q83vEjRWeJq8...")!)
|
|
429
327
|
```
|
|
430
328
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
-
|
|
501
|
-
-
|
|
502
|
-
-
|
|
503
|
-
-
|
|
504
|
-
-
|
|
505
|
-
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
329
|
+
A 2025 industry study found hardcoded secrets in 48% of mobile apps, and any
|
|
330
|
+
disassembler recovers them from the shipped binary. Keys instead come from one of:
|
|
331
|
+
the Keychain with `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`, derivation from
|
|
332
|
+
something the user knows, or a secure server.
|
|
333
|
+
|
|
334
|
+
A `SymmetricKey` lives in process memory. It is not in the Secure Enclave; only the
|
|
335
|
+
asymmetric `SecureEnclave.P256` (and on iOS 26 the post-quantum SE types) are
|
|
336
|
+
hardware-backed (see [secure-enclave.md](secure-enclave.md)). Persist symmetric keys
|
|
337
|
+
only in the Keychain, never in UserDefaults or plain files
|
|
338
|
+
(see [credential-storage-patterns.md](credential-storage-patterns.md)).
|
|
339
|
+
|
|
340
|
+
## Moving off CommonCrypto
|
|
341
|
+
|
|
342
|
+
CommonCrypto means manual buffers, unsafe pointers and no authenticated encryption.
|
|
343
|
+
|
|
344
|
+
| CommonCrypto | CryptoKit |
|
|
345
|
+
| --- | --- |
|
|
346
|
+
| `CC_SHA256` with `withUnsafeBytes` and `CC_SHA256_DIGEST_LENGTH` | `SHA256.hash(data:)` |
|
|
347
|
+
| `CCCrypt` AES-CBC, `kCCOptionPKCS7Padding`, `kCCKeySizeAES256`, manual IV, output buffer sized with `kCCBlockSizeAES128` | `AES.GCM.seal(_:using:)` |
|
|
348
|
+
| `CCHmac(kCCHmacAlgSHA256, ...)` | `HMAC<SHA256>.authenticationCode` and `isValidAuthenticationCode` |
|
|
349
|
+
|
|
350
|
+
CBC without encrypt-then-MAC is exposed to padding-oracle attacks and accepts
|
|
351
|
+
tampered ciphertext silently. GCM's `open` throws
|
|
352
|
+
`CryptoKitError.authenticationFailure` if any byte changed.
|
|
353
|
+
|
|
354
|
+
Keep CommonCrypto only for PBKDF2 (`CCKeyDerivationPBKDF`), interop with an existing
|
|
355
|
+
AES-CBC format, and AES-ECB, which is almost never the right choice.
|
|
356
|
+
|
|
357
|
+
## Frequent mistakes
|
|
358
|
+
|
|
359
|
+
1. Writing CommonCrypto for new code when the target is iOS 13+.
|
|
360
|
+
2. Fixed, zero-filled or reused nonces. Leave `nonce:` out.
|
|
361
|
+
3. AES-CBC with no MAC.
|
|
362
|
+
4. Turning a password straight into a `SymmetricKey`. Use PBKDF2 with at least
|
|
363
|
+
600,000 iterations, then HKDF if subkeys are needed.
|
|
364
|
+
5. MD5 or SHA-1 "just for a checksum". SHA-256 is just as fast in practice.
|
|
365
|
+
6. Hand-concatenating nonce, ciphertext and tag. Use `SealedBox.combined` and
|
|
366
|
+
`SealedBox(combined:)`.
|
|
367
|
+
|
|
368
|
+
## Post-quantum outlook
|
|
369
|
+
|
|
370
|
+
WWDC 2025 session 314 introduced ML-KEM and ML-DSA for the asymmetric side (see
|
|
371
|
+
[cryptokit-public-key.md](cryptokit-public-key.md)). Symmetric primitives survive a
|
|
372
|
+
quantum adversary with roughly half their bit strength, so use
|
|
373
|
+
`SymmetricKey(size: .bits256)` and nothing smaller. iOS 26 also turns on
|
|
374
|
+
quantum-secure TLS 1.3 key exchange by default for `URLSession` and
|
|
375
|
+
Network.framework.
|
|
376
|
+
|
|
377
|
+
Under the hood CryptoKit calls corecrypto, which carries FIPS 140-2 / 140-3
|
|
378
|
+
validation and hand-tuned assembly per CPU microarchitecture, and the hardware
|
|
379
|
+
engine in the storage DMA path encrypts with AES-256 inline at line speed.
|
|
380
|
+
|
|
381
|
+
## OWASP mapping
|
|
382
|
+
|
|
383
|
+
This material addresses OWASP Mobile Top 10 (2024) M10, Insufficient Cryptography.
|
|
384
|
+
|
|
385
|
+
- MASTG tests: MASTG-TEST-0061 (algorithm configuration), 0062 (key management),
|
|
386
|
+
0209 (key sizes), 0210 (broken symmetric algorithms), 0211 (broken hashing),
|
|
387
|
+
0213 (hardcoded keys), 0317 (broken modes).
|
|
388
|
+
- MASTG knowledge: MASTG-KNOW-0066 (CryptoKit), MASTG-KNOW-0067 (CommonCrypto).
|
|
389
|
+
- MASWE: 0010 (key derivation), 0013 (hardcoded keys), 0020 (improper encryption),
|
|
390
|
+
0021 (improper hashing), 0022 (predictable IVs).
|
|
391
|
+
|
|
392
|
+
See also [common-anti-patterns.md](common-anti-patterns.md) (#6 nonce reuse, #7 weak
|
|
393
|
+
hashes) and [compliance-owasp-mapping.md](compliance-owasp-mapping.md).
|
|
394
|
+
|
|
395
|
+
## Tests worth writing
|
|
396
|
+
|
|
397
|
+
- Flip one ciphertext byte; `open` throws `CryptoKitError.authenticationFailure`.
|
|
398
|
+
- Open with different AAD; same error.
|
|
399
|
+
- HMAC checked with the wrong key returns false.
|
|
400
|
+
- HMAC over a modified message returns false.
|
|
401
|
+
- Below iOS 26 the SHA-3 path falls back to SHA-256.
|
|
402
|
+
- A `combined` round trip returns the original plaintext.
|
|
403
|
+
- PBKDF2 plus HKDF is deterministic: same password and salt give the same key.
|
|
404
|
+
|
|
405
|
+
CI should flag `Insecure.MD5`, `Insecure.SHA1`, `CCCrypt`, `SymmetricKey(data:` fed
|
|
406
|
+
from a string literal, and base64 blobs that look like embedded keys.
|
|
407
|
+
|
|
408
|
+
## Sources
|
|
409
|
+
|
|
410
|
+
WWDC 2019 session 709 (Cryptography and Your Apps); WWDC 2020 "What's New in
|
|
411
|
+
CryptoKit" (HKDF, iOS 14); WWDC 2025 session 314; the CryptoKit documentation; Apple
|
|
412
|
+
Platform Security Guide; OWASP Mobile Top 10 (2024) M10; OWASP MASTG; RFC 5869;
|
|
413
|
+
Joux 2006 on GCM nonce reuse.
|
|
414
|
+
|
|
415
|
+
## Checklist
|
|
416
|
+
|
|
417
|
+
- [ ] CryptoKit everywhere except PBKDF2.
|
|
418
|
+
- [ ] Nothing weaker than SHA-256; CI flags MD5 and SHA-1.
|
|
419
|
+
- [ ] Only AEAD (AES-GCM or ChaChaPoly); no unauthenticated CBC.
|
|
420
|
+
- [ ] `nonce:` omitted unless a protocol dictates it; never static or zero.
|
|
421
|
+
- [ ] 256-bit keys; no `.bits128` for sensitive data.
|
|
422
|
+
- [ ] Passwords: `CCKeyDerivationPBKDF` with at least 600,000 iterations and a random
|
|
423
|
+
salt of 16 bytes or more, then HKDF if needed.
|
|
424
|
+
- [ ] Serialization through `.combined` and `SealedBox(combined:)`.
|
|
425
|
+
- [ ] Keys in the Keychain as WhenUnlockedThisDeviceOnly; never in source,
|
|
426
|
+
UserDefaults or a plist.
|
|
427
|
+
- [ ] MACs verified with `HMAC.isValidAuthenticationCode()`.
|
|
428
|
+
- [ ] `SHA3_256` behind `#available(iOS 26.0, macOS 26.0, *)` with a SHA-256 fallback.
|
|
429
|
+
- [ ] Metadata (user id, resource id, version) bound through `authenticating:`.
|