A Swift package for ECDH P-256 key exchange, SHA-256 shared secret derivation, and AES-256-CBC encryption/decryption. Interoperable with the TypeScript and Go implementations — any pair can derive the same shared secret and exchange encrypted payloads directly.
Built on Apple's CryptoKit and CommonCrypto. No third-party dependencies.
- iOS 15+ / macOS 12+ / tvOS 15+ / watchOS 8+
- Swift 5.9+
- Open your project in Xcode.
- Go to File → Add Package Dependencies…
- Enter the repository URL — use HTTPS without the
.gitsuffix, or SSH:https://github.com/CypriotUnknown/swift-encryptorgit@github.com:CypriotUnknown/swift-encryptor.git - Select Up to Next Major Version starting from
1.0.0. - Add SwiftEncryptor to your target.
Add the dependency to your Package.swift:
dependencies: [
.package(url: "https://github.com/CypriotUnknown/swift-encryptor", from: "1.0.0")
],
targets: [
.target(
name: "YourTarget",
dependencies: ["SwiftEncryptor"]
)
]- Go to File → Add Package Dependencies…
- Click Add Local… and select the
swift-encryptorfolder.
import SwiftEncryptor// For app ↔ server communication (SPKI/PKCS8, base64-encoded)
let keys = try Encryptor.generateKeys(platform: .app)
print(keys.publicKeyString) // share this with the server
print(keys.privateKeyString) // keep this privateUse .browser when the counterpart is a web client exchanging JWK-formatted keys.
Both sides derive the same secret from their own private key and the other party's public key.
// App side
let appKeys = try Encryptor.generateKeys(platform: .app)
// Server sends its public key; compute the shared secret
let secret = try Encryptor.computeSecret(dto: ComputeSecretDTO(
platform: .app,
clientPublicKeyBase64: serverPublicKeyBase64,
privateKey: appKeys.privateKey
))
// secret is a base64 SHA-256 digest of the raw ECDH shared secretIf you have a previously exported or server-provided key as a base64 string, you can reconstruct it independently of the handshake flow:
let privateKey = try Encryptor.importPrivateKey(platform: .app, base64KeyString: "...")
let publicKey = try Encryptor.importPublicKey(platform: .app, base64KeyString: "...")let encrypted = try Encryptor.encryptContent(
content: "Hello, server!",
secret: secret,
platform: .app
)
// encrypted.iv — base64 IV
// encrypted.hash — base64 ciphertext (hex for .browser)struct RequestBody: Encodable {
let userId: String
let action: String
}
let body = RequestBody(userId: "abc123", action: "purchase")
let encrypted = try Encryptor.encryptContent(
content: body,
secret: secret,
platform: .app
)let data = encrypted.toData() // Data? — JSON representation of EncryptedBodyDTOAll decryptContent variants return Data, giving you full control over how to interpret the result.
let decrypted: Data = try Encryptor.decryptContent(
content: encrypted,
secret: secret,
platform: .app
)Useful when you receive the encrypted payload as raw JSON data (e.g. from a network response):
let decrypted: Data = try Encryptor.decryptContent(
data,
secret: secret,
platform: .app
)Pass intoType to have the decrypted data JSON-decoded into a concrete type directly:
struct ResponseBody: Decodable {
let status: String
let token: String
}
let response: ResponseBody = try Encryptor.decryptContent(
data,
secret: secret,
platform: .app,
intoType: ResponseBody.self
)let otp = Encryptor.generateRandomDigits() // 6 digits (default)
let otp = Encryptor.generateRandomDigits(maxDigits: 8) // 8 digitsimport SwiftEncryptor
// 1. Generate the app's key pair and send the public key to the server
let appKeys = try Encryptor.generateKeys(platform: .app)
let appPublicKey = appKeys.publicKeyString // → POST to /session/init
// 2. Server responds with its own public key
let serverPublicKey: String = ... // received from server
// 3. Derive the shared secret (server does the same on its side)
let secret = try Encryptor.computeSecret(dto: ComputeSecretDTO(
platform: .app,
clientPublicKeyBase64: serverPublicKey,
privateKey: appKeys.privateKey
))
// 4. Encrypt a request body
struct LoginRequest: Encodable {
let email: String
let password: String
}
let encrypted = try Encryptor.encryptContent(
content: LoginRequest(email: "user@example.com", password: "hunter2"),
secret: secret,
platform: .app
)
// 5. Send encrypted.toData() as the HTTP body
// 6. Decrypt the server response — pass intoType to decode directly into a struct
struct LoginResponse: Decodable {
let token: String
}
let responseData: Data = ... // received from server
let response: LoginResponse = try Encryptor.decryptContent(
responseData,
secret: secret,
platform: .app,
intoType: LoginResponse.self
)
// Or keep it as raw Data and handle it yourself
let rawData: Data = try Encryptor.decryptContent(responseData, secret: secret, platform: .app)| Property | Value |
|---|---|
| Curve | P-256 (secp256r1) |
| Key exchange | ECDH |
| Secret digest | SHA-256 |
| Encryption | AES-256-CBC, PKCS7 padding |
| IV | 16 random bytes, base64 |
| App key format | SPKI (public) / PKCS8 (private), base64 |
| Browser key format | JWK (JSON) |
| App hash format | base64 |
| Browser hash format | hex |
| Language | Package |
|---|---|
| TypeScript | @cypriot/encryptor — JSR (Deno / Bun / Node.js) · @cypriotunknown/encryptor — npm |
| Go | encryptor-go — go get github.com/CypriotUnknown/encryptor-go |
All three implementations share the same P-256 curve, SHA-256 secret digest, AES-256-CBC cipher, and "app"/"browser" platform conventions, so any pair can interoperate directly.
MIT