Initial commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
out/
|
||||
@@ -0,0 +1,17 @@
|
||||
# love is a gentle thing
|
||||
Velvet Ring by Big Thief.<br>
|
||||
an encryption tool!
|
||||
|
||||
## building
|
||||
```sh
|
||||
./build-static.sh # creates a static executable
|
||||
./build.sh # creates a dynamic executable
|
||||
```
|
||||
|
||||
## usage
|
||||
```sh
|
||||
ec-pass <input file> -p <password> [-o <out>]
|
||||
dec-pass <input file> -p <password> [-o <out>]
|
||||
ec-key <input file> [-o <out>]
|
||||
dec-key <input file> [-o <out>]
|
||||
```
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,151 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include "velvet_ring.h"
|
||||
|
||||
using ByteVec = std::vector<unsigned char>;
|
||||
|
||||
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<char>(f)),
|
||||
std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
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<const char*>(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 <input file> -p <password> [-o <out>]
|
||||
dec-pass <input file> -p <password> [-o <out>]
|
||||
ec-key <input file> [-o <out>]
|
||||
dec-key <input file> [-o <out>])";
|
||||
|
||||
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 <password>.");
|
||||
|
||||
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 <password>.");
|
||||
|
||||
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;
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
|
||||
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<unsigned char>;
|
||||
|
||||
// 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<const unsigned char*>(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<char*>(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);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using ByteVec = std::vector<unsigned char>;
|
||||
|
||||
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);
|
||||
Reference in New Issue
Block a user