File Encryptor — Swift source
Encrypt or decrypt any file with AES-256-GCM in your browser. Password-based, zero server contact. Drag, drop, done.
This is the Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.
// file-encryptor — password-based AES-256-GCM file encryption.
//
// Language: Swift 5.9+ (Foundation + CryptoKit + CommonCrypto for PBKDF2)
// Ported from src/lib/file-encryptor.ts
// display source — part of CosmoDev's polyglot tool pages
//
// Wire format: salt (16 B) || IV (12 B) || AES-256-GCM ciphertext + tag.
// The 256-bit key is derived from the password with PBKDF2-SHA256 and a fresh
// random salt per encryption, so the same file + password never encrypts to
// the same bytes, and the password itself is never stored or derivable from
// the output.
import Foundation
import CryptoKit
import CommonCrypto
let saltLength = 16
let ivLength = 12
let iterations = 100_000
// GCM appends a 16-byte auth tag to the ciphertext; the smallest possible
// encrypted payload is therefore salt + IV + tag = 44 bytes.
let tagLength = 16
let minLength = saltLength + ivLength + tagLength
enum FileEncryptorError: Error, CustomStringConvertible {
case emptyPassword
case emptyInput
case tooShort
case keyDerivationFailed
case decryptionFailed
var description: String {
switch self {
case .emptyPassword: return "Password must not be empty."
case .emptyInput: return "Input data is empty - nothing to encrypt."
case .tooShort:
return "Input is too short to be an encrypted file (needs at least \(minLength) bytes: salt + IV + auth tag)."
case .keyDerivationFailed: return "PBKDF2 key derivation failed."
case .decryptionFailed: return "Decryption failed: wrong password or corrupted file."
}
}
}
/// PBKDF2-SHA256 (100k iterations) -> a CryptoKit AES-256 key.
func deriveKey(password: String, salt: [UInt8]) throws -> SymmetricKey {
let passwordBytes = Array(password.utf8)
var derived = [UInt8](repeating: 0, count: 32)
let status = salt.withUnsafeBufferPointer { saltPtr in
passwordBytes.withUnsafeBufferPointer { pwPtr in
CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
pwPtr.baseAddress, passwordBytes.count,
saltPtr.baseAddress, salt.count,
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256),
UInt32(iterations),
&derived, derived.count
)
}
}
guard status == kCCSuccess else { throw FileEncryptorError.keyDerivationFailed }
return SymmetricKey(data: Data(derived))
}
// SystemRandomNumberGenerator is backed by a cryptographically secure source
// on Apple platforms (arc4random / CSPRNG), so it stands in for crypto.getRandomValues.
private func randomBytes(_ count: Int) -> [UInt8] {
var rng = SystemRandomNumberGenerator()
return (0..<count).map { _ in UInt8.random(in: .min ... .max, using: &rng) }
}
/// Encrypt `data` under `password`. Returns salt || IV || ciphertext+tag.
func encryptFile(_ data: [UInt8], password: String) throws -> [UInt8] {
guard !password.isEmpty else { throw FileEncryptorError.emptyPassword }
guard !data.isEmpty else { throw FileEncryptorError.emptyInput }
let salt = randomBytes(saltLength)
let iv = randomBytes(ivLength)
let key = try deriveKey(password: password, salt: salt)
let sealed = try AES.GCM.seal(Data(data), using: key, nonce: AES.GCM.Nonce(data: Data(iv)))
// combined = ciphertext || 16-byte GCM tag (never nil: a nonce was supplied).
guard let combined = sealed.combined else { throw FileEncryptorError.decryptionFailed }
var out = [UInt8]()
out.reserveCapacity(saltLength + ivLength + combined.count)
out += salt
out += iv
out += [UInt8](combined)
return out
}
/// Decrypt a payload produced by `encryptFile`. Throws when the password is
/// wrong or the payload was corrupted/tampered (GCM auth-tag failure).
func decryptFile(_ data: [UInt8], password: String) throws -> [UInt8] {
guard !password.isEmpty else { throw FileEncryptorError.emptyPassword }
guard data.count >= minLength else { throw FileEncryptorError.tooShort }
let salt = Array(data[0..<saltLength])
let iv = Array(data[saltLength..<(saltLength + ivLength)])
let ciphertext = Array(data[(saltLength + ivLength)...])
let key = try deriveKey(password: password, salt: salt)
do {
let box = try AES.GCM.SealedBox(nonce: AES.GCM.Nonce(data: Data(iv)),
ciphertext: Data(ciphertext.dropLast(tagLength)),
tag: Data(ciphertext.suffix(tagLength)))
let plain = try AES.GCM.open(box, using: key)
return [UInt8](plain)
} catch {
// A GCM auth-tag failure means the key did not match (wrong password) or
// the payload was modified after encryption.
throw FileEncryptorError.decryptionFailed
}
}
Also available in 8 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →