Text Encryptor — C++ source
Encrypt or decrypt text with a password using AES-256-GCM. Share the ciphertext safely - only someone with the password can read it.
This is the C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.
// text-encryptor — password-based text encryption (AES-256-GCM + PBKDF2).
//
// Language: C++ (C++17, standard library only; crypto primitives implemented below)
// Ported from src/lib/text-encryptor.ts (the canonical TypeScript implementation).
// display source — part of CosmoDev's polyglot tool pages.
//
// Format: PBKDF2 (SHA-256, 100k iterations, 16-byte random salt) derives an
// AES-256 key from the password; AES-GCM encrypts with a 12-byte random IV.
// The Base64 output packs salt + IV + ciphertext (+ GCM tag), so every
// encryption is unique and self-contained - decrypt needs only the string and
// the password.
//
// The TS reference uses the browser's SubtleCrypto; C++ has no standard crypto
// facility, so the primitives are implemented here in portable C++.
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <random>
#include <stdexcept>
#include <string>
#include <vector>
namespace text_encryptor {
using Bytes = std::vector<uint8_t>;
constexpr uint32_t PBKDF2_ITERATIONS = 100000;
constexpr std::size_t SALT_BYTES = 16;
constexpr std::size_t IV_BYTES = 12;
/// Smallest valid packed payload: salt + IV + one AES-GCM block (tag).
constexpr std::size_t MIN_BYTES = SALT_BYTES + IV_BYTES + 16;
const char* const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
// --- Base64 codec (pure - no platform btoa, so it runs anywhere) ---------------
/** Bytes -> canonical Base64. */
std::string bytesToBase64(const Bytes& bytes) {
std::string out;
out.reserve((bytes.size() + 2) / 3 * 4);
for (std::size_t i = 0; i < bytes.size(); i += 3) {
uint8_t b0 = bytes[i];
uint8_t b1 = i + 1 < bytes.size() ? bytes[i + 1] : 0;
uint8_t b2 = i + 2 < bytes.size() ? bytes[i + 2] : 0;
out += ALPHABET[b0 >> 2];
out += ALPHABET[((b0 & 0x03) << 4) | (b1 >> 4)];
out += i + 1 < bytes.size() ? ALPHABET[((b1 & 0x0f) << 2) | (b2 >> 6)] : '=';
out += i + 2 < bytes.size() ? ALPHABET[b2 & 0x3f] : '=';
}
return out;
}
static int b64Value(char c) {
const char* p = std::strchr(ALPHABET, c);
return c == '+' || (p && p - ALPHABET < 64) ? int(p - ALPHABET) : -1;
}
/** Base64 -> bytes. Accepts surrounding whitespace; throws on any other
* non-alphabet character, wrong length, or misplaced padding. */
Bytes base64ToBytes(const std::string& b64) {
std::string clean;
for (char c : b64) {
if (!std::isspace(static_cast<unsigned char>(c))) clean += c;
}
if (clean.empty()) throw std::runtime_error("Invalid Base64: input is empty");
if (clean.size() % 4 != 0) {
throw std::runtime_error("Invalid Base64: length must be a multiple of 4");
}
for (char c : clean) {
bool ok = std::isalnum(static_cast<unsigned char>(c)) || c == '+' || c == '/';
if (!ok && c != '=') {
throw std::runtime_error(std::string("Invalid Base64: unexpected character \"") + c + "\"");
}
}
std::size_t outLength = (clean.size() / 4) * 3;
if (clean.size() >= 2 && clean.compare(clean.size() - 2, 2, "==") == 0) outLength -= 2;
else if (!clean.empty() && clean.back() == '=') outLength -= 1;
Bytes bytes(outLength);
std::size_t p = 0;
for (std::size_t i = 0; i < clean.size(); i += 4) {
int c0 = b64Value(clean[i]);
int c1 = b64Value(clean[i + 1]);
int c2 = clean[i + 2] == '=' ? -1 : b64Value(clean[i + 2]);
int c3 = clean[i + 3] == '=' ? -1 : b64Value(clean[i + 3]);
if (p < outLength) bytes[p++] = uint8_t((c0 << 2) | (c1 >> 4));
if (c2 != -1 && p < outLength) bytes[p++] = uint8_t(((c1 & 0x0f) << 4) | (c2 >> 2));
if (c2 != -1 && c3 != -1 && p < outLength) bytes[p++] = uint8_t(((c2 & 0x03) << 6) | c3);
}
return bytes;
}
// --- SHA-256 / HMAC / PBKDF2 ----------------------------------------------------
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 = uint64_t(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;
uint64_t len_ = 0;
};
static 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;
}
/** PBKDF2-SHA256 (100k iterations) -> 32-byte AES-256 key. */
Bytes deriveKey(const std::string& password, const Bytes& salt) {
Bytes out;
uint32_t block = 1;
while (out.size() < 32) {
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 < PBKDF2_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(32);
return out;
}
// --- AES-256-GCM ------------------------------------------------------------------
class Aes256 {
public:
explicit Aes256(const Bytes& key) {
for (int i = 0; i < 8; i++) {
roundKeys_[i] = (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 = 1;
for (int i = 8; i < 60; i++) {
uint32_t temp = roundKeys_[i - 1];
if (i % 8 == 0) {
temp = (temp << 8) | (temp >> 24);
temp = subWord(temp) ^ (rcon << 24);
rcon = uint32_t(((rcon << 1) & 0xff) ^ ((rcon & 0x80) ? 0x1b : 0));
} else if (i % 8 == 4) {
temp = subWord(temp);
}
roundKeys_[i] = roundKeys_[i - 8] ^ 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 rotl8(uint8_t v, int n) { return uint8_t((v << n) | (v >> (8 - n))); }
static uint8_t sbox(uint8_t x) {
static uint8_t table[256];
static bool init = false;
if (!init) {
uint8_t p = 1, q = 1;
do {
p = uint8_t(p ^ ((p << 1) & 0xff) ^ ((p & 0x80) ? 0x1b : 0));
q ^= uint8_t(q << 1);
q ^= uint8_t(q << 2);
q ^= uint8_t(q << 4);
if (q & 0x80) q ^= 0x09;
table[p] = q ^ rotl8(q, 1) ^ rotl8(q, 2) ^ rotl8(q, 3) ^ rotl8(q, 4) ^ 0x63;
} while (p != 1);
table[0] = 0x63;
init = true;
}
return table[x];
}
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];
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;
t = s[1]; s[1] = s[5]; s[5] = s[9]; s[9] = s[13]; s[13] = t;
t = s[2]; s[2] = s[10]; s[10] = t; t = s[6]; s[6] = s[14]; s[14] = t;
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_{};
};
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);
}
/** CTR-encrypt (also decrypts) + GHASH + tag: ciphertext || 16-byte tag. */
Bytes aesGcmEncrypt(const Bytes& key, const Bytes& iv, const Bytes& plaintext) {
Aes256 aes(key);
uint8_t H[16] = {0};
aes.encryptBlock(H, H);
uint8_t J0[16] = {0};
std::memcpy(J0, iv.data(), IV_BYTES);
J0[15] = 1;
Bytes out = plaintext;
uint8_t counter[16];
std::memcpy(counter, J0, 16);
for (std::size_t off = 0; off < plaintext.size(); off += 16) {
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[off + j] ^= ks[j];
}
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.size(); off += 16) {
uint8_t block[16] = {0};
std::memcpy(block, out.data() + off, std::min(std::size_t(16), out.size() - off));
ghashBlock(block);
}
uint8_t lenBlock[16] = {0};
uint64_t cBits = uint64_t(out.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.insert(out.end(), tag, tag + 16);
return out;
}
static Bytes aesGcmDecrypt(const Bytes& key, const Bytes& iv, const Bytes& tagged) {
if (tagged.size() < 16) {
throw std::runtime_error("Decryption failed - wrong password or corrupted ciphertext");
}
std::size_t cLen = tagged.size() - 16;
Bytes body(tagged.begin(), tagged.begin() + cLen);
Bytes re = aesGcmEncrypt(key, iv, body);
for (std::size_t i = 0; i < 16; i++) {
if (re[cLen + i] != tagged[cLen + i]) {
throw std::runtime_error("Decryption failed - wrong password or corrupted ciphertext");
}
}
re.resize(cLen);
return re;
}
// --- CSPRNG + UTF-8 ---------------------------------------------------------------
static Bytes randomBytes(std::size_t n) {
Bytes out(n);
std::random_device rd; // OS entropy (getrandom/arc4random) on mainstream platforms
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;
}
// std::string already carries arbitrary bytes; treat UTF-8 text as bytes
// (std::string::size() counts bytes, matching the TS TextEncoder output).
// --- Public API --------------------------------------------------------------------
/**
* Encrypt UTF-8 `plaintext` under `password` and return the Base64 string
* packing salt + IV + AES-256-GCM ciphertext. Throws if either input is empty.
*/
std::string encryptText(const std::string& plaintext, const std::string& password) {
if (plaintext.empty()) throw std::runtime_error("Nothing to encrypt: input is empty");
if (password.empty()) throw std::runtime_error("Password must not be empty");
Bytes salt = randomBytes(SALT_BYTES);
Bytes iv = randomBytes(IV_BYTES);
Bytes key = deriveKey(password, salt);
Bytes plain(plaintext.begin(), plaintext.end());
Bytes tagged = aesGcmEncrypt(key, iv, plain);
Bytes packed;
packed.reserve(SALT_BYTES + IV_BYTES + tagged.size());
packed.insert(packed.end(), salt.begin(), salt.end());
packed.insert(packed.end(), iv.begin(), iv.end());
packed.insert(packed.end(), tagged.begin(), tagged.end());
return bytesToBase64(packed);
}
/**
* Decrypt a Base64 string produced by `encryptText` back to plaintext. Throws
* on empty input/password, invalid Base64, a payload too short to be one of
* ours, or a GCM authentication failure (wrong password / tampered data).
*/
std::string decryptText(const std::string& ciphertext, const std::string& password) {
auto isBlank = [](const std::string& s) {
for (char c : s) {
if (!std::isspace(static_cast<unsigned char>(c))) return false;
}
return true;
};
if (isBlank(ciphertext)) throw std::runtime_error("Nothing to decrypt: input is empty");
if (password.empty()) throw std::runtime_error("Password must not be empty");
std::string trimmed;
for (char c : ciphertext) {
if (!std::isspace(static_cast<unsigned char>(c))) trimmed += c;
}
Bytes packed = base64ToBytes(trimmed);
if (packed.size() < MIN_BYTES) {
throw std::runtime_error("Ciphertext too short - not a valid salt + IV + AES-GCM payload");
}
Bytes salt(packed.begin(), packed.begin() + SALT_BYTES);
Bytes iv(packed.begin() + SALT_BYTES, packed.begin() + SALT_BYTES + IV_BYTES);
Bytes data(packed.begin() + SALT_BYTES + IV_BYTES, packed.end());
Bytes key = deriveKey(password, salt);
Bytes plain = aesGcmDecrypt(key, iv, data);
return std::string(plain.begin(), plain.end());
}
} // namespace text_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 →