swift-ejson is a Swift implementation of Shopify's EJSON library for managing encrypted secrets in source control. The library module is named EJSONKit following Apple-style framework naming conventions.
Reference Implementation: https://github.com/Shopify/ejson
Remember to bump the version! Before starting work on a new feature or bug fix, update the VERSION file at the project root with the new version number. This ensures the release workflow can create a new release when the changes are merged.
Provide a native Swift library that can:
- Encrypt and decrypt individual values using NaCl cryptography
- Process JSON files with selective encryption of sensitive values
- Maintain format compatibility with the Go EJSON implementation
- Enable secure secret management in Swift-based projects
import EJSONKit
// Key generation
let keyPair: EJSONKit.KeyPair = try EJSONKit.generateKeyPair()
// Value encryption/decryption
let encrypted = try EJSONKit.encrypt("secret", publicKey: key)
let decrypted = try EJSONKit.decrypt(encrypted, privateKey: key)
// File operations
let manager = EJSONKit.Manager()
try manager.encryptFile(at: path, publicKey: key)-
Key Management
- Generate keypairs (32-byte Curve25519 keys)
- Format: 64-character hex strings
- Functions:
generateKeyPair() -> (publicKey: String, privateKey: String)
-
Value Encryption/Decryption
- Encrypt individual string values
- Format:
EJ[1:ephemeral_pk:nonce:ciphertext] - Base64 encoding for binary data
- NaCl Box encryption (Curve25519 + Salsa20 + Poly1305)
-
Recursive JSON Processing
- Walk JSON tree and encrypt string values
- Preserve structure (objects, arrays, types)
- Special handling for
_public_keyfield (never encrypt) - Support nested structures
-
File Operations
- Read/write EJSON files
- Validate format
- Extract public key from files
{
"_public_key": "63ccf05a9492e68e12eeb1c705888aebdcc0080af7e594fc402beb24cce9d14f",
"database_password": "EJ[1:yF4JKMR4RUJY0hcxKYKDOg==:Yw6rqhvtLx7Kdc1hGtxqPBnx9bxk8kAzTCGNZPwVU5c=:ZCaH/xShYQ==]",
"nested": {
"secret": "EJ[1:x7F9KMTR5RUJZ1ida9KDPh==:Zw7sqiwuMy8Ldc2iHuyqQCoy0cyl9lB0UDHOaQxWV6d=:ADcI/yTiZR==]"
}
}EJ[1:ephemeral_pk:nonce:ciphertext]
1: Version numberephemeral_pk: Base64-encoded ephemeral public key (32 bytes)nonce: Base64-encoded nonce (24 bytes)ciphertext: Base64-encoded encrypted data
- Repository: https://github.com/jedisct1/swift-sodium
- Provides: NaCl cryptography primitives
- Includes: Precompiled Clibsodium.xcframework
- No external tools required
-
Must be compatible with Go EJSON
- Encrypted files must decrypt with Go ejson
- Files encrypted by Go ejson must decrypt in Swift
- Identical format for all components
-
Format Compliance
- Exact
EJ[1:...]format - Base64 encoding (standard, not URL-safe)
- 64-character hex for public keys
- Preserve
_public_keyfield
- Exact
- Start with single-value encryption/decryption
- Create test files with Go ejson, decrypt in Swift
- Create test files in Swift, decrypt with Go ejson
- Test recursive JSON processing
- Test edge cases: unicode, nested structures, arrays
- Invalid key formats
- Corrupted encrypted data
- Format mismatches
- Missing
_public_keyfield - Type preservation errors
- Repository:
swift-ejson(follows Swift package conventions) - Module:
EJSONKit(Apple-style framework naming) - Rationale: Professional feel, clean imports, unlikely conflicts
public struct EJSON {
private let sodium = Sodium()
public func generateKeyPair() throws -> (publicKey: String, privateKey: String)
public func encrypt(_ plaintext: String, publicKey: String) throws -> String
public func decrypt(_ ciphertext: String, privateKey: String) throws -> String
public func encryptFile(at path: String, publicKey: String) throws
public func decryptFile(at path: String, privateKey: String) throws -> [String: Any]
}- Start small: Basic encrypt/decrypt of single values
- Verify compatibility: Cross-test with Go ejson
- Build incrementally: Add recursive JSON support
- Test extensively: Use Go ejson as reference
- Format compatibility - Must match Go implementation exactly
- Recursive encryption - Walk JSON tree correctly
- Type preservation - Distinguish encrypted vs. plain values
- Error handling - Graceful handling of invalid data
- Public key handling - Never encrypt
_public_keyfield
- EJSON Go Library: https://github.com/Shopify/ejson
- EJSON Format Specification: https://github.com/Shopify/ejson/blob/master/DESIGN.md
- NaCl Cryptography: https://nacl.cr.yp.to/
- swift-sodium: https://github.com/jedisct1/swift-sodium
Implementation Complete - Branch: claude/implement-ejson-swift-assessment-01GMRgL5Ckidt55G3FKEUSic
The library has been fully implemented with:
- Complete EJSON encryption/decryption functionality
- Comprehensive test suite (25+ test cases)
- Full documentation and examples
- Format compatibility with Go EJSON
This project requires Swift 6.2 or later.
Linux only: Install libsodium-dev (required for EJSONKit cryptography on Linux):
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install -y libsodium-devmacOS: No additional dependencies needed. The swift-sodium package includes bundled libsodium (Clibsodium.xcframework) for Apple platforms.
This is the most reliable method for getting Swift 6.2.1.
For Ubuntu 22.04/24.04 (x86_64):
# Download Swift 6.2.1
cd /tmp
wget https://download.swift.org/swift-6.2.1-release/ubuntu2204/swift-6.2.1-RELEASE/swift-6.2.1-RELEASE-ubuntu22.04.tar.gz
# Extract
tar xzf swift-6.2.1-RELEASE-ubuntu22.04.tar.gz
# Option A: Install system-wide (requires sudo)
sudo mv swift-6.2.1-RELEASE-ubuntu22.04 /opt/swift
echo 'export PATH=/opt/swift/usr/bin:$PATH' >> ~/.bashrc
# Option B: Install to user directory (no sudo required)
mkdir -p ~/.local/swift
mv swift-6.2.1-RELEASE-ubuntu22.04 ~/.local/swift/6.2.1
echo 'export PATH=$HOME/.local/swift/6.2.1/usr/bin:$PATH' >> ~/.bashrc
# Reload shell configuration
source ~/.bashrc
# Verify installation
swift --version
# Should output: Swift version 6.2.1 (swift-6.2.1-RELEASE)For macOS:
# Install Xcode Command Line Tools (includes Swift 6.2+)
xcode-select --install
# Or download the latest Swift toolchain from:
# https://www.swift.org/install/macos/
# and follow the installer instructionsSwiftly is the official Swift toolchain manager for Linux. Note that it requires network access and may have connectivity issues in some environments.
# Download and install Swiftly
curl -O https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz
tar zxf swiftly-$(uname -m).tar.gz
echo "Y" | ./swiftly init --skip-install --quiet-shell-followup
# Load Swiftly environment
source "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh"
hash -r
# Install Swift 6.2 (requires network access)
swiftly install latest
# Verify installation
swift --version# Install swiftenv
git clone https://github.com/kylef/swiftenv.git ~/.swiftenv
# Add to PATH
echo 'export SWIFTENV_ROOT="$HOME/.swiftenv"' >> ~/.bashrc
echo 'export PATH="$SWIFTENV_ROOT/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(swiftenv init -)"' >> ~/.bashrc
source ~/.bashrc
# Install Swift 6.2
swiftenv install 6.2
swiftenv global 6.2Swift requires several system libraries on Linux:
Ubuntu/Debian:
sudo apt-get update
sudo apt-get install -y \
binutils \
git \
gnupg2 \
libc6-dev \
libcurl4-openssl-dev \
libedit2 \
libpython3.8 \
libsqlite3-0 \
libxml2-dev \
libz3-dev \
pkg-config \
tzdata \
unzip \
zlib1g-devOnce Swift and libsodium are installed:
cd /home/user/swift-ejson
swift buildExpected output:
Fetching https://github.com/jedisct1/swift-sodium.git
Fetched https://github.com/jedisct1/swift-sodium.git from cache
Computing version for https://github.com/jedisct1/swift-sodium.git
Computed https://github.com/jedisct1/swift-sodium.git at 0.9.1
Building for debugging...
[2/3] Emitting module EJSONKit
warning: Using a system installation of libsodium - This is unsupported.
[3/3] Compiling EJSONKit EJSON.swift
Build complete! (2.00s)
Note: On Linux, you may see a warning "Using a system installation of libsodium - This is unsupported." This is expected and safe to ignore. The library will work correctly with the system-installed libsodium. On macOS, the bundled Clibsodium.xcframework is used automatically with no warnings.
swift testExpected output:
Test Suite 'All tests' started at...
Test Suite 'EJSONKitTests' started at...
[... 30 test cases ...]
Test Suite 'All tests' passed at...
Executed 30 tests, with 0 failures (0 unexpected) in 0.294 seconds
All 30 tests should pass, including:
- Key generation and validation tests
- Encryption/decryption tests
- JSON processing tests
- File operations tests
- Unicode and special character handling
- Performance benchmarks
# Run only basic encryption tests
swift test --filter testBasicEncryptDecrypt
# Run with verbose output
swift test -vswift build -c releaseswift package generate-xcodeproj
open swift-ejson.xcodeprojTo verify format compatibility with the Go implementation:
go install github.com/Shopify/ejson/cmd/ejson@latestejson keygen
# Output:
# Public Key: abc123...
# Private Key: def456...cat > test.json <<EOF
{
"_public_key": "YOUR_PUBLIC_KEY_HERE",
"password": "secret123",
"api_key": "my_api_key"
}
EOF
ejson encrypt test.jsonimport EJSONKit
let ejson = EJSON()
let secrets = try ejson.decryptFile(
at: "test.json",
privateKey: "YOUR_PRIVATE_KEY_HERE"
)
print(secrets["password"]) // Should print: secret123let keyPair = try EJSON.generateKeyPair()
// Create file with keyPair.publicKey
try ejson.encryptFile(at: "swift_encrypted.json", publicKey: keyPair.publicKey)# Use the private key from Swift
ejson decrypt swift_encrypted.jsonError: error: failed to clone https://github.com/jedisct1/swift-sodium.git
- Solution: Check internet connectivity and GitHub access
Error: cannot find 'sodium_init' in scope
- Solution: The dependency didn't download correctly. Try:
rm -rf .build swift package resolve swift build
Error: sodiumInitializationFailed
- Solution: Libsodium isn't properly linked. Reinstall swift-sodium dependency.
Error: Network-related test failures
- Some tests create temporary files. Ensure
/tmpis writable:ls -ld /tmp chmod 1777 /tmp # If needed
Run performance tests:
swift test --filter testPerformanceTypical results on modern hardware:
- Key generation: ~0.5ms per keypair
- Value encryption: ~0.1ms per value
- Value decryption: ~0.1ms per value
- File encryption (1KB): ~5ms
- File decryption (1KB): ~5ms
This library will be integrated with Xproject's secret management system (EJSONService) to provide standardized EJSON encryption/decryption capabilities.