Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ jobs:
- name: Build Project
run: cmake --build build --config ${{ matrix.build-configuration }}

- name: Run Catch2 Unit Tests
- name: Run Catch2 Unit Tests (Release)
if: matrix.build-configuration == 'Release'
# Runs the unit tests you specified
run: ctest --test-dir build -C ${{ matrix.build-configuration }} --output-on-failure

Expand Down Expand Up @@ -107,4 +108,4 @@ jobs:
gh release create "$TAG_NAME" ./release-artifacts/*.zip \
--title "Development Build $TAG_NAME" \
--prerelease \
--generate-notes
--generate-notes
6 changes: 3 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[submodule "lib/Base256"]
path = lib/Base256
url = https://github.com/ParallelEngineering/Base256.git
[submodule "lib/BigInt"]
path = lib/BigInt
url = https://github.com/ParallelEngineering/BigInt.git
6 changes: 4 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ set(CMAKE_CXX_EXTENSIONS OFF)

add_subdirectory(lib)
add_subdirectory(src)
include(CTest)
add_subdirectory(tests)

target_link_libraries(RSA PRIVATE Base256)
target_include_directories(RSA PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(RSA PUBLIC Base256)
target_include_directories(RSA PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
1 change: 0 additions & 1 deletion lib/Base256
Submodule Base256 deleted from 3fa56d
1 change: 1 addition & 0 deletions lib/BigInt
Submodule BigInt added at d3f446
6 changes: 3 additions & 3 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
if(NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/Base256/CMakeLists.txt")
message(FATAL_ERROR "Base256 submodule not initialized. Run: git submodule update --init --recursive")
if(NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/BigInt/CMakeLists.txt")
message(FATAL_ERROR "BigInt submodule not initialized. Run: git submodule update --init --recursive")
endif()
add_subdirectory(Base256)
add_subdirectory(BigInt)
4 changes: 4 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ target_include_directories(RSA
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)

if (WIN32)
target_link_libraries(RSA PRIVATE bcrypt)
endif ()
27 changes: 16 additions & 11 deletions src/decrypt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@

#include <iostream>

namespace core {
Decryptor::Decryptor(PrivateKey privKey) : key(std::move(privKey)) {}
#include "helper.h"
#include "math_utils.h"

std::string Decryptor::decrypt(const std::vector<uint8_t>& ciphertext) const {
using namespace operations::math;

namespace core::decryptor {
std::string decrypt(keyPair& keyPair, const std::vector<uint8_t>& ciphertext) {
std::string plaintext;

const size_t blockSize = key.n.getBytes().size();
// The block size in bytes is determined by the size of modulus n multiplied by 8 (64 bits per
// limb)
const size_t blockSize = keyPair.getPrivateKey().n.getBytes().size() * 8;
if (blockSize == 0 || ciphertext.size() % blockSize != 0) {
std::cerr << "Decryption error: Invalid ciphertext block size alignment." << std::endl;
return plaintext;
Expand All @@ -17,18 +22,18 @@ std::string Decryptor::decrypt(const std::vector<uint8_t>& ciphertext) const {
// Process the ciphertext block-by-block using the fixed block size
for (size_t i = 0; i < ciphertext.size(); i += blockSize) {
// 1. Extract a single block chunk
std::vector chunk(ciphertext.begin() + i, ciphertext.begin() + i + blockSize);
std::vector<uint8_t> chunk(ciphertext.begin() + i, ciphertext.begin() + i + blockSize);

// 2. Construct a Base256 representation from the extracted block chunk
const operations::Base256 c_num(chunk);
// 2. Construct representation from the extracted block chunk (using 64-bit limbs)
const operations::Base256 c_num(bytesToByteArray(chunk));

// 3. Perform RSA mathematical operation: M = C^d mod n
operations::Base256 m_num = operations::Base256::modPow(c_num, key.d, key.n);
operations::Base256 m_num =
modPow(c_num, keyPair.getPrivateKey().d, keyPair.getPrivateKey().n);

// 4. Retrieve the decrypted byte value and convert it back to a character
const auto& m_bytes = m_num.getBytes();
std::vector<uint8_t> m_bytes = byteArrayToBytes(m_num.getBytes());
if (!m_bytes.empty()) {
// Assuming little-endian layout where index 0 is the least significant byte
plaintext.push_back(static_cast<char>(m_bytes[0]));
} else {
plaintext.push_back('\0'); // Fallback for a zero-value block
Expand All @@ -37,4 +42,4 @@ std::string Decryptor::decrypt(const std::vector<uint8_t>& ciphertext) const {

return plaintext;
}
} // namespace core
} // namespace core::decryptor
14 changes: 4 additions & 10 deletions src/decrypt.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,11 @@
#include "keyPair.h"

namespace core {
class Decryptor {
private:
PrivateKey key;
namespace decryptor {

public:
// Constructor binds the decryption process to a specific Private Key
explicit Decryptor(PrivateKey privKey);

// Performs RSA decryption on a ciphertext byte vector
[[nodiscard]] std::string decrypt(const std::vector<uint8_t>& ciphertext) const;
};
// Performs RSA decryption on a ciphertext byte vector
[[nodiscard]] std::string decrypt(keyPair& keyPair, const std::vector<uint8_t>& ciphertext);
}; // namespace decryptor
} // namespace core

#endif
33 changes: 15 additions & 18 deletions src/encrypt.cpp
Original file line number Diff line number Diff line change
@@ -1,36 +1,33 @@
#include "encrypt.h"

namespace core {
Encryptor::Encryptor(PublicKey pubKey) : key(std::move(pubKey)) {}
#include "helper.h"
#include "math_utils.h"

std::vector<uint8_t> Encryptor::encrypt(const std::string& plaintext) const {
using namespace operations::math;

namespace core::encryptor {
std::vector<uint8_t> encrypt(keyPair& keyPair, const std::string& plaintext) {
std::vector<uint8_t> ciphertext;

// The ciphertext block size is determined by the byte-length of the modulus n
const size_t blockSize = key.n.getBytes().size();
// The block size in bytes is determined by the size of modulus n multiplied by 8 (64 bits per
// limb)
const size_t blockSize = keyPair.getPublicKey().n.getBytes().size() * 8;
if (blockSize == 0) return ciphertext;

for (const char c : plaintext) {
// 1. Convert the character byte to an arbitrary-precision Base256 representation
// 1. Convert the character byte to the internal 64-bit limb representation
const operations::Base256 m(static_cast<uint8_t>(c));

// 2. Perform RSA mathematical operation: C = M^e mod n
operations::Base256 c_num = operations::Base256::modPow(m, key.e, key.n);

// 3. Extract the raw bytes from the computed ciphertext number
std::vector<uint8_t> c_bytes = c_num.getBytes();
operations::Base256 c_num = modPow(m, keyPair.getPublicKey().e, keyPair.getPublicKey().n);

// 4. Padding: Pad the byte vector with trailing zeros up to the required block size.
// (Assuming little-endian layout, where the most significant bytes are placed at the
// end)
while (c_bytes.size() < blockSize) {
c_bytes.push_back(0);
}
// 3. Extract raw bytes from the computed ciphertext number (padded to target block size)
std::vector<uint8_t> c_bytes = byteArrayToBytes(c_num.getBytes(), blockSize);

// 5. Append the padded block to the final ciphertext vector
// 4. Append the padded block to the final ciphertext vector
ciphertext.insert(ciphertext.end(), c_bytes.begin(), c_bytes.end());
}

return ciphertext;
}
} // namespace core
} // namespace core::encryptor
15 changes: 4 additions & 11 deletions src/encrypt.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,10 @@
#include "keyPair.h"

namespace core {
class Encryptor {
private:
PublicKey key;

public:
// Constructor binds the encryption process to a specific Public Key
explicit Encryptor(PublicKey pubKey);

// Performs RSA encryption on a plaintext string
[[nodiscard]] std::vector<uint8_t> encrypt(const std::string& plaintext) const;
};
namespace encryptor {
// Performs RSA encryption on a plaintext string
[[nodiscard]] std::vector<uint8_t> encrypt(keyPair& keyPair, const std::string& plaintext);
}; // namespace encryptor
} // namespace core

#endif
55 changes: 55 additions & 0 deletions src/helper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#pragma once
#include <cstdint>
#include <filesystem>
#include <string>
#include <vector>

#include "base256.h"
#include "key_fwd.h"

// Converts a little-endian byte array to a 64-bit word array
[[nodiscard]] inline ByteArray bytesToByteArray(const std::vector<uint8_t>& bytes) {
if (bytes.empty()) return {0};
ByteArray data;
data.reserve((bytes.size() + 7) / 8);
for (size_t i = 0; i < bytes.size(); i += 8) {
uint64_t val = 0;
for (size_t j = 0; j < 8 && (i + j) < bytes.size(); ++j) {
val |= (static_cast<uint64_t>(bytes[i + j]) << (j * 8));
}
data.push_back(val);
}
// Remove trailing zeros to normalize representation
while (data.size() > 1 && data.back() == 0) {
data.pop_back();
}
return data;
}

// Converts a 64-bit word array to a little-endian byte array
[[nodiscard]] inline std::vector<uint8_t> byteArrayToBytes(const ByteArray& data,
size_t targetSize = 0) {
std::vector<uint8_t> bytes;
bytes.reserve(data.size() * 8);
for (uint64_t word : data) {
for (int i = 0; i < 8; ++i) {
bytes.push_back(static_cast<uint8_t>((word >> (i * 8)) & 0xFF));
}
}
if (targetSize == 0) {
// Trim trailing zeros but keep at least 1 byte if the number is 0
while (bytes.size() > 1 && bytes.back() == 0) {
bytes.pop_back();
}
} else {
// Adjust array size to match the exact block or key size constraint
if (bytes.size() < targetSize) {
bytes.resize(targetSize, 0);
} else if (bytes.size() > targetSize) {
while (bytes.size() > targetSize && bytes.back() == 0) {
bytes.pop_back();
}
}
}
return bytes;
}
Loading
Loading