SwiftKyber makes it possible to explore CRYSTALS-Kyber in a Swift context.
In your project Package.swift file add a dependency like Usage
dependencies: [ .package(url: "https://github.com/leif-ibsen/SwiftKyber", from: "1.3.0"), ]
Suppose Alice and Bob wish to share a secret key they can use as a symmetric encryption key: How it Works
- Alice generates a Kyber key pair, publicKey and secretKey. She sends publicKey to Bob
- Bob runs publicKey.Encapsulate() to generate a shared secret K and a cipher text cipher
- Bob sends cipher to Alice
- Alice runs secretKey.Decapsulate(ct: cipher) to generate the same shared secret K
Here is a Kyber.K512 example:
import SwiftKyber // Alice: let (publicKey, secretKey) = Kyber.K512.GenerateKeyPair() // Bob: let (cipher, K1) = publicKey.Encapsulate() print("Bob's K: ", K1) // Alice: let K2 = try secretKey.Decapsulate(ct: cipher) print("Alice's K:", K2)
giving (for example):
Bob's K: [106, 169, 16, 187, 123, 157, 206, 223, 236, 143, 173, 180, 243, 130, 157, 122, 150, 68, 167, 31, 33, 246, 28, 150, 215, 182, 71, 72, 128, 37, 202, 17] Alice's K: [106, 169, 16, 187, 123, 157, 206, 223, 236, 143, 173, 180, 243, 130, 157, 122, 150, 68, 167, 31, 33, 246, 28, 150, 215, 182, 71, 72, 128, 37, 202, 17]
SwiftKyber public and secret keys can be stored in three formats: Key Representation
- DER encoded - a byte array
- PEM encoded - a string
- As raw bytes
- Generate new keys
- Store existing keys in DER or PEM format or as raw bytes
- Load keys from their DER or PEM encoding or from their raw bytes
import SwiftKyber let (pk, sk) = Kyber.K768.GenerateKeyPair()
generates a new public key pk and a new secret key sk for the K768 instance.
Keys can be stored in DER format, in PEM format or as raw bytes.
import SwiftKyber let (pk, _) = Kyber.K512.GenerateKeyPair() let pkDer = pk.der // The DER encoding - a byte array let pkPem = pk.pem // The PEM encoding - a String let pkBytes = pk.bytes // The raw bytes let newPkFromDER = try PublicKey(der: pkDer) let newPkFromPEM = try PublicKey(pem: pkPem) let newPkFromRaw = try PublicKey(bytes: pkBytes) assert(pk == newPkFromDER) assert(pk == newPkFromPEM) assert(pk == newPkFromRaw)
and for secret keys:
import SwiftKyber let (_, sk) = Kyber.K512.GenerateKeyPair() let skDer = sk.der // The DER encoding - a byte array let skPem = sk.pem // The PEM encoding - a String let skBytes = sk.bytes // The raw bytes let newSkFromDER = try SecretKey(der: skDer) let newSkFromPEM = try SecretKey(pem: skPem) let newSkFromRaw = try SecretKey(bytes: skBytes) assert(sk == newSkFromDER) assert(sk == newSkFromPEM) assert(sk == newSkFromRaw)
The DER and PEM formats are based on the ASN1 structure of the keys. These structures are defined in [KEYS], but it is my understanding that the ASN1 representation is not settled yet: It may change, there may be no ASN1 structure at all or I may have misread [KEYS]. ASN1 reservations
SwiftKyber's key generation, encapsulation and decapsulation performance was measured on an iMac 2021, Apple M1 chip. The table below shows the figures in milli seconds for the three Kyber instances. Performance
|Kyber.K512||0.14 mSec||0.16 mSec||0.18 mSec|
|Kyber.K768||0.22 mSec||0.25 mSec||0.28 mSec|
|Kyber.K1024||0.33 mSec||0.36 mSec||0.40 mSec|
The SwiftKyber package depends on the ASN1 and BigInt packages Dependencies
dependencies: [ .package(url: "https://github.com/leif-ibsen/ASN1", from: "2.2.0"), .package(url: "https://github.com/leif-ibsen/BigInt", from: "1.14.0"), ],
SwiftKyber does not do Big Integer arithmetic, but BigInt is a dependency because ASN1 depends on it.
Algorithms from the following papers have been used in the implementation. There are references in the source code where appropriate.
- [KYBER] - CRYSTALS-Kyber, Algorithm Specifications And Supporting Documentation, January 2021
- [DRAFT] - Kyber Post-Quantum KEM, draft-cfrg-schwabe-kyber-03, September 2023
- [KEYS] - Quantum Safe Cryptography Key Information for CRYSTALS-Kyber, October 2022