test: Add 8 and update 14 files
This commit is contained in:
187
src/bcrypt.hpp
Normal file
187
src/bcrypt.hpp
Normal file
@@ -0,0 +1,187 @@
|
||||
// Simplified BCrypt implementation for token hashing
|
||||
// Based on OpenBSD's bcrypt with modifications for C++ header-only use
|
||||
// This implementation uses SHA-256 as the core hashing function with salt and multiple rounds
|
||||
|
||||
#ifndef BCRYPT_HPP
|
||||
#define BCRYPT_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <cstring>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
namespace simple_object_storage {
|
||||
|
||||
class BCrypt {
|
||||
public:
|
||||
// Default cost factor (number of rounds = 2^cost)
|
||||
static constexpr int DEFAULT_COST = 12;
|
||||
static constexpr int MIN_COST = 4;
|
||||
static constexpr int MAX_COST = 31;
|
||||
static constexpr size_t SALT_LENGTH = 16; // 128 bits
|
||||
static constexpr size_t HASH_LENGTH = 32; // 256 bits (SHA-256)
|
||||
|
||||
// Generate a hash from a password/token
|
||||
static std::string hashPassword(const std::string& password, int cost = DEFAULT_COST) {
|
||||
if (cost < MIN_COST || cost > MAX_COST) {
|
||||
cost = DEFAULT_COST;
|
||||
}
|
||||
|
||||
// Generate random salt
|
||||
std::vector<unsigned char> salt(SALT_LENGTH);
|
||||
if (RAND_bytes(salt.data(), SALT_LENGTH) != 1) {
|
||||
throw std::runtime_error("Failed to generate random salt");
|
||||
}
|
||||
|
||||
// Perform the hashing
|
||||
std::vector<unsigned char> hash = hashWithSalt(password, salt, cost);
|
||||
|
||||
// Format: $2b$<cost>$<salt><hash>
|
||||
// We use a simplified format for header-only implementation
|
||||
return formatHash(cost, salt, hash);
|
||||
}
|
||||
|
||||
// Verify a password against a hash
|
||||
static bool verifyPassword(const std::string& password, const std::string& hash) {
|
||||
// Parse the hash to extract cost, salt, and expected hash
|
||||
int cost;
|
||||
std::vector<unsigned char> salt;
|
||||
std::vector<unsigned char> expectedHash;
|
||||
|
||||
if (!parseHash(hash, cost, salt, expectedHash)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hash the password with the extracted salt and cost
|
||||
std::vector<unsigned char> computedHash = hashWithSalt(password, salt, cost);
|
||||
|
||||
// Constant-time comparison to prevent timing attacks
|
||||
return constantTimeCompare(computedHash, expectedHash);
|
||||
}
|
||||
|
||||
private:
|
||||
// Perform the actual hashing with salt and cost
|
||||
static std::vector<unsigned char> hashWithSalt(const std::string& password,
|
||||
const std::vector<unsigned char>& salt,
|
||||
int cost) {
|
||||
// Number of iterations = 2^cost
|
||||
size_t rounds = 1ULL << cost;
|
||||
|
||||
// Initial hash: SHA256(salt + password)
|
||||
std::vector<unsigned char> data;
|
||||
data.insert(data.end(), salt.begin(), salt.end());
|
||||
data.insert(data.end(), password.begin(), password.end());
|
||||
|
||||
std::vector<unsigned char> hash(SHA256_DIGEST_LENGTH);
|
||||
SHA256(data.data(), data.size(), hash.data());
|
||||
|
||||
// Iterate to increase computational cost
|
||||
for (size_t i = 0; i < rounds; ++i) {
|
||||
// SHA256(hash + salt + password)
|
||||
data.clear();
|
||||
data.insert(data.end(), hash.begin(), hash.end());
|
||||
data.insert(data.end(), salt.begin(), salt.end());
|
||||
data.insert(data.end(), password.begin(), password.end());
|
||||
SHA256(data.data(), data.size(), hash.data());
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Format hash for storage
|
||||
static std::string formatHash(int cost, const std::vector<unsigned char>& salt,
|
||||
const std::vector<unsigned char>& hash) {
|
||||
std::stringstream ss;
|
||||
ss << "$2b$" << std::setfill('0') << std::setw(2) << cost << "$";
|
||||
|
||||
// Encode salt as hex
|
||||
for (unsigned char byte : salt) {
|
||||
ss << std::hex << std::setw(2) << static_cast<int>(byte);
|
||||
}
|
||||
|
||||
// Encode hash as hex
|
||||
for (unsigned char byte : hash) {
|
||||
ss << std::hex << std::setw(2) << static_cast<int>(byte);
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
// Parse a formatted hash
|
||||
static bool parseHash(const std::string& hashStr, int& cost,
|
||||
std::vector<unsigned char>& salt,
|
||||
std::vector<unsigned char>& hash) {
|
||||
// Expected format: $2b$<cost>$<salt><hash>
|
||||
if (hashStr.size() < 10 || hashStr.substr(0, 4) != "$2b$") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the second $
|
||||
size_t secondDollar = hashStr.find('$', 4);
|
||||
if (secondDollar == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse cost
|
||||
try {
|
||||
cost = std::stoi(hashStr.substr(4, secondDollar - 4));
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse salt and hash (both in hex)
|
||||
std::string hexData = hashStr.substr(secondDollar + 1);
|
||||
if (hexData.size() != (SALT_LENGTH + HASH_LENGTH) * 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decode salt
|
||||
salt.clear();
|
||||
for (size_t i = 0; i < SALT_LENGTH * 2; i += 2) {
|
||||
try {
|
||||
unsigned char byte = static_cast<unsigned char>(
|
||||
std::stoi(hexData.substr(i, 2), nullptr, 16));
|
||||
salt.push_back(byte);
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Decode hash
|
||||
hash.clear();
|
||||
for (size_t i = SALT_LENGTH * 2; i < hexData.size(); i += 2) {
|
||||
try {
|
||||
unsigned char byte = static_cast<unsigned char>(
|
||||
std::stoi(hexData.substr(i, 2), nullptr, 16));
|
||||
hash.push_back(byte);
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Constant-time comparison to prevent timing attacks
|
||||
static bool constantTimeCompare(const std::vector<unsigned char>& a,
|
||||
const std::vector<unsigned char>& b) {
|
||||
if (a.size() != b.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char result = 0;
|
||||
for (size_t i = 0; i < a.size(); ++i) {
|
||||
result |= a[i] ^ b[i];
|
||||
}
|
||||
|
||||
return result == 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace simple_object_storage
|
||||
|
||||
#endif // BCRYPT_HPP
|
156
src/hash_token.cpp
Normal file
156
src/hash_token.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
// Utility to generate bcrypt hashes from tokens
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include "bcrypt.hpp"
|
||||
|
||||
using namespace simple_object_storage;
|
||||
|
||||
// Function to read password without echoing to terminal
|
||||
std::string readPasswordFromStdin() {
|
||||
struct termios oldt, newt;
|
||||
tcgetattr(STDIN_FILENO, &oldt);
|
||||
newt = oldt;
|
||||
newt.c_lflag &= ~ECHO;
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
|
||||
|
||||
std::string password;
|
||||
std::getline(std::cin, password);
|
||||
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
|
||||
std::cout << std::endl;
|
||||
|
||||
return password;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
std::string token;
|
||||
int cost = BCrypt::DEFAULT_COST;
|
||||
bool verify_mode = false;
|
||||
bool quiet_mode = false;
|
||||
|
||||
// Parse command line arguments
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string arg = argv[i];
|
||||
if (arg == "--help" || arg == "-h") {
|
||||
std::cout << "Usage: " << argv[0] << " [OPTIONS] [TOKEN]\n"
|
||||
<< "\nGenerate bcrypt hashes for authentication tokens.\n"
|
||||
<< "\nOptions:\n"
|
||||
<< " -h, --help Show this help message\n"
|
||||
<< " -c, --cost N Set bcrypt cost factor (4-31, default: " << BCrypt::DEFAULT_COST << ")\n"
|
||||
<< " -v, --verify Verify a token against a hash\n"
|
||||
<< " -q, --quiet Quiet mode (only output hash)\n"
|
||||
<< " -g, --generate Generate a random token and hash it\n"
|
||||
<< "\nExamples:\n"
|
||||
<< " " << argv[0] << " mytoken123 # Hash a token\n"
|
||||
<< " " << argv[0] << " -c 14 mytoken123 # Hash with cost 14\n"
|
||||
<< " " << argv[0] << " # Read token from stdin\n"
|
||||
<< " " << argv[0] << " -v # Verify token against hash\n"
|
||||
<< " " << argv[0] << " -g # Generate random token\n";
|
||||
return 0;
|
||||
} else if (arg == "--cost" || arg == "-c") {
|
||||
if (i + 1 < argc) {
|
||||
cost = std::atoi(argv[++i]);
|
||||
if (cost < BCrypt::MIN_COST || cost > BCrypt::MAX_COST) {
|
||||
std::cerr << "Error: Cost must be between " << BCrypt::MIN_COST
|
||||
<< " and " << BCrypt::MAX_COST << std::endl;
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
std::cerr << "Error: --cost requires an argument\n";
|
||||
return 1;
|
||||
}
|
||||
} else if (arg == "--verify" || arg == "-v") {
|
||||
verify_mode = true;
|
||||
} else if (arg == "--quiet" || arg == "-q") {
|
||||
quiet_mode = true;
|
||||
} else if (arg == "--generate" || arg == "-g") {
|
||||
// Generate a random token
|
||||
const char* charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
const size_t charset_size = 62;
|
||||
const size_t token_length = 32;
|
||||
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
std::uniform_int_distribution<> dis(0, charset_size - 1);
|
||||
|
||||
token = "";
|
||||
for (size_t i = 0; i < token_length; ++i) {
|
||||
token += charset[dis(gen)];
|
||||
}
|
||||
|
||||
if (!quiet_mode) {
|
||||
std::cout << "Generated token: " << token << std::endl;
|
||||
}
|
||||
} else if (!arg.empty() && arg[0] != '-') {
|
||||
token = arg;
|
||||
} else {
|
||||
std::cerr << "Error: Unknown option: " << arg << "\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (verify_mode) {
|
||||
// Verification mode
|
||||
if (!quiet_mode) {
|
||||
std::cout << "Enter token to verify: ";
|
||||
}
|
||||
std::string plaintext = readPasswordFromStdin();
|
||||
|
||||
if (!quiet_mode) {
|
||||
std::cout << "Enter hash to verify against: ";
|
||||
}
|
||||
std::string hash;
|
||||
std::getline(std::cin, hash);
|
||||
|
||||
try {
|
||||
bool valid = BCrypt::verifyPassword(plaintext, hash);
|
||||
if (!quiet_mode) {
|
||||
std::cout << "Verification result: ";
|
||||
}
|
||||
std::cout << (valid ? "VALID" : "INVALID") << std::endl;
|
||||
return valid ? 0 : 1;
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
// Hash generation mode
|
||||
if (token.empty()) {
|
||||
if (!quiet_mode) {
|
||||
std::cout << "Enter token to hash: ";
|
||||
}
|
||||
token = readPasswordFromStdin();
|
||||
}
|
||||
|
||||
if (token.empty()) {
|
||||
std::cerr << "Error: Token cannot be empty\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
std::string hash = BCrypt::hashPassword(token, cost);
|
||||
if (!quiet_mode) {
|
||||
std::cout << "Bcrypt hash (cost=" << cost << "):\n";
|
||||
}
|
||||
std::cout << hash << std::endl;
|
||||
|
||||
// Verify the hash works
|
||||
if (!quiet_mode) {
|
||||
bool verified = BCrypt::verifyPassword(token, hash);
|
||||
if (!verified) {
|
||||
std::cerr << "Warning: Generated hash failed verification!\n";
|
||||
return 1;
|
||||
}
|
||||
std::cout << "Hash verified successfully.\n";
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@@ -19,6 +19,7 @@
|
||||
#include "welcome_page.hpp"
|
||||
#include "rate_limiter.hpp"
|
||||
#include "HttpController.hpp"
|
||||
#include "bcrypt.hpp" // For secure token hashing
|
||||
|
||||
namespace simple_object_storage {
|
||||
|
||||
@@ -67,7 +68,16 @@ bool Server::validate_write_request(const drogon::HttpRequestPtr &req, drogon::H
|
||||
return false;
|
||||
}
|
||||
|
||||
bool write_token_valid = std::find(config_.write_tokens.begin(), config_.write_tokens.end(), token) != config_.write_tokens.end();
|
||||
// Check if token is valid by comparing against stored bcrypt hashes
|
||||
bool write_token_valid = false;
|
||||
for (const auto& stored_hash : config_.write_tokens) {
|
||||
// Verify the token against the stored bcrypt hash
|
||||
if (BCrypt::verifyPassword(token, stored_hash)) {
|
||||
write_token_valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!write_token_valid) {
|
||||
// Only count failed attempt (increment the limiter)
|
||||
auth_rate_limiter_->is_allowed(client_ip); // This will increment the count
|
||||
|
Reference in New Issue
Block a user