commit 5ec1a7af8a361b427543e2dfff7148430b4bae84 Author: rrryfoo Date: Fri Jan 16 20:51:39 2026 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..466e248 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +out/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4f25e13 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# love is a gentle thing +Velvet Ring by Big Thief.
+an encryption tool! + +## building +```sh +./build-static.sh # creates a static executable +./build.sh # creates a dynamic executable +``` + +## usage +```sh +ec-pass -p [-o ] +dec-pass -p [-o ] +ec-key [-o ] +dec-key [-o ] +``` diff --git a/build-static.sh b/build-static.sh new file mode 100644 index 0000000..32a7e61 --- /dev/null +++ b/build-static.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -e + +CXX=g++ +CXXFLAGS="-O2 -Wall -std=c++17" +LIBS="-ldl -lpthread -static" + +mkdir -p out + +$CXX $CXXFLAGS velvet_ring.cpp main.cpp libcrypto.a libssl.a -o out/liagt-static $LIBS + +echo "Build successful" diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..726af30 --- /dev/null +++ b/build.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -e + +CXX=g++ +CXXFLAGS="-O2 -Wall -std=c++17" +LIBS="-lssl -lcrypto" + +mkdir -p out + +$CXX $CXXFLAGS velvet_ring.cpp main.cpp -o out/liagt $LIBS + +echo "Build successful" diff --git a/libcrypto.a b/libcrypto.a new file mode 100644 index 0000000..235c5f8 Binary files /dev/null and b/libcrypto.a differ diff --git a/libssl.a b/libssl.a new file mode 100644 index 0000000..1170f8f Binary files /dev/null and b/libssl.a differ diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..70b14f6 --- /dev/null +++ b/main.cpp @@ -0,0 +1,151 @@ +#include +#include +#include +#include +#include +#include "velvet_ring.h" + +using ByteVec = std::vector; + +ByteVec read_file(const std::string path) { + std::ifstream f(path, std::ios::binary); + if (!f) throw std::runtime_error("failed to open: " + path); + return ByteVec((std::istreambuf_iterator(f)), + std::istreambuf_iterator()); +} + +void write_file(const std::string path, const ByteVec data) { + std::ofstream f(path, std::ios::binary); + if (!f) throw std::runtime_error("failed to write: " + path); + f.write(reinterpret_cast(data.data()), data.size()); +} + +bool ends_with(const std::string s, const std::string suf) { + return s.size() >= suf.size() && + s.compare(s.size() - suf.size(), suf.size(), suf) == 0; +} + +std::string enc_out(const std::string in) { + return in + ".vvt"; +} + +std::string dec_out(const std::string in) { + if (ends_with(in, ".vvt")) + return in.substr(0, in.size() - 4); + return in + ".dec"; +} + +std::string ring_name(const std::string& enc) { + return enc + ".ring"; +} + +std::string get_opt(int argc, char* argv[], const std::string opt) { + for (int i = 3; i < argc - 1; ++i) + if (argv[i] == opt) + return argv[i + 1]; + return ""; +} + + +std::string help = R"(love is a gentle thing(lx) by maki +v1.0 + +usage: + ec-pass -p [-o ] + dec-pass -p [-o ] + ec-key [-o ] + dec-key [-o ])"; + +void usage() { + std::cout << help << "\n"; +} + +int main(int argc, char* argv[]) { + if (argc < 3) { + int retcode = 1; + if (argc == 1) + retcode = 0; + usage(); + return retcode; + } + + std::string mode = argv[1]; + std::string in = argv[2]; + std::string out = get_opt(argc, argv, "-o"); + std::string pass = get_opt(argc, argv, "-p"); + + try { + ByteVec input = read_file(in); + + // using key (ring) + if (mode == "ec-key") { + if (out.empty()) out = enc_out(in); + std::string ring = ring_name(out); + + ByteVec key = rb(32); + ByteVec enc = ec_wky( + std::string(input.begin(), input.end()), + key + ); + + write_file(out, enc); + write_file(ring, key); + + std::cout << "encrypted file: " << out << "\n"; + std::cout << "key ring: " << ring << "\n"; + } + + else if (mode == "dec-key") { + if (out.empty()) out = dec_out(in); + std::string ring = ring_name(in); + + ByteVec key = read_file(ring); + if (key.size() != 32) + throw std::runtime_error("invalid ring file!"); + + std::string dec = dec_wky(input, key); + write_file(out, ByteVec(dec.begin(), dec.end())); + + //std::cout << "Decrypted " << out << "\n"; + } + + // using pass + else if (mode == "ec-pass") { + if (pass.empty()) + throw std::runtime_error("missing password! provide one using -p ."); + + if (out.empty()) out = enc_out(in); + + ByteVec enc = ec_p( + std::string(input.begin(), input.end()), + pass + ); + write_file(out, enc); + + std::cout << "encrypted file: " << out << "\n"; + } + + else if (mode == "dec-pass") { + if (pass.empty()) + throw std::runtime_error("missing password! provide one using -p ."); + + if (out.empty()) out = dec_out(in); + + std::string dec = dec_p(input, pass); + write_file(out, ByteVec(dec.begin(), dec.end())); + + //std::cout << "Decrypted " << out << "\n"; // i think this is redundnant now cuz duh it better be there + } + + else { + usage(); + return 1; + } + } + catch (const std::exception e) { + std::cerr << "error: " << e.what() << "\n"; + return 1; + } + + return 0; +} diff --git a/velvet_ring.cpp b/velvet_ring.cpp new file mode 100644 index 0000000..4eec7b1 --- /dev/null +++ b/velvet_ring.cpp @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include +#include +#include + +constexpr int IV_LEN = 12; // 96-bit recommended for GCM +constexpr int KEY_LEN = 32; // 256-bit key +constexpr int SALT_LEN = 16; // for PBKDF2 +constexpr int TAG_LEN = 16; // GCM tag length + +using ByteVec = std::vector; + +// Generate random bytes +ByteVec rb(int bytes) { + ByteVec buf(bytes); + if (RAND_bytes(buf.data(), bytes) != 1) + throw std::runtime_error("RAND_bytes failed"); + return buf; +} + +// AES-256-GCM encryption with raw key +ByteVec ec_wky(const std::string& plain, const ByteVec& key) { + if (key.size() != KEY_LEN) + throw std::runtime_error("Key must be 32 bytes"); + + ByteVec iv = rb(IV_LEN); + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + if (!ctx) throw std::runtime_error("EVP_CIPHER_CTX_new failed"); + + ByteVec ciphertext(plain.size() + EVP_MAX_BLOCK_LENGTH); + int len = 0, ciphertext_len = 0; + + if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) != 1) + throw std::runtime_error("EncryptInit failed"); + + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, IV_LEN, nullptr); + + if (EVP_EncryptInit_ex(ctx, nullptr, nullptr, key.data(), iv.data()) != 1) + throw std::runtime_error("EncryptInit key/iv failed"); + + if (EVP_EncryptUpdate(ctx, ciphertext.data(), &len, + reinterpret_cast(plain.data()), plain.size()) != 1) + throw std::runtime_error("EncryptUpdate failed"); + ciphertext_len = len; + + if (EVP_EncryptFinal_ex(ctx, ciphertext.data() + len, &len) != 1) + throw std::runtime_error("EncryptFinal failed"); + ciphertext_len += len; + + ByteVec tag(TAG_LEN); + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, TAG_LEN, tag.data()); + + EVP_CIPHER_CTX_free(ctx); + + ciphertext.resize(ciphertext_len); + + ByteVec output; + output.reserve(IV_LEN + ciphertext.size() + TAG_LEN); + output.insert(output.end(), iv.begin(), iv.end()); + output.insert(output.end(), ciphertext.begin(), ciphertext.end()); + output.insert(output.end(), tag.begin(), tag.end()); + + return output; +} + +// AES-256-GCM decryption with raw key +std::string dec_wky(const ByteVec& data, const ByteVec& key) { + if (key.size() != KEY_LEN) + throw std::runtime_error("Key must be 32 bytes"); + if (data.size() < IV_LEN + TAG_LEN) + throw std::runtime_error("Data too short"); + + const unsigned char* iv = data.data(); + const unsigned char* tag = data.data() + data.size() - TAG_LEN; + const unsigned char* ciphertext = data.data() + IV_LEN; + size_t ciphertext_len = data.size() - IV_LEN - TAG_LEN; + + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + if (!ctx) throw std::runtime_error("EVP_CIPHER_CTX_new failed"); + + if (EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) != 1) + throw std::runtime_error("DecryptInit failed"); + + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, IV_LEN, nullptr); + if (EVP_DecryptInit_ex(ctx, nullptr, nullptr, key.data(), iv) != 1) + throw std::runtime_error("DecryptInit key/iv failed"); + + ByteVec plain(ciphertext_len + EVP_MAX_BLOCK_LENGTH); + int len = 0, plain_len = 0; + + if (EVP_DecryptUpdate(ctx, plain.data(), &len, ciphertext, ciphertext_len) != 1) + throw std::runtime_error("DecryptUpdate failed"); + plain_len = len; + + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, TAG_LEN, (void*)tag); + + int ret = EVP_DecryptFinal_ex(ctx, plain.data() + len, &len); + EVP_CIPHER_CTX_free(ctx); + + if (ret <= 0) + throw std::runtime_error("Decryption failed (auth tag mismatch)"); + + plain_len += len; + plain.resize(plain_len); + + return std::string(reinterpret_cast(plain.data()), plain.size()); +} + +// PBKDF2 derive key +struct DerivedKey { + ByteVec key; + ByteVec salt; +}; + +DerivedKey dervk_p(const std::string& password, const ByteVec* saltOpt = nullptr, int iterations = 200000) { + ByteVec salt = saltOpt ? *saltOpt : rb(SALT_LEN); + ByteVec key(KEY_LEN); + if (PKCS5_PBKDF2_HMAC(password.c_str(), password.size(), + salt.data(), salt.size(), + iterations, EVP_sha512(), KEY_LEN, key.data()) != 1) + throw std::runtime_error("PBKDF2 failed"); + return { key, salt }; +} + +// Encrypt using password +ByteVec ec_p(const std::string& plain, const std::string& password) { + DerivedKey dk = dervk_p(password); + ByteVec payload = ec_wky(plain, dk.key); + ByteVec result; + result.reserve(SALT_LEN + payload.size()); + result.insert(result.end(), dk.salt.begin(), dk.salt.end()); + result.insert(result.end(), payload.begin(), payload.end()); + return result; +} + +// Decrypt using password +std::string dec_p(const ByteVec& data, const std::string& password, int iterations = 200000) { + if (data.size() < SALT_LEN + IV_LEN + TAG_LEN) + throw std::runtime_error("Data too short"); + + ByteVec salt(data.begin(), data.begin() + SALT_LEN); + ByteVec payload(data.begin() + SALT_LEN, data.end()); + DerivedKey dk = dervk_p(password, &salt, iterations); + return dec_wky(payload, dk.key); +} \ No newline at end of file diff --git a/velvet_ring.h b/velvet_ring.h new file mode 100644 index 0000000..03950a9 --- /dev/null +++ b/velvet_ring.h @@ -0,0 +1,25 @@ +#pragma once +#include +#include + +using ByteVec = std::vector; + +constexpr int IV_LEN = 12; +constexpr int KEY_LEN = 32; +constexpr int SALT_LEN = 16; +constexpr int TAG_LEN = 16; + +// Function declarations +ByteVec rb(int bytes); + +ByteVec ec_wky(const std::string& plain, const ByteVec& key); +std::string dec_wky(const ByteVec& data, const ByteVec& key); + +struct DerivedKey { + ByteVec key; + ByteVec salt; +}; + +DerivedKey dervk_p(const std::string& password, const ByteVec* saltOpt = nullptr, int iterations = 200000); +ByteVec ec_p(const std::string& plain, const std::string& password); +std::string dec_p(const ByteVec& data, const std::string& password, int iterations = 200000);