File Encryptor — C++ source
Encrypt or decrypt any file with AES-256-GCM in your browser. Password-based, zero server contact. Drag, drop, done.
This is the C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.
// file-encryptor — password-based AES-256-GCM file encryption.
//
// Language: C++ (C++17, standard library only; crypto primitives implemented below)
// 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 file + password never encrypts to
// the same bytes, and the password itself is never stored or derivable from
// the output.
//
// The TS reference uses the browser's Web Crypto (SubtleCrypto); C++ has no
// standard crypto facility, so SHA-256, HMAC, PBKDF2, AES-256 and GCM are
// implemented here in portable C++ with the same test vectors in mind.
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <random>
#include <stdexcept>
#include <string>
#include <vector>
namespace file_encryptor {
using Bytes = std::vector<uint8_t>;
constexpr std::size_t SALT_LENGTH = 16;
constexpr std::size_t IV_LENGTH = 12;
constexpr uint32_t ITERATIONS = 100000;
// GCM appends a 16-byte auth tag to the ciphertext; the smallest possible
// encrypted payload is therefore salt + IV + tag = 44 bytes.
constexpr std::size_t TAG_LENGTH = 16;
constexpr std::size_t MIN_LENGTH = SALT_LENGTH + IV_LENGTH + TAG_LENGTH;
// --- SHA-256 -----------------------------------------------------------------
class Sha256 {
public:
Sha256() { reset(); }
void reset() {
h_ = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
len_ = 0;
buffered_ = 0;
}
void update(const uint8_t* data, std::size_t n) {
len_ += n;
while (n > 0) {
std::size_t take = std::min(n, std::size_t(64) - buffered_);
std::memcpy(buf_.data() + buffered_, data, take);
buffered_ += take;
data += take;
n -= take;
if (buffered_ == 64) {
transform(buf_.data());
buffered_ = 0;
}
}
}
void final(uint8_t out[32]) {
uint64_t bitLen = len_ * 8;
uint8_t pad = 0x80;
update(&pad, 1);
uint8_t zero = 0;
while (buffered_ != 56) update(&zero, 1);
uint8_t lenBytes[8];
for (int i = 0; i < 8; i++) lenBytes[i] = uint8_t(bitLen >> (56 - 8 * i));
update(lenBytes, 8);
for (int i = 0; i < 8; i++) {
out[i * 4] = uint8_t(h_[i] >> 24);
out[i * 4 + 1] = uint8_t(h_[i] >> 16);
out[i * 4 + 2] = uint8_t(h_[i] >> 8);
out[i * 4 + 3] = uint8_t(h_[i]);
}
}
private:
static uint32_t rotr(uint32_t x, int n) { return (x >> n) | (x << (32 - n)); }
void transform(const uint8_t block[64]) {
static const uint32_t K[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
uint32_t w[64];
for (int i = 0; i < 16; i++) {
w[i] = (uint32_t(block[i * 4]) << 24) | (uint32_t(block[i * 4 + 1]) << 16) |
(uint32_t(block[i * 4 + 2]) << 8) | uint32_t(block[i * 4 + 3]);
}
for (int i = 16; i < 64; i++) {
uint32_t s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ (w[i - 15] >> 3);
uint32_t s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ (w[i - 2] >> 10);
w[i] = w[i - 16] + s0 + w[i - 7] + s1;
}
uint32_t a = h_[0], b = h_[1], c = h_[2], d = h_[3];
uint32_t e = h_[4], f = h_[5], g = h_[6], h = h_[7];
for (int i = 0; i < 64; i++) {
uint32_t S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
uint32_t ch = (e & f) ^ (~e & g);
uint32_t t1 = h + S1 + ch + K[i] + w[i];
uint32_t S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
uint32_t t2 = S0 + maj;
h = g; g = f; f = e; e = d + t1;
d = c; c = b; b = a; a = t1 + t2;
}
h_[0] += a; h_[1] += b; h_[2] += c; h_[3] += d;
h_[4] += e; h_[5] += f; h_[6] += g; h_[7] += h;
}
std::array<uint32_t, 8> h_;
std::array<uint8_t, 64> buf_;
std::size_t buffered_ = 0;
std::size_t len_ = 0;
};
// --- HMAC-SHA256 + PBKDF2 ----------------------------------------------------
Bytes hmacSha256(const Bytes& key, const Bytes& data) {
uint8_t k[64] = {0};
Bytes kCopy = key;
if (kCopy.size() > 64) {
Sha256 big;
big.update(kCopy.data(), kCopy.size());
Bytes digest(32);
big.final(digest.data());
kCopy = digest;
}
std::memcpy(k, kCopy.data(), kCopy.size());
uint8_t ipad[64], opad[64];
for (int i = 0; i < 64; i++) {
ipad[i] = k[i] ^ 0x36;
opad[i] = k[i] ^ 0x5c;
}
Sha256 inner;
inner.update(ipad, 64);
inner.update(data.data(), data.size());
uint8_t idigest[32];
inner.final(idigest);
Sha256 outer;
outer.update(opad, 64);
outer.update(idigest, 32);
Bytes out(32);
outer.final(out.data());
return out;
}
Bytes pbkdf2Sha256(const std::string& password, const Bytes& salt,
uint32_t iterations, std::size_t outLen) {
Bytes out;
uint32_t block = 1;
while (out.size() < outLen) {
Bytes saltBlock = salt;
saltBlock.push_back(uint8_t(block >> 24));
saltBlock.push_back(uint8_t(block >> 16));
saltBlock.push_back(uint8_t(block >> 8));
saltBlock.push_back(uint8_t(block));
Bytes u = hmacSha256(Bytes(password.begin(), password.end()), saltBlock);
Bytes t = u;
for (uint32_t i = 1; i < iterations; i++) {
u = hmacSha256(Bytes(password.begin(), password.end()), u);
for (std::size_t j = 0; j < 32; j++) t[j] ^= u[j];
}
out.insert(out.end(), t.begin(), t.end());
block++;
}
out.resize(outLen);
return out;
}
// --- AES-256 (encrypt-only block cipher — all GCM needs) ---------------------
class Aes256 {
public:
explicit Aes256(const Bytes& key) {
static const uint32_t RCON = 0x01;
for (int i = 0; i < 8; i++) {
roundKeys_[i][0] = (uint32_t(key[i * 4]) << 24) | (uint32_t(key[i * 4 + 1]) << 16) |
(uint32_t(key[i * 4 + 2]) << 8) | uint32_t(key[i * 4 + 3]);
}
uint32_t rcon = RCON;
for (int i = 8; i < 60; i++) {
uint32_t temp = roundKeys_[i - 1][0];
if (i % 8 == 0) {
temp = (temp << 8) | (temp >> 24); // RotWord
temp = subWord(temp) ^ (rcon << 24);
rcon = (rcon << 1) ^ ((rcon & 0x80) ? 0x11b : 0);
} else if (i % 8 == 4) {
temp = subWord(temp);
}
roundKeys_[i][0] = roundKeys_[i - 8][0] ^ temp;
}
}
void encryptBlock(const uint8_t in[16], uint8_t out[16]) const {
uint8_t s[16];
std::memcpy(s, in, 16);
addRoundKey(s, 0);
for (int round = 1; round < 14; round++) {
subBytes(s);
shiftRows(s);
mixColumns(s);
addRoundKey(s, round);
}
subBytes(s);
shiftRows(s);
addRoundKey(s, 14);
std::memcpy(out, s, 16);
}
private:
static uint8_t sbox(uint8_t x) {
// Computed S-box via multiplicative inverse in GF(2^8) would be slow to
// spell out; the table is the standard FIPS-197 one, generated compactly
// below by affine transform of the inverse (still constant work).
static uint8_t table[256];
static bool init = false;
if (!init) {
uint8_t p = 1, q = 1;
do {
// p advances by 3 (generator), q by its inverse so p*q == 1
p = uint8_t(p ^ ((p << 1) & 0xff) ^ ((p & 0x80) ? 0x1b : 0));
q ^= uint8_t(q << 1); // q advances by inverse-generator steps
q ^= uint8_t(q << 2);
q ^= uint8_t(q << 4);
if (q & 0x80) q ^= 0x09;
uint8_t xformed = q ^ rotl8(q, 1) ^ rotl8(q, 2) ^ rotl8(q, 3) ^ rotl8(q, 4);
table[p] = xformed ^ 0x63;
} while (p != 1);
table[0] = 0x63;
init = true;
}
return table[x];
}
static uint8_t rotl8(uint8_t v, int n) { return uint8_t((v << n) | (v >> (8 - n))); }
static uint32_t subWord(uint32_t w) {
return (uint32_t(sbox(uint8_t(w >> 24))) << 24) | (uint32_t(sbox(uint8_t(w >> 16))) << 16) |
(uint32_t(sbox(uint8_t(w >> 8))) << 8) | sbox(uint8_t(w));
}
void addRoundKey(uint8_t s[16], int round) const {
for (int col = 0; col < 4; col++) {
uint32_t rk = roundKeys_[round * 4 + col][0];
s[col * 4] ^= uint8_t(rk >> 24);
s[col * 4 + 1] ^= uint8_t(rk >> 16);
s[col * 4 + 2] ^= uint8_t(rk >> 8);
s[col * 4 + 3] ^= uint8_t(rk);
}
}
void subBytes(uint8_t s[16]) const {
for (int i = 0; i < 16; i++) s[i] = sbox(s[i]);
}
static void shiftRows(uint8_t s[16]) {
uint8_t t;
// Row 1: rotate left by 1 (indices 1,5,9,13)
t = s[1]; s[1] = s[5]; s[5] = s[9]; s[9] = s[13]; s[13] = t;
// Row 2: rotate left by 2
t = s[2]; s[2] = s[10]; s[10] = t; t = s[6]; s[6] = s[14]; s[14] = t;
// Row 3: rotate left by 3 (== right by 1)
t = s[15]; s[15] = s[11]; s[11] = s[7]; s[7] = s[3]; s[3] = t;
}
static uint8_t xtime(uint8_t a) {
return uint8_t((a << 1) ^ ((a & 0x80) ? 0x1b : 0));
}
static void mixColumns(uint8_t s[16]) {
for (int c = 0; c < 4; c++) {
uint8_t* col = s + c * 4;
uint8_t a0 = col[0], a1 = col[1], a2 = col[2], a3 = col[3];
uint8_t all = a0 ^ a1 ^ a2 ^ a3;
col[0] ^= all ^ xtime(a0 ^ a1);
col[1] ^= all ^ xtime(a1 ^ a2);
col[2] ^= all ^ xtime(a2 ^ a3);
col[3] ^= all ^ xtime(a3 ^ a0);
}
}
std::array<uint32_t, 60> roundKeys_{};
};
// --- AES-256-GCM ---------------------------------------------------------------
// Multiply a by b in GF(2^128) with the GCM polynomial (bit-by-bit, MSB first).
static void gcmMul(uint8_t a[16], const uint8_t bIn[16]) {
uint8_t b[16], p[16] = {0};
std::memcpy(b, bIn, 16);
for (int i = 0; i < 128; i++) {
if (a[i / 8] & (0x80 >> (i % 8))) {
for (int j = 0; j < 16; j++) p[j] ^= b[j];
}
bool lsb = b[15] & 1;
for (int j = 15; j > 0; j--) b[j] = uint8_t((b[j] >> 1) | (b[j - 1] << 7));
b[0] >>= 1;
if (lsb) b[0] ^= 0xe1;
}
std::memcpy(a, p, 16);
}
struct GcmResult {
Bytes ciphertext; // ciphertext + 16-byte tag appended
};
static GcmResult aesGcmEncrypt(const Bytes& key, const Bytes& iv, const Bytes& plaintext) {
Aes256 aes(key);
uint8_t H[16] = {0};
aes.encryptBlock(H, H);
// J0 = IV || 0^31 || 1 for the 96-bit IV this format always uses.
uint8_t J0[16] = {0};
std::memcpy(J0, iv.data(), IV_LENGTH);
J0[15] = 1;
// CTR mode encryption starting at inc32(J0).
GcmResult out;
out.ciphertext = plaintext;
uint8_t counter[16];
std::memcpy(counter, J0, 16);
for (std::size_t off = 0; off < plaintext.size(); off += 16) {
// inc32
for (int i = 15; i >= 12; i--) {
if (++counter[i] != 0) break;
}
uint8_t ks[16];
aes.encryptBlock(counter, ks);
std::size_t n = std::min(std::size_t(16), plaintext.size() - off);
for (std::size_t j = 0; j < n; j++) out.ciphertext[off + j] ^= ks[j];
}
// GHASH over (empty AAD, C) with 64-bit length blocks (bit lengths).
uint8_t X[16] = {0};
auto ghashBlock = [&](const uint8_t block[16]) {
for (int i = 0; i < 16; i++) X[i] ^= block[i];
gcmMul(X, H);
};
for (std::size_t off = 0; off < out.ciphertext.size(); off += 16) {
uint8_t block[16] = {0};
std::memcpy(block, out.ciphertext.data() + off,
std::min(std::size_t(16), out.ciphertext.size() - off));
ghashBlock(block);
}
uint8_t lenBlock[16] = {0};
uint64_t cBits = uint64_t(out.ciphertext.size()) * 8;
for (int i = 0; i < 8; i++) lenBlock[8 + i] = uint8_t(cBits >> (56 - 8 * i));
ghashBlock(lenBlock);
uint8_t tag[16];
aes.encryptBlock(J0, tag);
for (int i = 0; i < 16; i++) tag[i] ^= X[i];
out.ciphertext.insert(out.ciphertext.end(), tag, tag + 16);
return out;
}
// Returns the plaintext, or throws on auth failure (wrong password / tamper).
static Bytes aesGcmDecrypt(const Bytes& key, const Bytes& iv, const Bytes& ciphertextWithTag) {
if (ciphertextWithTag.size() < TAG_LENGTH) {
throw std::runtime_error("Decryption failed: wrong password or corrupted file.");
}
std::size_t cLen = ciphertextWithTag.size() - TAG_LENGTH;
Bytes body(ciphertextWithTag.begin(), ciphertextWithTag.begin() + cLen);
GcmResult re = aesGcmEncrypt(key, iv, body); // GHASH is the same operation
for (std::size_t i = 0; i < TAG_LENGTH; i++) {
if (re.ciphertext[cLen + i] != ciphertextWithTag[cLen + i]) {
// A GCM auth-tag failure means the key did not match (wrong password) or
// the payload was modified after encryption.
throw std::runtime_error("Decryption failed: wrong password or corrupted file.");
}
}
return re.ciphertext; // CTR encryption with the same keystream decrypts
}
// --- CSPRNG --------------------------------------------------------------------
// std::random_device is backed by the OS entropy pool on every mainstream
// desktop/server platform (getrandom/arc4random) — the C++ stand-in for the
// browser's crypto.getRandomValues.
static Bytes randomBytes(std::size_t n) {
Bytes out(n);
std::random_device rd;
for (std::size_t i = 0; i < n; i += sizeof(unsigned)) {
unsigned v = rd();
for (std::size_t j = 0; j < sizeof(unsigned) && i + j < n; j++) {
out[i + j] = uint8_t(v >> (8 * j));
}
}
return out;
}
// --- Public API ------------------------------------------------------------------
/** Encrypt `data` under `password`. Returns salt || IV || ciphertext+tag. */
Bytes encryptFile(const Bytes& data, const std::string& password) {
if (password.empty()) throw std::runtime_error("Password must not be empty.");
if (data.empty()) throw std::runtime_error("Input data is empty - nothing to encrypt.");
Bytes salt = randomBytes(SALT_LENGTH);
Bytes iv = randomBytes(IV_LENGTH);
Bytes key = pbkdf2Sha256(password, salt, ITERATIONS, 32);
GcmResult enc = aesGcmEncrypt(key, iv, data);
Bytes out;
out.reserve(SALT_LENGTH + IV_LENGTH + enc.ciphertext.size());
out.insert(out.end(), salt.begin(), salt.end());
out.insert(out.end(), iv.begin(), iv.end());
out.insert(out.end(), enc.ciphertext.begin(), enc.ciphertext.end());
return out;
}
/**
* Decrypt a payload produced by `encryptFile`. Throws when the password is
* wrong or the payload was corrupted/tampered (GCM auth-tag failure).
*/
Bytes decryptFile(const Bytes& data, const std::string& password) {
if (password.empty()) throw std::runtime_error("Password must not be empty.");
if (data.size() < MIN_LENGTH) {
throw std::runtime_error("Input is too short to be an encrypted file (needs at least " +
std::to_string(MIN_LENGTH) +
" bytes: salt + IV + auth tag).");
}
Bytes salt(data.begin(), data.begin() + SALT_LENGTH);
Bytes iv(data.begin() + SALT_LENGTH, data.begin() + SALT_LENGTH + IV_LENGTH);
Bytes ciphertext(data.begin() + SALT_LENGTH + IV_LENGTH, data.end());
Bytes key = pbkdf2Sha256(password, salt, ITERATIONS, 32);
return aesGcmDecrypt(key, iv, ciphertext);
}
} // namespace file_encryptor
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 →