File Encryptor — Zig source
Encrypt or decrypt any file with AES-256-GCM in your browser. Password-based, zero server contact. Drag, drop, done.
This is the Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.
//! file-encryptor — password-based AES-256-GCM file encryption.
//!
//! Language: Zig 0.14 (standard library only)
//! Ported from: src/lib/file-encryptor.ts (the canonical TypeScript implementation).
//! 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 data + password never encrypts to
//! the same bytes, and the password itself is never stored or derivable from
//! the output.
//!
//! The TS reference leans on the Web Crypto API; Zig's std.crypto provides the
//! same primitives natively (pbkdf2, Aes256Gcm, a CSPRNG), so this port keeps
//! the identical wire format without any third-party dependency.
const std = @import("std");
pub const salt_length: usize = 16;
pub const iv_length: usize = 12;
pub const iterations: u32 = 100_000;
/// GCM appends a 16-byte auth tag to the ciphertext; the smallest possible
/// encrypted payload is therefore salt + IV + tag = 44 bytes.
const tag_length: usize = 16;
pub const min_length: usize = salt_length + iv_length + tag_length;
const Aes256Gcm = std.crypto.aead.aes_gcm.Aes256Gcm;
const HmacSha256 = std.crypto.auth.hmac.sha2.HmacSha256;
pub const Error = error{
EmptyPassword,
EmptyInput,
InputTooShort,
WrongPasswordOrCorrupted,
};
/// Derive an AES-256 key from `password` + `salt` with PBKDF2-SHA256.
fn deriveKey(password: []const u8, salt: [salt_length]u8) [32]u8 {
var key: [32]u8 = undefined;
std.crypto.pwhash.pbkdf2(&key, password, &salt, iterations, HmacSha256) catch
unreachable; // fixed-size output can only fail on weak parameters
return key;
}
/// Encrypt `data` under `password`. Returns salt || IV || ciphertext+tag.
/// Caller owns the returned slice.
pub fn encryptFile(
allocator: std.mem.Allocator,
data: []const u8,
password: []const u8,
) Error![]u8 {
if (password.len == 0) return Error.EmptyPassword;
if (data.len == 0) return Error.EmptyInput;
var salt: [salt_length]u8 = undefined;
var iv: [iv_length]u8 = undefined;
std.crypto.random.bytes(&salt);
std.crypto.random.bytes(&iv);
const key = deriveKey(password, salt);
const out = allocator.alloc(u8, salt_length + iv_length + data.len + tag_length) catch
return Error.WrongPasswordOrCorrupted; // allocation failure surface
@memcpy(out[0..salt_length], &salt);
@memcpy(out[salt_length .. salt_length + iv_length], &iv);
// AES-256-GCM in-place: ciphertext follows the header, tag at the end.
var tag: [tag_length]u8 = undefined;
Aes256Gcm.encrypt(
out[salt_length + iv_length ..][0..data.len],
&tag,
data,
"", // no associated data
iv, // npub
key,
) catch return Error.WrongPasswordOrCorrupted;
@memcpy(out[out.len - tag_length ..], &tag);
return out;
}
/// Decrypt a payload produced by `encryptFile`. Fails when the password is
/// wrong or the payload was corrupted/tampered (GCM auth-tag failure).
/// Caller owns the returned slice.
pub fn decryptFile(
allocator: std.mem.Allocator,
data: []const u8,
password: []const u8,
) Error![]u8 {
if (password.len == 0) return Error.EmptyPassword;
if (data.len < min_length) return Error.InputTooShort;
var salt: [salt_length]u8 = undefined;
@memcpy(&salt, data[0..salt_length]);
var iv: [iv_length]u8 = undefined;
@memcpy(&iv, data[salt_length .. salt_length + iv_length]);
const ciphertext = data[salt_length + iv_length .. data.len - tag_length];
var tag: [tag_length]u8 = undefined;
@memcpy(&tag, data[data.len - tag_length ..]);
const key = deriveKey(password, salt);
const plain = allocator.alloc(u8, ciphertext.len) catch
return Error.WrongPasswordOrCorrupted; // allocation failure surface
errdefer allocator.free(plain);
// A GCM auth-tag failure means the key did not match (wrong password) or
// the payload was modified after encryption.
Aes256Gcm.decrypt(plain, ciphertext, tag, "", iv, key) catch
return Error.WrongPasswordOrCorrupted;
return plain;
}
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 →