{"id":28754209,"url":"https://github.com/dm-zharov/swift-security","last_synced_at":"2025-06-17T01:07:56.357Z","repository":{"id":218144784,"uuid":"745007087","full_name":"dm-zharov/swift-security","owner":"dm-zharov","description":"Modern Swift framework for Keychain API. Supports iOS, macOS, watchOS, tvOS and visionOS","archived":false,"fork":false,"pushed_at":"2024-09-13T17:37:26.000Z","size":244,"stargazers_count":294,"open_issues_count":0,"forks_count":14,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-16T11:03:30.417Z","etag":null,"topics":["certificate","cryptokit","identity","keychain","pkcs12","security","swift","swiftui","x509"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dm-zharov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-18T13:05:32.000Z","updated_at":"2025-06-11T11:46:40.000Z","dependencies_parsed_at":"2024-01-22T14:26:41.342Z","dependency_job_id":"3a468d6c-e7f8-4a77-9e65-2e9fec577057","html_url":"https://github.com/dm-zharov/swift-security","commit_stats":null,"previous_names":["dm-zharov/swift-security"],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/dm-zharov/swift-security","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dm-zharov%2Fswift-security","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dm-zharov%2Fswift-security/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dm-zharov%2Fswift-security/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dm-zharov%2Fswift-security/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dm-zharov","download_url":"https://codeload.github.com/dm-zharov/swift-security/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dm-zharov%2Fswift-security/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260148281,"owners_count":22965910,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["certificate","cryptokit","identity","keychain","pkcs12","security","swift","swiftui","x509"],"created_at":"2025-06-17T01:07:54.244Z","updated_at":"2025-06-17T01:07:56.341Z","avatar_url":"https://github.com/dm-zharov.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SwiftSecurity\n\n[![Platforms](https://img.shields.io/badge/platforms-_iOS_|_macOS_|_watchOS_|_tvOS_|_visionOS-lightgrey.svg?style=flat)](https://developer.apple.com/resources/)\n[![SPM supported](https://img.shields.io/badge/SPM-supported-DE5C43.svg?style=flat)](https://swift.org/package-manager)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://mit-license.org)\n\nSwiftSecurity is a modern Swift API for Apple's [Security](https://developer.apple.com/documentation/security) framework (Keychain API, SharedWebCredentials API, Cryptography, etc). Secure the data your app manages in a much easier way with compile-time checks. \n\n## Features\n\nHow does SwiftSecurity differ from other popular frameworks?\n\n* Supports every [Keychain item](https://developer.apple.com/documentation/security/keychain_services/keychain_items/item_class_keys_and_values) (Generic \u0026 Internet Password, Key, Certificate and Identity).\n* Prevents usage of an incorrect set of [attributes](https://developer.apple.com/documentation/security/keychain_services/keychain_items/item_attribute_keys_and_values) for Keychain items.\n* Provides consistent behavior across platforms. Verified through [apple-oss-distributions/Security](https://github.com/apple-oss-distributions/Security).\n* Follows recommendations from [DTS Engineer](https://forums.developer.apple.com/forums/thread/707279). Excludes legacy from OS X.\n* Compatible with [CryptoKit](https://developer.apple.com/documentation/cryptokit/), [SwiftUI](https://developer.apple.com/documentation/swiftui/) and [apple/swift-certificates](https://github.com/apple/swift-certificates).\n\n## Installation\n\n#### Requirements\n\n* iOS 14.0+ / macOS 11.0+ / Mac Catalyst 14.0+ / watchOS 7.0+ / tvOS 14.0+ / visionOS 1.0+\n* Swift 5.9\n\n#### Swift Package Manager\n\nTo use the `SwiftSecurity`, add the following dependency in your `Package.swift`:\n```swift\n.package(url: \"https://github.com/dm-zharov/swift-security.git\", from: \"2.0.0\")\n```\n\nFinally, add `import SwiftSecurity` to your source code.\n\n## Quick Start\n\n####  Basic\n\n```swift\n// Choose Keychain\nlet keychain = Keychain.default\n\n// Store secret\ntry keychain.store(\"8e9c0a7f\", query: .credential(for: \"OpenAI\"))\n\n// Retrieve secret\nlet token: String? = try keychain.retrieve(.credential(for: \"OpenAI\"))\n\n// Remove secret\ntry keychain.remove(.credential(for: \"OpenAI\"))\n```\n\n#### Basic (SwiftUI)\n\n```swift\nstruct AuthView: View {\n    @Credential(\"OpenAI\") private var token: String?\n\n    var body: some View {\n        VStack {\n            Button(\"Save\") {\n                // Store secret\n                try? _token.store(\"8e9c0a7f\")\n            }\n            Button(\"Delete\") {\n                // Remove secret\n                try? _token.remove()\n            }\n        }\n        .onChange(of: token) {\n            if let token {\n                // Use secret\n            }\n        }\n    }\n} \n```\n\n#### Web Credential\n\nA password for a website or an area on a server, that requires authentication.\n\n```swift\n// Store password for a website\ntry keychain.store(\n    password, query: .credential(for: \"username\", space: .website(\"https://example.com\"))\n)\n\n// Retrieve password for a website\nlet password: String? = try keychain.retrieve(\n    .credential(for: \"username\", space: .website(\"https://example.com\"))\n)\n```\n\nFor example, if you need to store distinct ports credentials for the same user working on the same server, you might further characterize the query by specifying protection space.\n\n```swift\nlet space1 = WebProtectionSpace(host: \"example.com\", port: 443)\ntry keychain.store(password1, query: .credential(for: user, space: space1))\n\nlet space2 = WebProtectionSpace(host: \"example.com\", port: 8443)\ntry keychain.store(password2, query: .credential(for: user, space: space2))\n```\n\n#### Get Attributes\n\n```swift\nif let info = try keychain.info(for: .credential(for: \"OpenAI\")) {\n    // Creation date\n    print(info.creationDate)\n    // Comment\n    print(info.comment)\n    ...\n}\n```\n\n#### Error Handling\n\n`SwiftSecurityError` offers values for the most common issues. \n\n```swift\ndo {\n    try keychain.store(\"8e9c0a7f\", query: .credential(for: \"OpenAI\"))\n} catch {\n    switch error as? SwiftSecurityError {\n    case .duplicateItem:\n        // handle duplicate\n    default:\n        // unhandled\n    }\n}\n```\n\nIf case of the rare issue, you'll receive `.underlyingSecurityError(error:)` with an `OSStatus` code that can be matched with underlying [Security Framework Result Codes](https://developer.apple.com/documentation/security/1542001-security_framework_result_codes).\n\n#### Remove All\n\n```swift\n// Removes everything from a keychain\ntry keychain.removeAll()\n\n// Removes everything from a keychain, including distributed to other devices credentials through iCloud\ntry keychain.removeAll(includingSynchronizableCredentials: true)\n```\n\n## 🛠️ Usage\n\n#### Get Data \u0026 Persistent Reference\n\nIf you're working with `NEVPNProtocol`, you likely need to access persistent reference to `password` or `identity`.\n\n```swift\n// Retrieve multiple values at once\nif case let .dictionary(info) = try keychain.retrieve([.data, .persistentReference], query: .credential(for: \"OpenAI\")) {\n    // Data\n    info.data\n    // Persistent Reference\n    info.persistentReference\n}\n\n// Retrieve persistent reference right after storing the secret\nif case let .persistentReference(data) = try keychain.store(\n    \"8e9c0a7f\",\n    returning: .persistentReference, /* OptionSet */\n    query: .credential(for: \"OpenAI\")\n) {\n    // Persistent Reference\n    data\n}\n```\n\n#### CryptoKit\n\n`SwiftSecurity` lets you natively store `CryptoKit` keys as native `SecKey` instances. [Key types supporting such conversion](https://developer.apple.com/documentation/cryptokit/storing_cryptokit_keys_in_the_keychain#3369556), like `P256`/`P384`/`P521`, conform to `SecKeyConvertible` protocol.\n\n```swift\n// Store private key\nlet privateKey = P256.KeyAgreement.PrivateKey()\ntry keychain.store(privateKey, query: .key(for: \"Alice\"))\n\n// Retrieve private key (+ public key)\nlet privateKey: P256.KeyAgreement.PrivateKey? = try keychain.retrieve(.key(for: \"Alice\"))\nlet publicKey = privateKey.publicKey /* Recommended */\n\n// Store public key. Not recommended, as you can generate it\ntry keychain.store(\n    publicKey,\n    query: .key(for: \"Alice\", descriptor: .ecPublicKey)\n)\n```\n\nOther key types, like `SymmetricKey`, `Curve25519`, `SecureEnclave.P256`, have no direct keychain corollary. In particular, `SecureEnclave.P256.PrivateKey` is an encrypted block that only the same `Secure Enclave` can later use to restore the key. These types conform to `SecDataConvertible`, so store them as follows:\n\n```swift\n// Store symmetric key\nlet symmetricKey = SymmetricKey(size: .bits256)\ntry keychain.store(symmetricKey, query: .credential(for: \"Chat\"))\n```\n\n\u003e [!NOTE]\n\u003e `SecKey` supports only `P-256, P-384, P-521 Elliptic Curve` and `RSA` keys. For more details, see [On Cryptographic Key Formats](https://developer.apple.com/forums/thread/680554).\n\n#### Certificate\n\nDER-Encoded X.509 Certificate.\n\n```swift\n// Prepare certificate\nlet certificateData: Data // Content of file, often with `cer`/`der` extension \ncertificate = try Certificate(derRepresentation: certificateData)\n\n// Store certificate\ntry keychain.store(certificate, query: .certificate(for: \"Root CA\"))\n```\n\nYou could use `SwiftSecurity` simultaneously with `X509` package from [apple/swift-certificates](https://github.com/apple/swift-certificates). In case of `Swift Package Manager` dependency resolve issues, copy `SecCertificateConvertible` conformance directly to your project.\n\n#### Digital Identity\n\nA digital identity is the combination of a certificate and the private key that matches the public key within certificate.\n\n```swift\n// Import digital identity from `PKCS #12` data\nlet pkcs12Data: Data /* Contents of PKCS #12 file (also known as PKCS12, PFX, .p12, and .pfx) */\nfor importItem in try PKCS12.import(pkcs12Data, passphrase: \"8e9c0a7f\") {\n    if let identity = importItem.identity {\n        // Store digital identity\n        try keychain.store(identity, query: .identity(for: \"Apple Development\"))\n    }\n}\n\n// Retrieve digital identity\nif let identity = try keychain.retrieve(.identity(for: \"Apple Development\")) {\n    // Certificate\n    identity.certificate\n    // Private Key Data\n    identity.privateKey\n    // Underlying SecIdentity\n    identity.secIdentity\n}\n```\n\nThe system stores certificate and private key separately.\n\n#### Custom Query\n\n```swift\n// Create query\nvar query = SecItemQuery\u003cGenericPassword\u003e()\n\n// Customize query\nquery.synchronizable = true\nquery.service = \"OpenAI\"\nquery.label = \"OpenAI Access Token\"\n\n// Perform query\ntry keychain.store(secret, query: query, accessPolicy: AccessPolicy(.whenUnlocked, options: .biometryAny))\n_ = try keychain.retrieve(query, authenticationContext: LAContext())\ntry keychain.remove(query)\n```\n\nQuery prevents the creation of an incorrect set of attributes for item:\n\n```swift\nvar query = SecItemQuery\u003cInternetPassword\u003e()\nquery.synchronizable = true  // ✅ Common\nquery.server = \"example.com\" // ✅ Only for `InternetPassword`\nquery.service = \"OpenAI\"     // ❌ Only for `GenericPassword`, so not accessible\nquery.keySizeInBits = 2048   // ❌ Only for `SecKey`, so not accessible\n```\n\nPossible queries:\n\n```swift\nSecItemQuery\u003cGenericPassword\u003e  // kSecClassGenericPassword\nSecItemQuery\u003cInternetPassword\u003e // kSecClassInternetPassword\nSecItemQuery\u003cSecKey\u003e           // kSecClassSecKey\nSecItemQuery\u003cSecCertificate\u003e   // kSecClassSecCertificate\nSecItemQuery\u003cSecIdentity\u003e      // kSecClassSecIdentity\n```\n\n#### Debug\n\n```swift\n// Print Keychain (or use LLDB `po` command)\nprint(keychain.debugDescription)\n\n// Print Query\nprint(query.debugDescription)\n\n// Output -\u003e [\"Class: GenericPassword\", ..., \"Service: OpenAI\"]\n```\n\n## 🔑 How to Choose Keychain\n\n#### Default\n\n```swift\nlet keychain = Keychain.default\n```\n\nThe system considers the first item in the list of [keychain access groups](https://developer.apple.com/documentation/security/keychain_services/keychain_items/sharing_access_to_keychain_items_among_a_collection_of_apps/) to be the app’s default access group, evaluated in this order:\n- The optional [Keychain Access Groups Entitlement](https://developer.apple.com/documentation/bundleresources/entitlements/keychain-access-groups) holds an array of strings, each of which names an access group.\n- Application identifier, formed as the team identifier (team ID) plus the bundle identifier (bundle ID). For example, `J42EP42PB2.com.example.app`.\n\nIf the [Keychain Sharing](https://developer.apple.com/documentation/xcode/configuring-keychain-sharing#Specify-the-default-keychain-group) capability is not enabled, the default access group is `app ID`.\n\n\u003e [!NOTE]\n\u003e To enable macOS support, make sure to include the [Keychain Sharing (macOS)](https://developer.apple.com/documentation/xcode/configuring-keychain-sharing#Specify-the-default-keychain-group) capability and create a group `${TeamIdentifierPrefix}com.example.app`, to prevent errors in operations. This sharing group is [automatically generated](https://developer.apple.com/documentation/security/keychain_services/keychain_items/sharing_access_to_keychain_items_among_a_collection_of_apps/#2974917) for other platforms and accessible without capability. You could refer to [TestHost](https://github.com/dm-zharov/swift-security/tree/main/Tests/TestHost.xcodeproj) for information regarding project configuration.\n\n#### Sharing within Keychain Group\n\nIf you prefer not to rely on the automatic behavior of default storage selection, you have the option to explicitly specify a keychain sharing group.\n\n```swift\nlet keychain = Keychain(accessGroup: .keychainGroup(teamID: \"J42EP42PB2\", nameID: \"com.example.app\"))\n```\n\n#### Sharing within App Group\n\nSharing could also be achieved by using [App Groups](https://developer.apple.com/documentation/xcode/configuring-app-groups) capability. Unlike a keychain sharing group, the app group can’t automatically became the default storage for keychain items. You might already be using an app group, so it's probably would be the most convenient choice.\n\n```swift\nlet keychain = Keychain(accessGroup: .appGroupID(\"group.com.example.app\"))\n```\n\n\u003e [!NOTE]\n\u003e Use `Sharing within Keychain Group` for sharing on macOS, as the described behavior is not present on this platform. There's no issue with using one sharing solution on one platform and a different one on another.\n\n## 🔓 Protection with Face ID (Touch ID) and Passcode\n\n#### Store protected item\n\n```swift\n// Store with specified `AccessPolicy`\ntry keychain.store(\n    secret,\n    query: .credential(for: \"FBI\"),\n    accessPolicy: AccessPolicy(.whenUnlocked, options: .userPresence) // Requires biometry/passcode authentication\n)\n```\n\n#### Retrieve protected item\n\nIf you request the protected item, an authentication screen will automatically appear.\n\n```swift\n// Retrieve value\ntry keychain.retrieve(.credential(for: \"FBI\"))\n```\n\nIf you want to manually authenticate before making a request or customize authentication screen, provide [LAContext](https://developer.apple.com/documentation/localauthentication/lacontext) to the retrieval method.\n\n```swift\n// Create an LAContext\nvar context = LAContext()\n\n// Authenticate\ndo {\n    let success = try await context.evaluatePolicy(\n        .deviceOwnerAuthentication,\n        localizedReason: \"Authenticate to proceed.\" // Authentication prompt\n    )\n} else {\n    // Handle LAError error\n}\n\n// Check authentication result \nif success {\n    // Retrieve value\n    try keychain.retrieve(.credential(for: \"FBI\"), authenticationContext: context)\n}\n\n```\n\n\u003e [!WARNING]\n\u003e Include the [NSFaceIDUsageDescription](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW75) key in your app’s Info.plist file. Otherwise, authentication request may fail.\n\n## ℹ️ Data Types\n\nYou can store, retrieve, and remove various types of values.\n\n```swift\nFoundation:\n    - Data /* GenericPassword, InternetPassword */\n    - String /* GenericPassword, InternetPassword */\nCryptoKit:\n    - SymmetricKey /* GenericPassword */\n    - Curve25519 -\u003e PrivateKey /* GenericPassword */\n    - SecureEnclave.P256 -\u003e PrivateKey /* GenericPassword (SE's Key Data is Persistent Reference) */\n    - P256, P384, P521 -\u003e PrivateKey /* SecKey (ANSI x9.63 Elliptic Curves) */\nX509 (external package `apple/swift-certificates`):\n    - Certificate /* SecCertificate */\nSwiftSecurity:\n    - Certificate /* SecCertificate */\n    - DigitalIdentity /* SecIdentity (The Pair of SecCertificate and SecKey) */\n```\n\nTo add support for custom types, you can extend them by conforming to the following protocols.\n\n```swift\n// Store as Data (GenericPassword, InternetPassword)\nextension CustomType: SecDataConvertible {}\n\n// Store as Key (ANSI x9.63 Elliptic Curves or RSA Keys)\nextension CustomType: SecKeyConvertible {}\n\n// Store as Certificate (X.509)\nextension CustomType: SecCertificateConvertible {}\n\n// Store as Identity (The Pair of Certificate and Private Key)\nextension CustomType: SecIdentityConvertible {}\n```\n\nThese protocols are inspired by Apple's sample code from the [Storing CryptoKit Keys in the Keychain](https://developer.apple.com/documentation/cryptokit/storing_cryptokit_keys_in_the_keychain) article.\n\n## 🔑 Shared Web Credential\n\n\u003e [!TIP]\n\u003e [SharedWebCredentials API](https://developer.apple.com/documentation/security/shared_web_credentials) makes it possible to share credentials with the website counterpart. For example, a user may log in to a website in Safari and save credentials to the iCloud Keychain. Later, the user may run an app from the same developer, and instead of asking the user to reenter a username and password, it could access the existing credentials. The user can create new accounts, update passwords, or delete account from within the app. These changes should be saved from the app to be used by Safari.\n\n```swift\n// Store\nSharedWebCredential.store(\"https://example.com\", account: \"username\", password: \"secret\") { result in\n    switch result {\n    case .failure(let error):\n        // Handle error\n    case .success:\n        // Handle success\n    }\n}\n\n// Remove\nSharedWebCredential.remove(\"https://example.com\", account: \"username\") { result in\n    switch result {\n    case .failure(let error):\n        // Handle error\n    case .success:\n        // Handle success\n    }\n}\n\n// Retrieve\n// - Use `ASAuthorizationController` to make an `ASAuthorizationPasswordRequest`.\n```\n\n## 🔒 Secure Data Generator\n\n```swift\n// Data with 20 uniformly distributed random bytes\nlet randomData = try SecureRandomDataGenerator(count: 20).next()\n```\n\n## Security\n\nThe framework’s default behavior provides a reasonable balance between convenience and accessibility.\n\n- `kSecUseDataProtectionKeychain: true` helps to achieve [consistent behavior across platforms](https://developer.apple.com/documentation/security/ksecusedataprotectionkeychain), so it shouldn't and cannot be changed.\n- `kSecAttrAccessibleAfterFirstUnlock` makes keychain items [accessible from background state](https://developer.apple.com/documentation/security/ksecattraccessibleafterfirstunlock), yet changeable by using `AccessPolicy`.\n\n## Communication\n\n- If you **found a bug**, open an issue.\n- If you **have a feature request**, open an issue.\n- If you **want to contribute**, submit a pull request.\n\n## Knowledge\n\n* [Sharing access to keychain items among a collection of apps](https://developer.apple.com/documentation/security/keychain_services/keychain_items/sharing_access_to_keychain_items_among_a_collection_of_apps/)\n* [Storing CryptoKit Keys in the Keychain](https://developer.apple.com/documentation/cryptokit/storing_cryptokit_keys_in_the_keychain)\n* [TN3137: On Mac keychain APIs and implementations](https://developer.apple.com/documentation/technotes/tn3137-on-mac-keychains)\n* [On Cryptographic Key Formats](https://developer.apple.com/forums/thread/680554)\n* [SecItem: Fundamentals](https://developer.apple.com/forums/thread/724023)\n* [SecItem: Pitfalls and Best Practices](https://developer.apple.com/forums/thread/724013)\n\n## Author\n\nDmitriy Zharov, contact@zharov.dev\n\n## License\n\nSwiftSecurity is available under the MIT license. See the [LICENSE file](https://github.com/dm-zharov/swift-security/blob/master/LICENSE) for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdm-zharov%2Fswift-security","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdm-zharov%2Fswift-security","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdm-zharov%2Fswift-security/lists"}