Skip to content

CypriotUnknown/swift-encryptor

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SwiftEncryptor

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.


Requirements

  • iOS 15+ / macOS 12+ / tvOS 15+ / watchOS 8+
  • Swift 5.9+

Installation

Swift Package Manager — Xcode

  1. Open your project in Xcode.
  2. Go to File → Add Package Dependencies…
  3. Enter the repository URL — use HTTPS without the .git suffix, or SSH:
    https://github.com/CypriotUnknown/swift-encryptor
    
    git@github.com:CypriotUnknown/swift-encryptor.git
    
  4. Select Up to Next Major Version starting from 1.0.0.
  5. Add SwiftEncryptor to your target.

Swift Package Manager — Package.swift

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"]
    )
]

Local package (development)

  1. Go to File → Add Package Dependencies…
  2. Click Add Local… and select the swift-encryptor folder.

Usage

import SwiftEncryptor

1. Generate a key pair

// 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 private

Use .browser when the counterpart is a web client exchanging JWK-formatted keys.


2. Compute the shared ECDH secret

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 secret

Importing keys from a base64 string

If 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: "...")

3. Encrypt content

From a String

let encrypted = try Encryptor.encryptContent(
    content: "Hello, server!",
    secret: secret,
    platform: .app
)
// encrypted.iv   — base64 IV
// encrypted.hash — base64 ciphertext (hex for .browser)

From any Encodable type

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
)

Send it as Data

let data = encrypted.toData() // Data? — JSON representation of EncryptedBodyDTO

4. Decrypt content

All decryptContent variants return Data, giving you full control over how to interpret the result.

From an EncryptedBodyDTOData

let decrypted: Data = try Encryptor.decryptContent(
    content: encrypted,
    secret: secret,
    platform: .app
)

From DataData

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
)

From DataDecodable type

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
)

5. Generate random digits (OTP)

let otp = Encryptor.generateRandomDigits()          // 6 digits (default)
let otp = Encryptor.generateRandomDigits(maxDigits: 8) // 8 digits

Full example — App ↔ Server handshake

import 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)

Interoperability

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

Related Packages

Language Package
TypeScript @cypriot/encryptor — JSR (Deno / Bun / Node.js) · @cypriotunknown/encryptor — npm
Go encryptor-gogo 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.


License

MIT

About

Swift package for ECDH P-256 key exchange and AES-256-CBC encryption, interoperable with https://jsr.io/@cypriot/encryptor on Node.js.

Resources

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages