Skip to content

Commit

Permalink
feat: leak password byte to improve performance (#42)
Browse files Browse the repository at this point in the history
* feat: add offset to hashAndEncryptPassword for leaked byte functionality

* test: add testing for hashAndEncryptPassword with leaked byte

* fix: import cstring for testing

* fix: import cstring in the right file for testing

* feat: add offset param to encryptPassword and encrypt

* feat: send post request with offset leaked

* feat: update server for password with offset

* build: update Makefile with new test path

* build: add encoding package to frontend

* feat: add logging

* refactor: use size_t instead of int

* feat: add offset parameter to endpoint

* test: use variable for offset and fix test description

* refactor: use snake_case for variable names

* refactor: use const instead of var

* test: fix integration test and server encoding test

* docs: add description of offset parameter

* docs: remove TODO

* docs: add documentation for PSI functions

* test: add explanation for offset modulo

* refactor: remove unused imports and printTable call

* refactor: import iostream

* refactor: reimport cstring

---------

Co-authored-by: Cedric Sirianni <[email protected]>
  • Loading branch information
ni-jessica and csirianni authored Dec 11, 2023
1 parent 5bbe0e8 commit 4e834ac
Show file tree
Hide file tree
Showing 16 changed files with 189 additions and 96 deletions.
2 changes: 1 addition & 1 deletion backend/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ build:
cd $(BUILD_DIR) && cmake --build .

check: build
cd $(BUILD_DIR) && ./test/pdl_test
cd $(BUILD_DIR) && ./tests/pdl_test

.PHONY: all build run check
26 changes: 14 additions & 12 deletions backend/src/cryptography.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace cryptography
{
std::string hashAndEncryptPassword(const std::string &password, unsigned char *b)
std::string hashAndEncryptPassword(const std::string &password, unsigned char *b, size_t offset)
{
// hash password to point
unsigned char hash[crypto_core_ristretto255_HASHBYTES];
Expand All @@ -12,28 +12,30 @@ namespace cryptography
crypto_core_ristretto255_from_hash(point, hash);

// encrypt password
unsigned char encryptedPassword[crypto_core_ristretto255_BYTES];
crypto_scalarmult_ristretto255(encryptedPassword, b, point);
return std::string(encryptedPassword, encryptedPassword + crypto_core_ristretto255_BYTES);
unsigned char encrypted_password[crypto_core_ristretto255_BYTES + offset];
crypto_scalarmult_ristretto255(encrypted_password + offset, b, point);
memcpy(encrypted_password, point, offset);

return std::string(encrypted_password, encrypted_password + crypto_core_ristretto255_BYTES + offset);
}

std::string encryptPassword(const std::string &password, unsigned char *b)
std::string encryptPassword(const std::string &password, unsigned char *b, size_t offset)
{
// encrypt password
const unsigned char *data = (const unsigned char *)password.data();
unsigned char encryptedPassword[crypto_core_ristretto255_BYTES];
crypto_scalarmult_ristretto255(encryptedPassword, b, data);
return std::string(encryptedPassword, encryptedPassword + crypto_core_ristretto255_BYTES);
std::string raw_password = password.substr(offset, crypto_core_ristretto255_BYTES);
const unsigned char *data = (const unsigned char *)raw_password.data();
unsigned char encrypted_password[crypto_core_ristretto255_BYTES];
crypto_scalarmult_ristretto255(encrypted_password, b, data);
return std::string(encrypted_password, encrypted_password + crypto_core_ristretto255_BYTES);
}

std::vector<std::string> encrypt(const std::unordered_set<std::string> &passwords, unsigned char *b)
std::vector<std::string> encrypt(const std::unordered_set<std::string> &passwords, unsigned char *b, size_t offset)
{
std::vector<std::string> result;
result.reserve(passwords.size());

for (const auto &password : passwords)
{
result.push_back(hashAndEncryptPassword(password, b));
result.push_back(hashAndEncryptPassword(password, b, offset));
}

return result;
Expand Down
17 changes: 13 additions & 4 deletions backend/src/cryptography.hpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
#include <vector>
#include <string>
#include <cstring>
#include <unordered_set>

#ifndef CRYPTOGRAPHY_H
#define CRYPTOGRAPHY_H

namespace cryptography
{

Expand All @@ -12,9 +16,10 @@ namespace cryptography
*
* @param password the password to encrypt
* @param b the secret key
* @param offset the number of bytes to leak
* @return std::string the encrypted password
*/
std::string hashAndEncryptPassword(const std::string &password, unsigned char *b);
std::string hashAndEncryptPassword(const std::string &password, unsigned char *b, size_t offset = 0);

/**
* @brief Encrypt the provided password using secret key b.
Expand All @@ -23,17 +28,21 @@ namespace cryptography
*
* @param password the password to encrypt
* @param b the secret key
* @param offset the number of bytes to leak
* @return std::string the encrypted password
*/
std::string encryptPassword(const std::string &password, unsigned char *b);
std::string encryptPassword(const std::string &password, unsigned char *b, size_t offset = 0);

/**
* @brief Encrypt the provided set of passwords using secret key b.
*
* @param passwords the set of passwords to encrypt
* @param b the secret key
* @param offset the number of bytes to leak
* @return std::vector<std::string> the encrypted passwords
*/
std::vector<std::string> encrypt(const std::unordered_set<std::string> &passwords,
unsigned char *b);
}
unsigned char *b, size_t offset = 0);
}

#endif
9 changes: 6 additions & 3 deletions backend/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ int main(int argc, char *argv[])
}

database::Database db = database::Database(argv[1], build);
// TODO: store this data in server because same file with different offset will have different passwords
const size_t offset = 1;
spdlog::info("Password offset: {}", offset);

if (build)
{
// create password table
Expand All @@ -58,7 +62,7 @@ int main(int argc, char *argv[])
crypto_core_ristretto255_scalar_random(b);

// 2. encrypt each password with b (and hash to point)
std::vector<std::string> encrypted_passwords = cryptography::encrypt(passwords, b);
std::vector<std::string> encrypted_passwords = cryptography::encrypt(passwords, b, offset);

// 3. insert into database
for (const auto &password : encrypted_passwords)
Expand Down Expand Up @@ -90,7 +94,6 @@ int main(int argc, char *argv[])
throw std::invalid_argument("Passwords and/or secret key table does not exist. Use --build to create a new database");
}
}

// Enable CORS
crow::App<crow::CORSHandler> app;

Expand All @@ -101,7 +104,7 @@ int main(int argc, char *argv[])

// initialize endpoints
server::root(app);
server::breachedPasswords(app, db);
server::breachedPasswords(app, db, offset);

// set the port, set the app to run on multiple threads, and run the app
app.port(18080).multithreaded().run();
Expand Down
1 change: 0 additions & 1 deletion backend/src/password.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#include <iostream>
#include "password.hpp"

namespace password
Expand Down
1 change: 1 addition & 0 deletions backend/src/password.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <unordered_set>
#include <iostream>
#include <string>

#ifndef PASSWORDS_H
Expand Down
8 changes: 5 additions & 3 deletions backend/src/server.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "server.hpp"
#include "sqlite3.h"
#include "spdlog/spdlog.h"
namespace server
{
void root(crow::App<crow::CORSHandler> &app)
Expand All @@ -12,15 +13,16 @@ namespace server
return response; });
}

void breachedPasswords(crow::App<crow::CORSHandler> &app, database::Database &db)
void breachedPasswords(crow::App<crow::CORSHandler> &app, database::Database &db, size_t offset)
{
CROW_ROUTE(app, "/breachedPasswords")
.methods("POST"_method)([&db](const crow::request &req)
.methods("POST"_method)([&db, offset](const crow::request &req)
{
crow::json::wvalue response;
std::string user_password = req.body.data();
if (user_password.empty())
{
spdlog::error("Empty user password");
response["status"] = "error";
return response;
}
Expand All @@ -40,7 +42,7 @@ namespace server
unsigned char *b = (unsigned char *)decoded_b.data();

// encrypt user password
std::string encrypted_password = cryptography::encryptPassword(crow::utility::base64decode(user_password, user_password.size()), b);
std::string encrypted_password = cryptography::encryptPassword(crow::utility::base64decode(user_password, user_password.size()), b, offset);
response["status"] = "success";
response["userPassword"] = crow::utility::base64encode(encrypted_password, encrypted_password.size());
response["breachedPasswords"] = breached_passwords;
Expand Down
3 changes: 2 additions & 1 deletion backend/src/server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ namespace server
*
* @param app The crow server.
* @param db The database of all breached passwords.
* @param offset The number of bytes to leak.
*/
void breachedPasswords(crow::App<crow::CORSHandler> &app, database::Database &db);
void breachedPasswords(crow::App<crow::CORSHandler> &app, database::Database &db, size_t offset = 0);
}
#endif // SERVER_H
3 changes: 2 additions & 1 deletion backend/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
find_package(Catch2 REQUIRED)
find_package(nlohmann_json 3.11.3 REQUIRED)
find_package(SQLite3 REQUIRED)
find_package(spdlog REQUIRED)

set (TEST_SOURCE
password.cpp
Expand All @@ -11,6 +12,6 @@ set (TEST_SOURCE

add_executable(pdl_test ${TEST_SOURCE})

target_link_libraries(pdl_test src Catch2::Catch2WithMain nlohmann_json::nlohmann_json SQLite::SQLite3)
target_link_libraries(pdl_test src Catch2::Catch2WithMain nlohmann_json::nlohmann_json SQLite::SQLite3 spdlog::spdlog)

target_include_directories(pdl_test PUBLIC ${CMAKE_SOURCE_DIR}/src)
73 changes: 49 additions & 24 deletions backend/tests/cryptography.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,55 @@

TEST_CASE("Test hashAndEncryptPassword")
{
for (int i = 0; i < 30; ++i)
// generate constants
const std::string password = "TestPass1&";
unsigned char b[crypto_core_ristretto255_SCALARBYTES];
crypto_core_ristretto255_scalar_random(b);
unsigned char inverse[crypto_core_ristretto255_SCALARBYTES];
crypto_core_ristretto255_scalar_invert(inverse, b);

// compute expected value for password after computation
unsigned char expected_hash[crypto_core_ristretto255_HASHBYTES];
crypto_generichash(expected_hash, sizeof expected_hash, (const unsigned char *)password.data(), password.length(), NULL, 0);
unsigned char expected_point[crypto_core_ristretto255_BYTES];
crypto_core_ristretto255_from_hash(expected_point, expected_hash);

SECTION("without leaked byte")
{
// generate constants
unsigned char b[crypto_core_ristretto255_SCALARBYTES];
crypto_core_ristretto255_scalar_random(b);
unsigned char inverse[crypto_core_ristretto255_SCALARBYTES];
crypto_core_ristretto255_scalar_invert(inverse, b);
const std::string password = "TestPass1&";

// encrypt password
std::string encryptedPasswordStr = cryptography::hashAndEncryptPassword(password, b);
unsigned char encryptedPassword[crypto_core_ristretto255_BYTES];
memcpy(encryptedPassword, encryptedPasswordStr.data(), crypto_core_ristretto255_BYTES);

// unencrypt the password with the inverse of b
unsigned char decryptedPassword[crypto_core_ristretto255_BYTES];
crypto_scalarmult_ristretto255(decryptedPassword, inverse, encryptedPassword);

// compute expected value
unsigned char expectedHash[crypto_core_ristretto255_HASHBYTES];
crypto_generichash(expectedHash, sizeof expectedHash, (const unsigned char *)password.data(), password.length(), NULL, 0);
unsigned char expectedPoint[crypto_core_ristretto255_BYTES];
crypto_core_ristretto255_from_hash(expectedPoint, expectedHash);

CHECK(std::memcmp(expectedPoint, decryptedPassword, crypto_core_ristretto255_BYTES) == 0);
for (int i = 0; i < 30; ++i)
{
// encrypt password
std::string expected_password_str = cryptography::hashAndEncryptPassword(password, b);
unsigned char encrypted_password[crypto_core_ristretto255_BYTES];
memcpy(encrypted_password, expected_password_str.data(), crypto_core_ristretto255_BYTES);

// unencrypt the password with the inverse of b
unsigned char decrypted_password[crypto_core_ristretto255_BYTES];
int result = crypto_scalarmult_ristretto255(decrypted_password, inverse, encrypted_password);
REQUIRE(result == 0);

CHECK(std::memcmp(expected_point, decrypted_password, crypto_core_ristretto255_BYTES) == 0);
}
}

SECTION("with 1 leaked byte")
{
for (int i = 0; i < 30; ++i)
{
const size_t offset = 1;
// encrypt password
std::string encrypted_password_str = cryptography::hashAndEncryptPassword(password, b, offset);
unsigned char encrypted_password[crypto_core_ristretto255_BYTES + offset];
memcpy(encrypted_password, encrypted_password_str.data(), crypto_core_ristretto255_BYTES + offset);

// decrypt password
unsigned char decrypted_password[crypto_core_ristretto255_BYTES + offset];
int result = crypto_scalarmult_ristretto255(decrypted_password + offset, inverse, encrypted_password + offset);
REQUIRE(result == 0);
memcpy(decrypted_password, encrypted_password, offset);

CHECK(std::memcmp(expected_point, decrypted_password + offset, crypto_core_ristretto255_BYTES) == 0);
CHECK(expected_point[0] == decrypted_password[0]);
}
}
}
33 changes: 26 additions & 7 deletions backend/tests/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "cryptography.hpp"
#include "sodium.h"
#include <nlohmann/json.hpp>
#include <spdlog/spdlog.h>

TEST_CASE("Test endpoints using handler")
{
Expand All @@ -25,7 +26,7 @@ TEST_CASE("Test endpoints using handler")
REQUIRE(file.is_open());

// create the database, with tables passwords and secret
database::Database db = database::Database(path);
database::Database db = database::Database(path, true);
REQUIRE_NOTHROW(db.execute("CREATE TABLE passwords (password TEXT);"));
REQUIRE_NOTHROW(db.execute("CREATE TABLE secret (key TEXT);"));

Expand All @@ -37,7 +38,8 @@ TEST_CASE("Test endpoints using handler")
crypto_core_ristretto255_scalar_random(b);

// 2. encrypt each password with b (and hash to point)
std::vector<std::string> encrypted_passwords = cryptography::encrypt(passwords, b);
const size_t offset = 1;
std::vector<std::string> encrypted_passwords = cryptography::encrypt(passwords, b, offset);

// 3. insert into database
for (const auto &password : encrypted_passwords)
Expand All @@ -49,7 +51,7 @@ TEST_CASE("Test endpoints using handler")
// encode key b and insert into database
db.execute("INSERT INTO secret (key) VALUES ('" + crow::utility::base64encode(std::string(reinterpret_cast<const char *>(b), crypto_core_ristretto255_SCALARBYTES), crypto_core_ristretto255_SCALARBYTES) + "');");

server::breachedPasswords(app, db);
server::breachedPasswords(app, db, offset);

// check that all the route handlers were created
app.validate();
Expand Down Expand Up @@ -83,14 +85,31 @@ TEST_CASE("Test endpoints using handler")
CHECK(breached_passwords.size() == 3);
for (const auto &breached_password : breached_passwords)
{
// encoded password should end with '='
CHECK(breached_password.back() == '=');
// offset determines the amount of padding appended to the end of the password
switch (offset % 3)
{
case 0:
// password is padded with single '='
CHECK(breached_password[breached_password.size() - 1] == '=');
CHECK(breached_password[breached_password.size() - 2] != '=');
break;
case 1:
// password is not padded
CHECK(breached_password[breached_password.size() - 1] != '=');
CHECK(breached_password[breached_password.size() - 2] != '=');
break;
case 2:
// password is padded with two '='
CHECK(breached_password[breached_password.size() - 1] == '=');
CHECK(breached_password[breached_password.size() - 2] == '=');
break;
}
}

std::string user_password = body["userPassword"];
CHECK(!user_password.empty());
CHECK(user_password.back() == '=');

CHECK(res.code == 200);
}
}
REQUIRE(std::remove(path.c_str()) == 0);
}
2 changes: 1 addition & 1 deletion frontend/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export default function SignUp() {
) => {
event.preventDefault();
setIsLoading(true);
const response = await checkSecurity(password); // makes an API call with the user's password
const response = await checkSecurity(password, 1); // makes an API call with the user's password
setIsLoading(false);

if (response.status == "success") {
Expand Down
Loading

0 comments on commit 4e834ac

Please sign in to comment.