A practical guide to swift-crypto: CryptoKit-style APIs on Linux, Windows, and Apple platforms

Hi everyone! I've been using swift-crypto in server-side and cross-platform projects, so here's a quick overview with patterns I've found useful.

What is swift-crypto?

swift-crypto is Apple's open-source version of the CryptoKit API.

  • On Apple platforms, import Crypto re-exports the system CryptoKit, so you get the OS implementation.
  • On Linux, Windows, and Android, it uses a vendored copy of BoringSSL behind the same API.

This means one codebase with no #if canImport(CryptoKit) branches for most use cases.

Installation

.package(url: "https://github.com/apple/swift-crypto.git", "3.0.0"..<"5.0.0"),
// target:
.product(name: "Crypto", package: "swift-crypto"),

1. Hashing

import Crypto

let digest = SHA256.hash(data: Data("hello".utf8))
let hex = digest.map { String(format: "%02x", $0) }.joined()

Insecure.MD5 and Insecure.SHA1 exist only for legacy interop. The namespace is a deliberate warning.

2. HMAC (e.g. webhook signature verification)

let key = SymmetricKey(data: Data(secret.utf8))
let isValid = HMAC<SHA256>.isValidAuthenticationCode(
    receivedMAC, authenticating: body, using: key
)

Use isValidAuthenticationCode rather than comparing bytes with ==, because it compares in constant time.

3. Authenticated encryption (AES-GCM / ChaChaPoly)

let key = SymmetricKey(size: .bits256)

let sealed = try AES.GCM.seal(plaintext, using: key)
let wire = sealed.combined!   // nonce + ciphertext + tag

let box = try AES.GCM.SealedBox(combined: wire)
let decrypted = try AES.GCM.open(box, using: key)

seal generates a random nonce for you. If you supply your own, never reuse a nonce with the same key. ChaChaPoly has an identical API and is a good choice on hardware without AES acceleration.

4. Signatures (Curve25519 / P256)

let signingKey = Curve25519.Signing.PrivateKey()
let signature = try signingKey.signature(for: message)
let ok = signingKey.publicKey.isValidSignature(signature, for: message)

P256/P384/P521 keys support pemRepresentation and derRepresentation, which helps when interoperating with OpenSSL, JWTs (ES256), or other languages.

5. Key agreement + HKDF

let alice = P256.KeyAgreement.PrivateKey()
let bob = P256.KeyAgreement.PrivateKey()

let shared = try alice.sharedSecretFromKeyAgreement(with: bob.publicKey)
let sessionKey = shared.hkdfDerivedSymmetricKey(
    using: SHA256.self,
    salt: salt,
    sharedInfo: Data("my-app v1 session".utf8),
    outputByteCount: 32
)

Never use the raw SharedSecret directly as a key. Always run it through HKDF.

_CryptoExtras

The package also ships _CryptoExtras, which has APIs that aren't in CryptoKit, such as RSA (for legacy interop) and AES-CBC/CTR. The underscore means the API may change, so pin versions carefully.

Tips

  • Keep keys out of source code. Load them from the Keychain, a secrets manager, or environment variables.
  • Use SymmetricKey(size:) for random keys rather than building your own from Data.
  • Prefer the high-level AEAD APIs over hand-rolled encrypt-then-MAC.
  • Test on both macOS and Linux in CI. The API is the same, but the backends differ.

Happy to hear how others are using swift-crypto, especially on server-side or Android. Feedback and corrections are welcome!

3 Likes

FYI we're at 5.1.1 now so you should be able to do "3.0.0"..<"6.0.0"

Thanks for the heads-up! I’ve updated the installation snippet to "3.0.0"..<"6.0.0" so it picks up the 5.x releases.

Thanks for the practical guide!

We rely heavily on multiplatform Swift Crypto to build Swift Bitcoin. We combined it with a safe wrapper of the secp256k1 Elliptic Curve library and supplemented it to offer a BitcoinCrypto product.

All in all we use the following cryptographic/hashing functions from the Crypto package:

  • HMAC-SHA512
  • ChaCha20-Poly1305
  • HKDF-SHA256

Additional ones from secp256k1:

  • ECDSA
  • Schnorr Signatures (BIP340)
  • MuSig2 (BIP327)
  • PBKDF2
  • ElligatorSwift
  • ECDH

And while we don't like rolling our own crypto we had to fill in the blanks with these:

  • RIPEMD-160
  • SipHash-2-4
  • Double-SHA256 (SHA256d)

One thing we had to adapt Swift Crypto for is getting span support and more efficient representations of hashes/signatures like InlineArray or UInt256.

Anyway, that's how we leverage it and we hope that the library keeps growing so we can use it even more!