Swift-NaCL is a Swift binding to a fork of libsodium library. These libraries have a stated goal of improving usability, security and speed.
This package provides a modern, idiomatic Swift interface to the libsodium cryptographic library, offering state-of-the-art cryptographic primitives for secure communication, data integrity, and authentication.
- iOS 13.0+
- macOS 10.15+
- tvOS 13.0+
- watchOS 6.0+
- visionOS 1.0+
- Linux (Ubuntu 18.04+, with system libsodium or bundled binaries)
To add Swift-NaCL as dependency to your Xcode project, select File > Swift Packages > Add Package Dependency, enter its repository URL:
https://github.com/Kingpin-Apps/swift-ncal.git
Import both SwiftNcal and Clibsodium in your project.
Add the following to your Package.swift file:
dependencies: [
.package(url: "https://github.com/Kingpin-Apps/swift-ncal.git", from: "0.1.4")
],
targets: [
.target(
name: "YourTarget",
dependencies: [
.product(name: "SwiftNcal", package: "swift-ncal")
]
)
]In your Swift files, import the library:
import SwiftNcal- ✅ Digital Signatures - Ed25519 signing and verification
- ✅ Secret-key Encryption - XSalsa20-Poly1305 and XChacha20-Poly1305 AEAD
- ✅ Public-key Encryption - Curve25519 key exchange with authenticated encryption
- ✅ Cryptographic Hashing - SHA-256, SHA-512, BLAKE2b, SipHash variants
- ✅ Verifiable Random Functions (VRF) - IETF Draft 03 specification with Ed25519
- ✅ Message Authentication - Poly1305 MAC
- ✅ Key Derivation - Password-based and deterministic key generation
- ✅ Cross-platform - Works on Apple platforms and Linux
- ✅ Memory Safe - Secure memory handling with automatic cleanup
import SwiftNcal
// Generate a signing key
let signingKey = try SigningKey.generate()
let verifyKey = signingKey.verifyKey
// Sign a message
let message = "Hello, World!".data(using: .utf8)!
let signedMessage = try signingKey.sign(message: message)
// Verify the signature
let verifiedMessage = try verifyKey.verify(smessage: signedMessage.getCombined)
print(String(data: verifiedMessage, encoding: .utf8)!) // "Hello, World!"import SwiftNcal
// Generate a random key
let key = random(size: 32)
let secretBox = try SecretBox(key: key)
// Encrypt a message
let plaintext = "Secret message".data(using: .utf8)!
let encrypted = try secretBox.encrypt(plaintext: plaintext)
// Decrypt the message
let decrypted = try secretBox.decrypt(ciphertext: encrypted.combined)
print(String(data: decrypted, encoding: .utf8)!) // "Secret message"import SwiftNcal
// Generate key pairs
let aliceKeyPair = KeyPair.generate()
let bobKeyPair = KeyPair.generate()
// Alice encrypts a message for Bob
let aliceBox = try Box(privateKey: aliceKeyPair.secretKey, publicKey: bobKeyPair.publicKey)
let message = "Hello Bob!".data(using: .utf8)!
let encrypted = try aliceBox.encrypt(plaintext: message)
// Bob decrypts the message from Alice
let bobBox = try Box(privateKey: bobKeyPair.secretKey, publicKey: aliceKeyPair.publicKey)
let decrypted = try bobBox.decrypt(ciphertext: encrypted.combined)
print(String(data: decrypted, encoding: .utf8)!) // "Hello Bob!"import SwiftNcal
let hash = Hash()
let message = "Hello, World!".data(using: .utf8)!
// SHA-256
let sha256Hash = try hash.sha256(message: message)
print(sha256Hash.base64EncodedString())
// BLAKE2b with custom parameters
let blake2bHash = try hash.blake2b(
data: message,
digestSize: 32,
key: "secret-key".data(using: .utf8)!,
salt: "salt1234".data(using: .utf8)!,
person: "personal".data(using: .utf8)!
)import SwiftNcal
// Generate a VRF key pair
let keyPair = VRFKeyPair.generate()
// Create a proof for a message
let message = "Hello, VRF!".data(using: .utf8)!
let proof = try keyPair.signingKey.prove(message: message)
// Verify the proof and get the deterministic output
let output = try keyPair.verifyingKey.verify(message: message, proof: proof)
print("VRF Output: \(output.hexEncodedString())")
// The same message always produces the same output with the same key
let proof2 = try keyPair.signingKey.prove(message: message)
let output2 = try keyPair.verifyingKey.verify(message: message, proof: proof2)
assert(output == output2) // Always true!SigningKey- Ed25519 private key for signingVerifyKey- Ed25519 public key for verificationSignedMessage- Container for signed messages
PrivateKey- Curve25519 private keyPublicKey- Curve25519 public keyKeyPair- Combined public/private key pairBox- Public-key authenticated encryption
SecretBox- XSalsa20-Poly1305 AEAD encryptionAead- XChacha20-Poly1305 AEAD encryption with additional data
Hash- Cryptographic hash functions (SHA-256, SHA-512, BLAKE2b, SipHash)
VRFSeed- 32-byte cryptographic seed for key generationVRFSigningKey- Ed25519 private key for creating VRF proofsVRFVerifyingKey- Ed25519 public key for verifying VRF proofsVRFProof- 80-byte VRF proofVRFOutput- 64-byte deterministic VRF outputVRFKeyPair- Convenience wrapper for signing and verifying keys
RawEncoder- Raw binary data (default)HexEncoder- Hexadecimal encodingBase64Encoder- Base64 encoding (via Foundation)
Swift-NaCL uses Swift's error handling mechanism. All cryptographic operations that can fail throw descriptive errors:
do {
let signingKey = try SigningKey(seed: invalidSeed)
} catch {
print("Failed to create signing key: \(error)")
}The library automatically handles secure memory cleanup for sensitive data like private keys. However, you should still follow security best practices:
- Don't log sensitive data
- Clear sensitive variables when no longer needed
- Use secure storage for long-term key storage
You can specify different encoders for input/output:
// Use hex encoding for keys
let hexKey = "0123456789abcdef..."
let signingKey = try SigningKey(seed: Data(hex: hexKey), encoder: HexEncoder.self)
// Sign with hex output
let signedMessage = try signingKey.sign(message: message, encoder: HexEncoder.self)Convert between Ed25519 and Curve25519 keys:
let signingKey = try SigningKey.generate()
let verifyKey = signingKey.verifyKey
// Convert to Curve25519 for encryption
let curve25519Private = try signingKey.toCurve25519PrivateKey()
let curve25519Public = try verifyKey.toCurve25519PublicKey()Generate VRF keys deterministically from a seed:
// Create a deterministic seed
let seedData = "my-deterministic-seed-32-bytes!".data(using: .utf8)!
let seed = try VRFSeed(bytes: seedData)
// Generate keys from seed - always the same for the same seed
let keyPair = try VRFKeyPair.from(seed: seed)
// Extract the original seed from a signing key
let originalSeed = keyPair.signingKey.seed
assert(originalSeed == seed)Extract VRF output directly from a proof (without verification):
let keyPair = VRFKeyPair.generate()
let message = "data".data(using: .utf8)!
let proof = try keyPair.signingKey.prove(message: message)
// Extract output directly from proof (unverified)
let directOutput = try proof.hash()
// Verify and extract output (verified)
let verifiedOutput = try keyPair.verifyingKey.verify(message: message, proof: proof)
// Both outputs are identical
assert(directOutput == verifiedOutput)For Linux builds, you can control libsodium usage:
# Use system libsodium
export SWIFT_NCAL_USE_SYSTEM_LIBSODIUM=1
swift build- libsodium - Core cryptographic library
- Base32 - Base32 encoding support
- BigInt - Large integer arithmetic
Uses precompiled libsodium framework (Clibsodium.xcframework) for optimal performance.
- Default: Uses bundled libsodium binaries for x86_64 and arm64
- System: Set
SWIFT_NCAL_USE_SYSTEM_LIBSODIUM=1to use system libsodium - Package managers: Supports apt, yum, and brew for libsodium installation
Run the test suite:
swift testTests cover:
- All cryptographic operations
- Key generation and validation
- Cross-platform compatibility
- Error conditions
- Memory safety
- Private Key Protection: Private keys and seeds must be kept secret and secure
- Nonce Uniqueness: Never reuse nonces with the same key
- Random Generation: Use cryptographically secure random number generation
- Memory Handling: Sensitive data is automatically cleared from memory
- Constant-time Operations: All comparisons use constant-time algorithms
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
This project follows the same license as the underlying libsodium library.