@mmerterden/multi-agent-pipeline 20.7.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 +9 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +266 -267
- package/package.json +2 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +68 -68
- package/pipeline/skills/shared/README.md +70 -70
- 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/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/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/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/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/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/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/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/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-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-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-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/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/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/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/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/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/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/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- 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/weatherkit/SKILL.md +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- 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,602 +1,413 @@
|
|
|
1
|
-
# CryptoKit
|
|
1
|
+
# CryptoKit patterns
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Detail that sits behind the main skill: how keys are serialized and stored,
|
|
4
|
+
less common primitives, sizes, compliance and migration.
|
|
5
5
|
|
|
6
6
|
## Contents
|
|
7
7
|
|
|
8
|
-
- [Key
|
|
9
|
-
- [Keychain
|
|
10
|
-
- [AES
|
|
11
|
-
- [HKDF
|
|
12
|
-
- [HPKE
|
|
13
|
-
- [Post-
|
|
14
|
-
- [Insecure
|
|
15
|
-
- [
|
|
16
|
-
- [Signing
|
|
17
|
-
- [
|
|
18
|
-
- [Performance
|
|
19
|
-
- [CommonCrypto
|
|
20
|
-
|
|
21
|
-
## Key
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
let der = privateKey.derRepresentation
|
|
33
|
-
let fromDER = try P256.Signing.PrivateKey(derRepresentation: der)
|
|
34
|
-
|
|
35
|
-
// PEM (text, base64-encoded DER with header/footer)
|
|
36
|
-
let pem = privateKey.pemRepresentation
|
|
37
|
-
let fromPEM = try P256.Signing.PrivateKey(pemRepresentation: pem)
|
|
38
|
-
|
|
39
|
-
// X9.63 (used by SecKey / Keychain interop)
|
|
40
|
-
let x963 = privateKey.x963Representation
|
|
41
|
-
let fromX963 = try P256.Signing.PrivateKey(x963Representation: x963)
|
|
42
|
-
|
|
43
|
-
// Raw (scalar bytes only)
|
|
44
|
-
let raw = privateKey.rawRepresentation
|
|
45
|
-
let fromRaw = try P256.Signing.PrivateKey(rawRepresentation: raw)
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Public key serialization
|
|
49
|
-
|
|
50
|
-
Public keys support the same formats plus compact and compressed
|
|
51
|
-
representations:
|
|
8
|
+
- [Key serialization](#key-serialization)
|
|
9
|
+
- [Keychain storage](#keychain-storage)
|
|
10
|
+
- [AES key wrap](#aes-key-wrap)
|
|
11
|
+
- [HKDF on its own](#hkdf-on-its-own)
|
|
12
|
+
- [HPKE details](#hpke-details)
|
|
13
|
+
- [Post-quantum usage](#post-quantum-usage)
|
|
14
|
+
- [The Insecure namespace](#the-insecure-namespace)
|
|
15
|
+
- [Sealed box layout](#sealed-box-layout)
|
|
16
|
+
- [Signing a digest](#signing-a-digest)
|
|
17
|
+
- [Export compliance](#export-compliance)
|
|
18
|
+
- [Performance](#performance)
|
|
19
|
+
- [Moving from CommonCrypto](#moving-from-commoncrypto)
|
|
20
|
+
|
|
21
|
+
## Key serialization
|
|
22
|
+
|
|
23
|
+
| Key | Property / initializer | Use |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| NIST private (P256/P384/P521) | `derRepresentation` / `init(derRepresentation:)` | exchange with non-Apple systems |
|
|
26
|
+
| | `pemRepresentation` / `init(pemRepresentation:)` | text formats, config files |
|
|
27
|
+
| | `x963Representation` / `init(x963Representation:)` | `SecKey` and Keychain interop |
|
|
28
|
+
| | `rawRepresentation` / `init(rawRepresentation:)` | the private scalar only |
|
|
29
|
+
| NIST public | all of the above, plus `compactRepresentation` (optional, can be `nil`) and `compressedRepresentation` | |
|
|
30
|
+
| Curve25519 private and public | `rawRepresentation` / `init(rawRepresentation:)` only | |
|
|
31
|
+
| ECDSA signature | `derRepresentation` (interop), `rawRepresentation` (r followed by s) | |
|
|
52
32
|
|
|
53
33
|
```swift
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
let
|
|
59
|
-
let
|
|
60
|
-
let
|
|
61
|
-
|
|
34
|
+
import CryptoKit
|
|
35
|
+
import Foundation
|
|
36
|
+
|
|
37
|
+
func roundTrip() throws {
|
|
38
|
+
let issuer = P384.Signing.PrivateKey()
|
|
39
|
+
let asPEM = issuer.publicKey.pemRepresentation
|
|
40
|
+
let reloaded = try P384.Signing.PublicKey(pemRepresentation: asPEM)
|
|
41
|
+
|
|
42
|
+
let sig = try issuer.signature(for: Data("ticket-42".utf8))
|
|
43
|
+
let wire = sig.derRepresentation
|
|
44
|
+
let back = try P384.Signing.ECDSASignature(derRepresentation: wire)
|
|
45
|
+
let compact = try P384.Signing.ECDSASignature(rawRepresentation: sig.rawRepresentation)
|
|
46
|
+
_ = (reloaded.isValidSignature(back, for: Data("ticket-42".utf8)), compact)
|
|
47
|
+
}
|
|
62
48
|
```
|
|
63
49
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
```swift
|
|
67
|
-
let key = Curve25519.Signing.PrivateKey()
|
|
50
|
+
Rule: DER when the other end is not CryptoKit; raw when both ends are CryptoKit
|
|
51
|
+
and size matters.
|
|
68
52
|
|
|
69
|
-
|
|
70
|
-
let raw = key.rawRepresentation
|
|
71
|
-
let restored = try Curve25519.Signing.PrivateKey(rawRepresentation: raw)
|
|
53
|
+
## Keychain storage
|
|
72
54
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
```
|
|
55
|
+
Two strategies, chosen by whether the key has an X9.63 form that `SecKey`
|
|
56
|
+
understands.
|
|
76
57
|
|
|
77
|
-
###
|
|
58
|
+
### NIST keys as SecKey items
|
|
78
59
|
|
|
79
60
|
```swift
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
let derSig = signature.derRepresentation
|
|
84
|
-
|
|
85
|
-
// Raw (r || s concatenation)
|
|
86
|
-
let rawSig = signature.rawRepresentation
|
|
87
|
-
|
|
88
|
-
// Restore
|
|
89
|
-
let fromDER = try P256.Signing.ECDSASignature(derRepresentation: derSig)
|
|
90
|
-
let fromRaw = try P256.Signing.ECDSASignature(rawRepresentation: rawSig)
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
Use DER for interoperability with non-Apple systems. Use raw for compact
|
|
94
|
-
storage where both sides are CryptoKit.
|
|
95
|
-
|
|
96
|
-
## Keychain Storage
|
|
97
|
-
|
|
98
|
-
CryptoKit key types divide into two storage strategies based on whether
|
|
99
|
-
they have a SecKey-compatible representation.
|
|
100
|
-
|
|
101
|
-
### NIST keys via SecKey
|
|
102
|
-
|
|
103
|
-
P256, P384, and P521 private keys can be stored as native Keychain
|
|
104
|
-
elliptic-curve keys using their X9.63 representation.
|
|
61
|
+
import CryptoKit
|
|
62
|
+
import Foundation
|
|
63
|
+
import Security
|
|
105
64
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
init<Bytes>(x963Representation: Bytes) throws where Bytes: ContiguousBytes
|
|
65
|
+
protocol SecKeyStorable {
|
|
66
|
+
init<Bytes: ContiguousBytes>(x963Representation: Bytes) throws
|
|
109
67
|
var x963Representation: Data { get }
|
|
110
68
|
}
|
|
111
69
|
|
|
112
|
-
extension P256.Signing.PrivateKey:
|
|
113
|
-
extension P256.KeyAgreement.PrivateKey:
|
|
114
|
-
extension P384.Signing.PrivateKey:
|
|
115
|
-
extension P384.KeyAgreement.PrivateKey:
|
|
116
|
-
extension P521.Signing.PrivateKey:
|
|
117
|
-
extension P521.KeyAgreement.PrivateKey:
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
Store:
|
|
70
|
+
extension P256.Signing.PrivateKey: SecKeyStorable {}
|
|
71
|
+
extension P256.KeyAgreement.PrivateKey: SecKeyStorable {}
|
|
72
|
+
extension P384.Signing.PrivateKey: SecKeyStorable {}
|
|
73
|
+
extension P384.KeyAgreement.PrivateKey: SecKeyStorable {}
|
|
74
|
+
extension P521.Signing.PrivateKey: SecKeyStorable {}
|
|
75
|
+
extension P521.KeyAgreement.PrivateKey: SecKeyStorable {}
|
|
121
76
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate
|
|
127
|
-
]
|
|
128
|
-
|
|
129
|
-
guard let secKey = SecKeyCreateWithData(
|
|
130
|
-
key.x963Representation as CFData,
|
|
131
|
-
attributes as CFDictionary,
|
|
132
|
-
nil
|
|
133
|
-
) else {
|
|
134
|
-
throw KeyStoreError.unableToCreateSecKey
|
|
135
|
-
}
|
|
77
|
+
enum VaultError: Error {
|
|
78
|
+
case conversion(String)
|
|
79
|
+
case status(OSStatus)
|
|
80
|
+
}
|
|
136
81
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
let status = SecItemAdd(query as CFDictionary, nil)
|
|
146
|
-
guard status == errSecSuccess else {
|
|
147
|
-
throw KeyStoreError.saveFailed(status)
|
|
82
|
+
func storeECKey<K: SecKeyStorable>(_ key: K, label: String) throws {
|
|
83
|
+
let attributes = [
|
|
84
|
+
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
|
|
85
|
+
kSecAttrKeyClass: kSecAttrKeyClassPrivate
|
|
86
|
+
] as [String: Any]
|
|
87
|
+
var cfError: Unmanaged<CFError>?
|
|
88
|
+
guard let keyRef = SecKeyCreateWithData(key.x963Representation as CFData, attributes as CFDictionary, &cfError) else {
|
|
89
|
+
throw VaultError.conversion(cfError.map { String(describing: $0.takeRetainedValue()) } ?? "unknown")
|
|
148
90
|
}
|
|
91
|
+
let item = [
|
|
92
|
+
kSecClass: kSecClassKey,
|
|
93
|
+
kSecAttrApplicationLabel: label,
|
|
94
|
+
kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
|
|
95
|
+
kSecUseDataProtectionKeychain: true,
|
|
96
|
+
kSecValueRef: keyRef
|
|
97
|
+
] as [String: Any]
|
|
98
|
+
let status = SecItemAdd(item as CFDictionary, nil)
|
|
99
|
+
guard status == errSecSuccess else { throw VaultError.status(status) }
|
|
149
100
|
}
|
|
150
|
-
```
|
|
151
101
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
]
|
|
163
|
-
|
|
164
|
-
var item: CFTypeRef?
|
|
165
|
-
switch SecItemCopyMatching(query as CFDictionary, &item) {
|
|
102
|
+
func loadECKey<K: SecKeyStorable>(label: String) throws -> K? {
|
|
103
|
+
let lookup = [
|
|
104
|
+
kSecClass: kSecClassKey,
|
|
105
|
+
kSecAttrApplicationLabel: label,
|
|
106
|
+
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
|
|
107
|
+
kSecUseDataProtectionKeychain: true,
|
|
108
|
+
kSecReturnRef: true
|
|
109
|
+
] as [String: Any]
|
|
110
|
+
var found: CFTypeRef?
|
|
111
|
+
switch SecItemCopyMatching(lookup as CFDictionary, &found) {
|
|
166
112
|
case errSecSuccess:
|
|
167
|
-
let
|
|
168
|
-
var
|
|
169
|
-
guard let
|
|
170
|
-
throw
|
|
113
|
+
guard let ref = found, CFGetTypeID(ref) == SecKeyGetTypeID() else { return nil }
|
|
114
|
+
var failure: Unmanaged<CFError>?
|
|
115
|
+
guard let bytes = SecKeyCopyExternalRepresentation(ref as! SecKey, &failure) as Data? else {
|
|
116
|
+
throw VaultError.conversion("export failed")
|
|
171
117
|
}
|
|
172
|
-
return try
|
|
118
|
+
return try K(x963Representation: bytes)
|
|
173
119
|
case errSecItemNotFound:
|
|
174
120
|
return nil
|
|
175
|
-
case let
|
|
176
|
-
throw
|
|
121
|
+
case let other:
|
|
122
|
+
throw VaultError.status(other)
|
|
177
123
|
}
|
|
178
124
|
}
|
|
179
125
|
```
|
|
180
126
|
|
|
181
|
-
|
|
127
|
+
The P256, P384 and P521 signing and key-agreement private keys all have the
|
|
128
|
+
X9.63 initializer and property, so they conform to the helper protocol with an
|
|
129
|
+
empty extension. Reading asks for `kSecReturnRef`, exports the `SecKey` with
|
|
130
|
+
`SecKeyCopyExternalRepresentation` and rebuilds the CryptoKit key; a missing
|
|
131
|
+
item returns `nil` and every other status throws.
|
|
182
132
|
|
|
183
|
-
|
|
184
|
-
as generic password Keychain items using their raw data.
|
|
133
|
+
### Everything else as generic passwords
|
|
185
134
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes
|
|
189
|
-
var genericKeyRepresentation: SymmetricKey { get }
|
|
190
|
-
}
|
|
135
|
+
Curve25519 keys and `SymmetricKey` have no X9.63 form. Store their raw bytes as
|
|
136
|
+
`kSecClassGenericPassword` items.
|
|
191
137
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
138
|
+
```swift
|
|
139
|
+
import CryptoKit
|
|
140
|
+
import Foundation
|
|
141
|
+
import Security
|
|
196
142
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
143
|
+
protocol RawKeyStorable {
|
|
144
|
+
init<Bytes: ContiguousBytes>(rawKeyBytes: Bytes) throws
|
|
145
|
+
var rawKeyBytes: SymmetricKey { get }
|
|
200
146
|
}
|
|
201
147
|
|
|
202
|
-
extension Curve25519.
|
|
203
|
-
init<
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
var genericKeyRepresentation: SymmetricKey {
|
|
208
|
-
rawRepresentation.withUnsafeBytes { SymmetricKey(data: $0) }
|
|
209
|
-
}
|
|
148
|
+
extension Curve25519.Signing.PrivateKey: RawKeyStorable {
|
|
149
|
+
init<Bytes: ContiguousBytes>(rawKeyBytes: Bytes) throws { try self.init(rawRepresentation: rawKeyBytes) }
|
|
150
|
+
var rawKeyBytes: SymmetricKey { SymmetricKey(data: rawRepresentation) }
|
|
210
151
|
}
|
|
211
152
|
|
|
212
|
-
extension
|
|
213
|
-
init<
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
var genericKeyRepresentation: SymmetricKey { self }
|
|
153
|
+
extension Curve25519.KeyAgreement.PrivateKey: RawKeyStorable {
|
|
154
|
+
init<Bytes: ContiguousBytes>(rawKeyBytes: Bytes) throws { try self.init(rawRepresentation: rawKeyBytes) }
|
|
155
|
+
var rawKeyBytes: SymmetricKey { SymmetricKey(data: rawRepresentation) }
|
|
218
156
|
}
|
|
219
|
-
```
|
|
220
157
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
func storeKey<T: GenericPasswordConvertible>(
|
|
225
|
-
_ key: T, account: String
|
|
226
|
-
) throws {
|
|
227
|
-
try key.genericKeyRepresentation.withUnsafeBytes { keyBytes in
|
|
228
|
-
let query: [String: Any] = [
|
|
229
|
-
kSecClass as String: kSecClassGenericPassword,
|
|
230
|
-
kSecAttrAccount as String: account,
|
|
231
|
-
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked,
|
|
232
|
-
kSecUseDataProtectionKeychain as String: true,
|
|
233
|
-
kSecValueData as String: Data(keyBytes)
|
|
234
|
-
]
|
|
235
|
-
|
|
236
|
-
let status = SecItemAdd(query as CFDictionary, nil)
|
|
237
|
-
guard status == errSecSuccess else {
|
|
238
|
-
throw KeyStoreError.saveFailed(status)
|
|
239
|
-
}
|
|
240
|
-
}
|
|
158
|
+
extension SymmetricKey: RawKeyStorable {
|
|
159
|
+
init<Bytes: ContiguousBytes>(rawKeyBytes: Bytes) throws { self.init(data: rawKeyBytes) }
|
|
160
|
+
var rawKeyBytes: SymmetricKey { self }
|
|
241
161
|
}
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
### Secure Enclave keys in Keychain
|
|
245
|
-
|
|
246
|
-
Secure Enclave keys export an encrypted `dataRepresentation` that only
|
|
247
|
-
the same device's Secure Enclave can restore. Store this blob as a generic
|
|
248
|
-
password:
|
|
249
162
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
163
|
+
func storeRawKey<K: RawKeyStorable>(_ key: K, account: String) throws {
|
|
164
|
+
let bytes = key.rawKeyBytes.withUnsafeBytes { Data($0) }
|
|
165
|
+
let item = [
|
|
166
|
+
kSecClass: kSecClassGenericPassword,
|
|
167
|
+
kSecAttrAccount: account,
|
|
168
|
+
kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
|
|
169
|
+
kSecUseDataProtectionKeychain: true,
|
|
170
|
+
kSecValueData: bytes
|
|
171
|
+
] as [String: Any]
|
|
172
|
+
let status = SecItemAdd(item as CFDictionary, nil)
|
|
173
|
+
if status != errSecSuccess { throw NSError(domain: NSOSStatusErrorDomain, code: Int(status)) }
|
|
259
174
|
}
|
|
260
175
|
```
|
|
261
176
|
|
|
262
|
-
|
|
177
|
+
Secure Enclave keys follow the same generic-password route: store the encrypted
|
|
178
|
+
`dataRepresentation` as the password data and rebuild the key with
|
|
179
|
+
`init(dataRepresentation:)` after reading it back.
|
|
263
180
|
|
|
264
|
-
|
|
265
|
-
symmetric key with another.
|
|
266
|
-
|
|
267
|
-
```swift
|
|
268
|
-
let kek = SymmetricKey(size: .bits256) // Key Encryption Key
|
|
269
|
-
let dek = SymmetricKey(size: .bits256) // Data Encryption Key
|
|
181
|
+
## AES key wrap
|
|
270
182
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
// Unwrap
|
|
275
|
-
let unwrapped = try AES.KeyWrap.unwrap(wrappedData, using: kek)
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
Use key wrapping when transmitting or storing keys encrypted under
|
|
279
|
-
a master key.
|
|
280
|
-
|
|
281
|
-
## HKDF Key Derivation
|
|
282
|
-
|
|
283
|
-
HKDF (RFC 5869) derives cryptographic keys from input key material.
|
|
284
|
-
Available as a standalone operation outside of `SharedSecret`.
|
|
183
|
+
RFC 3394 key wrapping encrypts one key (the data key) under another (the key
|
|
184
|
+
encryption key). Use it to store or send keys protected by a master key.
|
|
285
185
|
|
|
286
186
|
```swift
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
// Derive with salt and info
|
|
290
|
-
let derived = HKDF<SHA256>.deriveKey(
|
|
291
|
-
inputKeyMaterial: inputKey,
|
|
292
|
-
salt: Data("salt".utf8),
|
|
293
|
-
info: Data("my-app-encryption-v1".utf8),
|
|
294
|
-
outputByteCount: 32
|
|
295
|
-
)
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
### Extract-then-expand (two-step)
|
|
187
|
+
import CryptoKit
|
|
188
|
+
import Foundation
|
|
299
189
|
|
|
300
|
-
|
|
190
|
+
func wrapFileKey(_ fileKey: SymmetricKey, under master: SymmetricKey) throws -> Data {
|
|
191
|
+
try AES.KeyWrap.wrap(fileKey, using: master)
|
|
192
|
+
}
|
|
301
193
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
inputKeyMaterial: inputKey,
|
|
306
|
-
salt: Data("salt".utf8)
|
|
307
|
-
)
|
|
308
|
-
|
|
309
|
-
// Expand: derive output key material
|
|
310
|
-
let okm = HKDF<SHA256>.expand(
|
|
311
|
-
pseudoRandomKey: prk,
|
|
312
|
-
info: Data("context".utf8),
|
|
313
|
-
outputByteCount: 32
|
|
314
|
-
)
|
|
194
|
+
func unwrapFileKey(_ wrapped: Data, under master: SymmetricKey) throws -> SymmetricKey {
|
|
195
|
+
try AES.KeyWrap.unwrap(wrapped, using: master)
|
|
196
|
+
}
|
|
315
197
|
```
|
|
316
198
|
|
|
317
|
-
##
|
|
318
|
-
|
|
319
|
-
HPKE (RFC 9180) combines key encapsulation with authenticated encryption
|
|
320
|
-
for public-key encryption workflows. It is available on iOS 17+; the X-Wing
|
|
321
|
-
post-quantum hybrid ciphersuite requires iOS 26+.
|
|
322
|
-
|
|
323
|
-
### Sending an encrypted message
|
|
324
|
-
|
|
325
|
-
```swift
|
|
326
|
-
let recipientKey = P256.KeyAgreement.PrivateKey()
|
|
327
|
-
|
|
328
|
-
var sender = try HPKE.Sender(
|
|
329
|
-
recipientKey: recipientKey.publicKey,
|
|
330
|
-
ciphersuite: .P256_SHA256_AES_GCM_256,
|
|
331
|
-
info: Data("my-protocol-v1".utf8)
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
let ciphertext = try sender.seal(Data("secret message".utf8))
|
|
335
|
-
let encapsulatedKey = sender.encapsulatedKey
|
|
336
|
-
// Send ciphertext + encapsulatedKey to recipient
|
|
337
|
-
```
|
|
199
|
+
## HKDF on its own
|
|
338
200
|
|
|
339
|
-
|
|
201
|
+
HKDF (RFC 5869) is also available directly, for key material that did not come
|
|
202
|
+
from ECDH.
|
|
340
203
|
|
|
341
204
|
```swift
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
205
|
+
import CryptoKit
|
|
206
|
+
import Foundation
|
|
207
|
+
|
|
208
|
+
func deriveSubkeys(from material: SymmetricKey) -> (SymmetricKey, SymmetricKey) {
|
|
209
|
+
let direct = HKDF<SHA256>.deriveKey(
|
|
210
|
+
inputKeyMaterial: material,
|
|
211
|
+
salt: Data("vault-salt".utf8),
|
|
212
|
+
info: Data("vault index key".utf8),
|
|
213
|
+
outputByteCount: 32
|
|
214
|
+
)
|
|
215
|
+
let pseudoKey = HKDF<SHA256>.extract(inputKeyMaterial: material, salt: Data("vault-salt".utf8))
|
|
216
|
+
let expanded = HKDF<SHA256>.expand(pseudoRandomKey: pseudoKey, info: Data("vault blob key".utf8), outputByteCount: 32)
|
|
217
|
+
return (direct, expanded)
|
|
218
|
+
}
|
|
350
219
|
```
|
|
351
220
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
| Ciphersuite | KEM | KDF | AEAD | Availability |
|
|
355
|
-
|---|---|---|---|---|
|
|
356
|
-
| `.P256_SHA256_AES_GCM_256` | P256 | HKDF-SHA256 | AES-GCM-256 | iOS 17+ |
|
|
357
|
-
| `.P384_SHA384_AES_GCM_256` | P384 | HKDF-SHA384 | AES-GCM-256 | iOS 17+ |
|
|
358
|
-
| `.P521_SHA512_AES_GCM_256` | P521 | HKDF-SHA512 | AES-GCM-256 | iOS 17+ |
|
|
359
|
-
| `.Curve25519_SHA256_ChachaPoly` | X25519 | HKDF-SHA256 | ChaCha20Poly1305 | iOS 17+ |
|
|
360
|
-
| `.XWingMLKEM768X25519_SHA256_AES_GCM_256` | X-Wing hybrid | HKDF-SHA256 | AES-GCM-256 | iOS 26+ |
|
|
221
|
+
`deriveKey` does both steps at once; `extract` then `expand` gives the
|
|
222
|
+
pseudorandom key in between.
|
|
361
223
|
|
|
362
|
-
##
|
|
224
|
+
## HPKE details
|
|
363
225
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
deployment target is iOS 26+.
|
|
367
|
-
|
|
368
|
-
### ML-KEM encapsulation
|
|
226
|
+
HPKE is RFC 9180. It is available from iOS 17; the X-Wing hybrid suite needs
|
|
227
|
+
iOS 26.
|
|
369
228
|
|
|
370
229
|
```swift
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
let result = try privateKey.publicKey.encapsulate()
|
|
230
|
+
import CryptoKit
|
|
231
|
+
import Foundation
|
|
374
232
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
233
|
+
func sendToServer(_ report: Data, serverKey: P256.KeyAgreement.PublicKey) throws -> (ciphertext: Data, encapsulated: Data) {
|
|
234
|
+
var hpkeSender = try HPKE.Sender(recipientKey: serverKey, ciphersuite: .P256_SHA256_AES_GCM_256,
|
|
235
|
+
info: Data("crash-report v1".utf8))
|
|
236
|
+
return (try hpkeSender.seal(report), hpkeSender.encapsulatedKey)
|
|
378
237
|
}
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
`encapsulated` is what the sender transmits. `sharedSecret` and the decapsulated
|
|
382
|
-
result are `SymmetricKey` values.
|
|
383
238
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
let privateKey = try MLDSA65.PrivateKey()
|
|
389
|
-
let signature = try privateKey.signature(for: message)
|
|
390
|
-
let isValid = privateKey.publicKey.isValidSignature(signature, for: message)
|
|
239
|
+
func receiveOnServer(_ ciphertext: Data, encapsulated: Data, key: P256.KeyAgreement.PrivateKey) throws -> Data {
|
|
240
|
+
var hpkeRecipient = try HPKE.Recipient(privateKey: key, ciphersuite: .P256_SHA256_AES_GCM_256,
|
|
241
|
+
info: Data("crash-report v1".utf8), encapsulatedKey: encapsulated)
|
|
242
|
+
return try hpkeRecipient.open(ciphertext)
|
|
391
243
|
}
|
|
392
244
|
```
|
|
393
245
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
`
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
246
|
+
| Ciphersuite | Minimum iOS | Key encapsulation | Key derivation | Authenticated cipher |
|
|
247
|
+
|---|---|---|---|---|
|
|
248
|
+
| `.P256_SHA256_AES_GCM_256` | 17 | NIST P-256 | SHA-256 HKDF | 256-bit AES-GCM |
|
|
249
|
+
| `.P384_SHA384_AES_GCM_256` | 17 | NIST P-384 | SHA-384 HKDF | 256-bit AES-GCM |
|
|
250
|
+
| `.P521_SHA512_AES_GCM_256` | 17 | NIST P-521 | SHA-512 HKDF | 256-bit AES-GCM |
|
|
251
|
+
| `.Curve25519_SHA256_ChachaPoly` | 17 | X25519 | SHA-256 HKDF | ChaCha20-Poly1305 |
|
|
252
|
+
| `.XWingMLKEM768X25519_SHA256_AES_GCM_256` | 26 | X-Wing hybrid (ML-KEM-768 with X25519) | SHA-256 HKDF | 256-bit AES-GCM |
|
|
401
253
|
|
|
402
|
-
##
|
|
254
|
+
## Post-quantum usage
|
|
403
255
|
|
|
404
|
-
|
|
256
|
+
Guard ML-KEM, ML-DSA and X-Wing with an iOS 26 availability check unless the
|
|
257
|
+
deployment target is 26 or later.
|
|
405
258
|
|
|
406
259
|
```swift
|
|
407
260
|
import CryptoKit
|
|
261
|
+
import Foundation
|
|
262
|
+
|
|
263
|
+
@available(iOS 26.0, *)
|
|
264
|
+
func kemHandshake() throws -> Bool {
|
|
265
|
+
let receiverKey = try MLKEM768.PrivateKey()
|
|
266
|
+
let result = try receiverKey.publicKey.encapsulate()
|
|
267
|
+
let transmitted = result.encapsulated
|
|
268
|
+
let recovered = try receiverKey.decapsulate(transmitted)
|
|
269
|
+
return recovered == result.sharedSecret
|
|
270
|
+
}
|
|
408
271
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
let
|
|
272
|
+
@available(iOS 26.0, *)
|
|
273
|
+
func quantumSafeSignature(_ firmware: Data) throws -> Bool {
|
|
274
|
+
let signer = try MLDSA65.PrivateKey()
|
|
275
|
+
let sig = try signer.signature(for: firmware)
|
|
276
|
+
return signer.publicKey.isValidSignature(sig, for: firmware)
|
|
277
|
+
}
|
|
412
278
|
```
|
|
413
279
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
- Password hashing
|
|
421
|
-
- Data integrity for security
|
|
422
|
-
- Digital signatures
|
|
423
|
-
- HMAC for authentication
|
|
280
|
+
- The sender runs `encapsulate()` on the receiver's public key and transmits
|
|
281
|
+
`encapsulated`. The receiver calls `decapsulate(_:)`.
|
|
282
|
+
- Both the sender's `sharedSecret` and the receiver's decapsulated value are a
|
|
283
|
+
`SymmetricKey`, and they are equal.
|
|
284
|
+
- The Secure Enclave variants listed in the main skill exist on supported
|
|
285
|
+
hardware.
|
|
424
286
|
|
|
425
|
-
The
|
|
287
|
+
## The Insecure namespace
|
|
426
288
|
|
|
427
|
-
|
|
289
|
+
`Insecure.MD5` and `Insecure.SHA1` live in the `Insecure` enum so that every
|
|
290
|
+
use is visibly flagged at the call site.
|
|
428
291
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
|
432
|
-
|
|
433
|
-
|
|
|
434
|
-
|
|
|
435
|
-
| Tag | 16 bytes | 16 bytes |
|
|
436
|
-
|
|
437
|
-
### Combined representation
|
|
292
|
+
| Fine | Never |
|
|
293
|
+
|---|---|
|
|
294
|
+
| verifying a legacy checksum | hashing passwords |
|
|
295
|
+
| cache keys, ETags, content hashes | integrity that security depends on |
|
|
296
|
+
| a protocol that requires MD5 or SHA-1 | digital signatures |
|
|
297
|
+
| | HMAC authentication |
|
|
438
298
|
|
|
439
299
|
```swift
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
// Combined: nonce (12) + ciphertext (N) + tag (16)
|
|
443
|
-
let combined = sealedBox.combined // Optional for AES-GCM, non-optional for ChaChaPoly
|
|
444
|
-
|
|
445
|
-
// Individual components
|
|
446
|
-
let nonce = sealedBox.nonce
|
|
447
|
-
let ciphertext = sealedBox.ciphertext
|
|
448
|
-
let tag = sealedBox.tag
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
### Reconstructing from components
|
|
452
|
-
|
|
453
|
-
When receiving nonce, ciphertext, and tag separately:
|
|
300
|
+
import CryptoKit
|
|
301
|
+
import Foundation
|
|
454
302
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
ciphertext: ciphertextData,
|
|
459
|
-
tag: tagData
|
|
460
|
-
)
|
|
461
|
-
let plaintext = try AES.GCM.open(box, using: key)
|
|
303
|
+
func legacyETag(for body: Data) -> String {
|
|
304
|
+
Insecure.MD5.hash(data: body).reduce(into: "") { $0 += String(format: "%02x", $1) }
|
|
305
|
+
}
|
|
462
306
|
```
|
|
463
307
|
|
|
464
|
-
|
|
308
|
+
## Sealed box layout
|
|
465
309
|
|
|
466
|
-
|
|
467
|
-
let box = try AES.GCM.SealedBox(combined: combinedData)
|
|
468
|
-
let plaintext = try AES.GCM.open(box, using: key)
|
|
469
|
-
```
|
|
310
|
+
For both AES-GCM and ChaChaPoly:
|
|
470
311
|
|
|
471
|
-
|
|
312
|
+
| Part | Size |
|
|
313
|
+
|---|---|
|
|
314
|
+
| nonce | 12 bytes |
|
|
315
|
+
| ciphertext | same length as the plaintext |
|
|
316
|
+
| tag | 16 bytes |
|
|
472
317
|
|
|
473
|
-
|
|
318
|
+
`combined` is nonce, then ciphertext, then tag. The parts are also exposed as
|
|
319
|
+
`.nonce`, `.ciphertext` and `.tag`.
|
|
474
320
|
|
|
475
321
|
```swift
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
let isValid = publicKey.isValidSignature(signature, for: digest)
|
|
479
|
-
```
|
|
480
|
-
|
|
481
|
-
This avoids hashing the data twice when the digest is already available.
|
|
482
|
-
|
|
483
|
-
## Encryption Export Compliance
|
|
484
|
-
|
|
485
|
-
Apps that use encryption must declare compliance in App Store Connect.
|
|
486
|
-
|
|
487
|
-
### ITSAppUsesNonExemptEncryption
|
|
488
|
-
|
|
489
|
-
Set in Info.plist:
|
|
322
|
+
import CryptoKit
|
|
323
|
+
import Foundation
|
|
490
324
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
325
|
+
func reassemble(nonce: Data, body: Data, tag: Data, key: SymmetricKey) throws -> Data {
|
|
326
|
+
let sealed = try AES.GCM.SealedBox(nonce: AES.GCM.Nonce(data: nonce), ciphertext: body, tag: tag)
|
|
327
|
+
return try AES.GCM.open(sealed, using: key)
|
|
328
|
+
}
|
|
494
329
|
```
|
|
495
330
|
|
|
496
|
-
|
|
497
|
-
- Apple-provided encryption (HTTPS via URLSession, CryptoKit for
|
|
498
|
-
data protection on-device only)
|
|
499
|
-
- Standard authentication (OAuth, SAML, biometrics)
|
|
500
|
-
|
|
501
|
-
Set to `true` if the app:
|
|
502
|
-
- Implements custom encryption protocols
|
|
503
|
-
- Communicates with non-standard encrypted services
|
|
504
|
-
- Encrypts data sent to third-party servers
|
|
505
|
-
|
|
506
|
-
When `true`, an export compliance review or proper classification is
|
|
507
|
-
required. See Apple's [Complying with Encryption Export Regulations](https://sosumi.ai/documentation/security/complying-with-encryption-export-regulations)
|
|
508
|
-
documentation.
|
|
509
|
-
|
|
510
|
-
## Performance Considerations
|
|
511
|
-
|
|
512
|
-
### AES-GCM vs ChaChaPoly
|
|
513
|
-
|
|
514
|
-
On Apple silicon devices, AES-GCM is hardware-accelerated and generally
|
|
515
|
-
faster. ChaChaPoly performs better on devices without AES hardware
|
|
516
|
-
acceleration (rare on modern Apple hardware). For most iOS apps, prefer
|
|
517
|
-
AES-GCM.
|
|
331
|
+
When the parts arrive already concatenated, use `SealedBox(combined:)` instead.
|
|
518
332
|
|
|
519
|
-
|
|
333
|
+
## Signing a digest
|
|
520
334
|
|
|
521
|
-
|
|
522
|
-
|
|
335
|
+
The NIST curves can sign and verify a digest you already computed, which avoids
|
|
336
|
+
hashing the data twice.
|
|
523
337
|
|
|
524
338
|
```swift
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
var hasher = SHA256()
|
|
528
|
-
|
|
529
|
-
while autoreleasepool(invoking: {
|
|
530
|
-
let chunk = handle.readData(ofLength: 1024 * 1024) // 1 MB
|
|
531
|
-
guard !chunk.isEmpty else { return false }
|
|
532
|
-
hasher.update(data: chunk)
|
|
533
|
-
return true
|
|
534
|
-
}) {}
|
|
339
|
+
import CryptoKit
|
|
340
|
+
import Foundation
|
|
535
341
|
|
|
536
|
-
|
|
342
|
+
func signPrehashed(_ file: Data, key: P521.Signing.PrivateKey) throws -> Bool {
|
|
343
|
+
let digest = SHA512.hash(data: file)
|
|
344
|
+
let signature = try key.signature(for: digest)
|
|
345
|
+
return key.publicKey.isValidSignature(signature, for: digest)
|
|
537
346
|
}
|
|
538
347
|
```
|
|
539
348
|
|
|
540
|
-
|
|
349
|
+
## Export compliance
|
|
541
350
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
| `SymmetricKey(size:)` | Very fast (CSPRNG) |
|
|
545
|
-
| `P256.Signing.PrivateKey()` | Fast |
|
|
546
|
-
| `P384.Signing.PrivateKey()` | Moderate |
|
|
547
|
-
| `P521.Signing.PrivateKey()` | Slower |
|
|
548
|
-
| `SecureEnclave.P256.*.PrivateKey()` | Slowest (hardware round-trip) |
|
|
351
|
+
Apps that use encryption declare it in App Store Connect and set the Boolean
|
|
352
|
+
Info.plist key `ITSAppUsesNonExemptEncryption`.
|
|
549
353
|
|
|
550
|
-
|
|
354
|
+
- `false` when the app only uses encryption that Apple's platforms provide (HTTPS
|
|
355
|
+
through `URLSession`, CryptoKit protecting data on the device) plus standard
|
|
356
|
+
authentication such as OAuth, SAML or biometrics.
|
|
357
|
+
- `true` when it implements its own encryption protocol, offers a non-standard
|
|
358
|
+
encrypted service, or encrypts data it sends to third-party servers. In that
|
|
359
|
+
case the app needs an export classification (or a compliance review) first.
|
|
360
|
+
Apple's guide on this lives at
|
|
361
|
+
<https://developer.apple.com/documentation/security/complying-with-encryption-export-regulations>.
|
|
551
362
|
|
|
552
|
-
##
|
|
363
|
+
## Performance
|
|
553
364
|
|
|
554
|
-
|
|
365
|
+
- AES-GCM runs in hardware on Apple silicon and is usually faster.
|
|
366
|
+
ChaChaPoly only wins where AES acceleration is missing, which is rare on
|
|
367
|
+
current Apple devices. Default to AES-GCM.
|
|
368
|
+
- Hash big files in pieces instead of loading them whole:
|
|
555
369
|
|
|
556
370
|
```swift
|
|
557
|
-
// CommonCrypto (old)
|
|
558
|
-
import CommonCrypto
|
|
559
|
-
var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
|
|
560
|
-
data.withUnsafeBytes { CC_SHA256($0.baseAddress, CC_LONG(data.count), &digest) }
|
|
561
|
-
|
|
562
|
-
// CryptoKit (new)
|
|
563
371
|
import CryptoKit
|
|
564
|
-
|
|
565
|
-
```
|
|
372
|
+
import Foundation
|
|
566
373
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
var
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
374
|
+
func sha256OfFile(_ fileURL: URL) throws -> SHA256.Digest {
|
|
375
|
+
let reader = try FileHandle(forReadingFrom: fileURL)
|
|
376
|
+
defer { try? reader.close() }
|
|
377
|
+
var hasher = SHA256()
|
|
378
|
+
var finished = false
|
|
379
|
+
while !finished {
|
|
380
|
+
try autoreleasepool {
|
|
381
|
+
if let piece = try reader.read(upToCount: 1 << 20), !piece.isEmpty {
|
|
382
|
+
hasher.update(data: piece)
|
|
383
|
+
} else {
|
|
384
|
+
finished = true
|
|
385
|
+
}
|
|
386
|
+
}
|
|
578
387
|
}
|
|
388
|
+
return hasher.finalize()
|
|
579
389
|
}
|
|
580
|
-
|
|
581
|
-
// CryptoKit (new)
|
|
582
|
-
let mac = HMAC<SHA256>.authenticationCode(for: data, using: key)
|
|
583
390
|
```
|
|
584
391
|
|
|
585
|
-
|
|
392
|
+
- Relative cost of key generation, cheapest first: `SymmetricKey(size:)`
|
|
393
|
+
(straight from the system random generator), P256, P384, P521, and
|
|
394
|
+
`SecureEnclave.P256.*.PrivateKey()` (a trip to the hardware).
|
|
395
|
+
- Generate a key once and keep it; never create one per operation.
|
|
586
396
|
|
|
587
|
-
|
|
588
|
-
// CommonCrypto (old) -- error-prone, manual IV/padding management
|
|
589
|
-
// ~30 lines of CCCrypt with buffer allocation
|
|
590
|
-
|
|
591
|
-
// CryptoKit (new) -- authenticated encryption in one call
|
|
592
|
-
let sealedBox = try AES.GCM.seal(data, using: key)
|
|
593
|
-
let decrypted = try AES.GCM.open(sealedBox, using: key)
|
|
594
|
-
```
|
|
397
|
+
## Moving from CommonCrypto
|
|
595
398
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
-
|
|
601
|
-
|
|
602
|
-
|
|
399
|
+
| CommonCrypto | CryptoKit |
|
|
400
|
+
|---|---|
|
|
401
|
+
| `CC_SHA256` into a manually sized buffer | `SHA256.hash(data:)` |
|
|
402
|
+
| `CCHmac(kCCHmacAlgSHA256, ...)` | `HMAC<SHA256>.authenticationCode(for:using:)` |
|
|
403
|
+
| `CCCrypt` with AES, a hand-managed IV and padding (dozens of lines) | `AES.GCM.seal` / `AES.GCM.open` |
|
|
404
|
+
|
|
405
|
+
What changes for the better:
|
|
406
|
+
|
|
407
|
+
- Encryption is authenticated by default; there is no unauthenticated CBC mode
|
|
408
|
+
to reach for by accident.
|
|
409
|
+
- Keys and nonces have their own types.
|
|
410
|
+
- Nonces are generated for you.
|
|
411
|
+
- No manual buffer sizing or pointer handling.
|
|
412
|
+
- Comparisons of MACs and signatures are constant-time.
|
|
413
|
+
- The types are `Sendable`.
|