Compare commits

...

5 Commits

Author SHA1 Message Date
612086dfb7 feat: add more robust tests
- add test to cover corner cases and known failure points

Signed-off-by: Avinal Kumar <avinal.xlvii@gmail.com>
2025-12-06 18:23:08 +05:30
5448271298 Merge pull request #7 from avinal/avinal/improve
All checks were successful
Build and Test / build (push) Successful in 19s
Improve GitHub Actions flow and increse Cmake version
2025-12-06 15:45:45 +05:30
c940ba9f4d Improve GitHub Actions flow and increse Cmake version
- Increased CMake minimum version to 3.30
- added test run in github workflow

Signed-off-by: Avinal Kumar <avinal.xlvii@gmail.com>
2025-12-06 15:44:43 +05:30
9d303b7855 Improve GitHub Actions flow and increse Cmake version
Some checks failed
Build and Test / build (Debug, clang) (push) Failing after 18s
Build and Test / build (Debug, gcc) (push) Failing after 17s
Build and Test / build (Release, clang) (push) Failing after 25s
Build and Test / build (Release, gcc) (push) Failing after 17s
Build and Test / sanitize (push) Failing after 3s
- Increased CMake minimum version to 3.30
- added test run in github workflow

Signed-off-by: Avinal Kumar <avinal.xlvii@gmail.com>
2025-12-04 21:13:31 +05:30
f9ae6fddfd Fix Blowfish and Blowfish2 correctness issues and improve initialization safety
In Blowfish
- Fix incorrect F-function byte extraction (critical bug).
- Correct key-schedule handling by using `uint8_t` key bytes.
- Initialize local variables in `initialize()` to prevent UB.
- Improve decrypt loop and XOR usage for clarity and correctness.

In Blowfish2
- Zero-initialize P-array and S-boxes to guarantee deterministic state.
- Fix incorrect key size comment (448 bits, not 4224 bits).
- Improve F-function byte extraction clarity.
- Normalize round loop logic and use XOR-assignment.

Others
- Replace macro `N` with `constexpr N`.
- Add `noexcept` to internal operations.
- Add `initialize(const uint8_t*, size_t)` overload for binary keys.
- Clean up readability and internal consistency across both ciphers.

Signed-off-by: Avinal Kumar <avinal.xlvii@gmail.com>
2025-12-04 21:11:16 +05:30
19 changed files with 558 additions and 305 deletions

View File

@@ -1,29 +1,33 @@
name: Build C++ Project name: Build and Test
on: [push] on: [push, pull_request]
jobs: jobs:
build: build:
name: Build on Ubuntu with GCC runs-on: ubuntu-latest
runs-on: ubuntu-latest # For Gitea, ensure you have a runner with the 'ubuntu-latest' label
# Environment variables for the C/C++ compiler
env:
CC: gcc
CXX: g++
steps: steps:
# Step 1: Check out the repository code - uses: actions/checkout@v4
- name: Checkout repository
uses: actions/checkout@v4 # Updated to the latest version for best practice
# Note for Gitea: Ensure your instance can access this action or use a native 'git clone' command.
# Step 2: Configure the project and build it - name: Install latest CMake
- name: Configure and Build
run: | run: |
# Configure using CMake. The -B flag creates the 'build' directory. python3 -m venv $HOME/cmake-venv
# The build type is now hardcoded to 'Debug'. source $HOME/cmake-venv/bin/activate
cmake -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_VERBOSE_MAKEFILE=ON . pip install --upgrade pip
pip install cmake
echo "$HOME/cmake-venv/bin" >> $GITHUB_PATH
# Build the project using the generated configuration. - name: Configure with sanitizers
cmake --build build --config Debug run: |
SAN="-fsanitize=address,undefined -fno-omit-frame-pointer"
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_FLAGS="$SAN" \
-DCMAKE_CXX_FLAGS="$SAN"
- name: Build
run: cmake --build build -j
- name: Run tests
working-directory: build
run: ctest --output-on-failure

1
.gitignore vendored
View File

@@ -36,3 +36,4 @@
cmake-build-debug cmake-build-debug
build build
.vscode .vscode
.cache

View File

@@ -1,53 +1,72 @@
cmake_minimum_required(VERSION 3.1) cmake_minimum_required(VERSION 3.30)
project(blowfish VERSION 1.0.0 LANGUAGES CXX) project(blowfish VERSION 1.0.0 LANGUAGES CXX)
message(STATUS "CMake version: ${CMAKE_VERSION}")
message(STATUS "Project version: ${PROJECT_VERSION}")
if (NOT CMAKE_CXX_STANDARD) # ===== compiler settings =====
set(CMAKE_CXX_STANDARD 14) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
elseif (CMAKE_CXX_STANDARD LESS 11)
message(WARNING "CMAKE_CXX_STANDARD has been set to '${CMAKE_CXX_STANDARD}' which is lower than the minimum required standard (c++14).")
endif ()
message(STATUS "Using C++ standard c++${CMAKE_CXX_STANDARD}") set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
message(STATUS "Using C++ standard c++${CMAKE_CXX_STANDARD}")
message (STATUS "CMake version: ${CMAKE_VERSION}") set(BF_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
message (STATUS "Project version: ${PROJECT_VERSION}")
set(BLOWFISH_SRC ${PROJECT_SOURCE_DIR}/src/blowfish.cc) # ===== blowfish library =====
set(BLOWFISH2_SRC ${PROJECT_SOURCE_DIR}/src/blowfish2.cc) add_library(blowfish)
source_group(src FILES ${BLOWFISH_SRC} ${BLOWFISH2_SRC}) target_compile_features(blowfish PUBLIC cxx_std_17)
set(BLOWFISH_TEST ${PROJECT_SOURCE_DIR}/tests/Main.cpp) target_sources(blowfish
set(BLOWFISH2_TEST ${PROJECT_SOURCE_DIR}/tests/Main2.cpp) PRIVATE
source_group(tests FILES ${BLOWFISH_TEST} ${BLOWFISH2_TEST}) src/blowfish.cc
PUBLIC FILE_SET HEADERS
set(BLOWFISH_INC ${PROJECT_SOURCE_DIR}/include/blowfish/blowfish.h) BASE_DIRS ${BF_INCLUDE_DIR}
set(BLOWFISH2_INC ${PROJECT_SOURCE_DIR}/include/blowfish/blowfish2.h) FILES include/blowfish/blowfish.h
source_group(include FILES ${BLOWFISH_INC} ${BLOWFISH2_INC})
set(BLOWFISH_DOC
README.md
LICENSE
) )
source_group(doc FILES ${BLOWFISH_DOC})
set(BLOWFISH_SCRIPTS target_include_directories(blowfish PUBLIC ${BF_INCLUDE_DIR})
.gitattributes
.gitignore # ===== blowfish2 library =====
build.sh add_library(blowfish2)
target_compile_features(blowfish2 PUBLIC cxx_std_14)
target_sources(blowfish2
PRIVATE
src/blowfish2.cc
PUBLIC FILE_SET HEADERS
BASE_DIRS ${BF_INCLUDE_DIR}
FILES include/blowfish/blowfish2.h
) )
source_group(scripts FILES ${BLOWFISH_SCRIPTS})
add_library(blowfish ${BLOWFISH_SRC} ${BLOWFISH_INC} ${BLOWFISH_SCRIPTS} ${BLOWFISH_DOC}) target_include_directories(blowfish2 PUBLIC ${BF_INCLUDE_DIR})
add_library(blowfish2 ${BLOWFISH2_SRC} ${BLOWFISH2_INC} ${BLOWFISH_SCRIPTS} ${BLOWFISH_DOC})
target_include_directories(blowfish PUBLIC ${PROJECT_SOURCE_DIR}/include) # ===== Tests =====
target_include_directories(blowfish2 PUBLIC ${PROJECT_SOURCE_DIR}/include) include(CTest)
if (BUILD_TESTING)
add_executable(bf_test ${BLOWFISH_TEST} ${BLOWFISH_SRC} ${BLOWFISH_INC}) # ---- Blowfish Test ----
add_executable(bf2_test ${BLOWFISH2_TEST} ${BLOWFISH2_SRC} ${BLOWFISH2_INC}) add_executable(bf_test
tests/Main.cpp
tests/test_framework.h
tests/test_vectors.cpp
tests/test_roundtrip.cpp
tests/test_avalanche.cpp
tests/test_properties.cpp
)
target_link_libraries(bf_test PRIVATE blowfish)
add_test(NAME BlowfishTests COMMAND bf_test)
target_include_directories(bf_test PUBLIC ${PROJECT_SOURCE_DIR}/include) # ---- Blowfish2 Test ----
target_include_directories(bf2_test PUBLIC ${PROJECT_SOURCE_DIR}/include) add_executable(bf2_test
tests/Main2.cpp
tests/test_framework.h
tests/bf2_test_vectors.cpp
tests/bf2_test_roundtrip.cpp
tests/bf2_test_avalanche.cpp
tests/bf2_test_properties.cpp
)
target_link_libraries(bf2_test PRIVATE blowfish2)
add_test(NAME Blowfish2Tests COMMAND bf2_test)
endif()

View File

@@ -1,10 +1,17 @@
# Blowfish and Blowfish2 Encryption Algorithm # Blowfish and Blowfish2 Encryption Algorithm
<p align=center><a href="https://github.com/avinal/blowfish/actions"><img alt="build" src="https://github.com/avinal/blowfish/workflows/build/badge.svg?branch=main"></a><a href="https://github.com/avinal/blowfish/blob/main/LICENSE"><img src="https://img.shields.io/github/license/avinal/blowfish" alt="license"></a></p> [![Build Status](https://github.com/avinal/blowfish/actions/workflows/cmake.yml/badge.svg)](https://github.com/avinal/blowfish/actions/workflows/cmake.yml)
Blowfish is a symmetric block cipher that can be used as a drop-in replacement for DES or IDEA. It takes a variable-length key, from 32 bits to 448 bits, making it ideal for both domestic and exportable use. Blowfish was designed in 1993 by Bruce Schneier as a fast, free alternative to existing encryption algorithms. Since then it has been analyzed considerably, and it is slowly gaining acceptance as a strong encryption algorithm. Blowfish is unpatented and license-free, and is available free for all uses. Blowfish is a symmetric block cipher that can be used as a drop-in replacement for DES or IDEA. It
takes a variable-length key, from 32 bits to 448 bits, making it ideal for both domestic and
exportable use. Blowfish was designed in 1993 by Bruce Schneier as a fast, free alternative to
existing encryption algorithms. Since then it has been analyzed considerably, and it is slowly
gaining acceptance as a strong encryption algorithm. Blowfish is not patented and license-free,
and is available free for all uses.
Blowfish 2 was released in 2005. It has exactly the same design but has twice as many S tables and uses 64-bit integers instead of 32-bit integers. It no longer works on 64-bit blocks but on 128-bit blocks like AES. 128-bit block, 64 rounds, key up to 4224 bits. Blowfish 2 was released in 2005. It has exactly the same design but has twice as many S tables and
uses 64-bit integers instead of 32-bit integers. It no longer works on 64-bit blocks but on 128-bit
blocks like AES. 128-bit block, 64 rounds, key up to 4224 bits.
## About this project ## About this project
@@ -12,8 +19,12 @@ This is a C++ implementation of the encryption algorithm.
## How to use this in your project? ## How to use this in your project?
1. You may fork it and use it like any other source file in your project. You only need [blowfish.hpp](include/blowfish/blowfish.hpp) and [blowfish.cpp](src/blowfish.cpp) files. Just modify the header as per your convienence. 1. You may fork it and use it like any other source file in your project. You only need
2. If you are using CMake, the work is lot easier. You can add this as a git submodule. It isolates your project from this dependency. [blowfish.hpp](include/blowfish/blowfish.hpp) and [blowfish.cpp](src/blowfish.cpp) files. Just
modify the header as per your convenience.
2. If you are using CMake, the work is lot easier. You can add this as a git submodule. It isolates
your project from this dependency.
```bash ```bash
# In your project root type these commands # In your project root type these commands
@@ -21,9 +32,37 @@ This is a C++ implementation of the encryption algorithm.
# considering this addition is your only change # considering this addition is your only change
git commit -m "blowfish submodule added" git commit -m "blowfish submodule added"
git push origin main git push origin main
``` ```
Add this to your CMakeLists.txt as well. Add this to your CMakeLists.txt as well.
## Building
This library has no dependencies on any other code. Just a C++ compiler should be enough to build
this. Although this library is supposed to be used under some other project, sometimes you may need
to build this. Here are the prerequisites:
- [CMake](https://cmake.org/download/)
- [G++](https://gcc.gnu.org/) or [Clang](https://clang.llvm.org/)
- [Make](https://www.gnu.org/software/make/) or [Ninja Build](https://ninja-build.org/) or any other CMake compatible generator.
Here is how I install them on Fedora:
```shell
sudo dnf install clang cmake g++ make ninja-build
```
Configure CMake, enabled debug for development purposes:
```shell
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
```
Build using configured generator:
```shell
cmake --build build --config Debug
```
## References ## References

View File

@@ -4,32 +4,34 @@
// Original Blowfish Algorithm copyright: // Original Blowfish Algorithm copyright:
// SPDX-FileCopyrightText: 1997 Paul Kocher // SPDX-FileCopyrightText: 1997 Paul Kocher
#include <algorithm> #pragma once
#include <array> #include <array>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#define MAXKEYBYTES 56 // 448 bits max static constexpr uint32_t BF_NUM_ROUNDS = 16;
#define N 16 static constexpr uint32_t BF_MAX_KEYBYTES = 56;
#if !defined(BLOWFISH_BLOWFISH_H_) #if !defined(BLOWFISH_BLOWFISH_H_)
#define BLOWFISH_BLOWFISH_H_ #define BLOWFISH_BLOWFISH_H_
class Blowfish { class Blowfish {
private: private:
std::array<uint32_t, N + 2> PArray; std::array<uint32_t, BF_NUM_ROUNDS + 2> PArray{};
std::array<std::array<uint32_t, 256>, 4> Sboxes; std::array<std::array<uint32_t, 256>, 4> Sboxes{};
uint32_t F(uint32_t x); uint32_t F(uint32_t x) const noexcept;
public: public:
Blowfish() {} Blowfish() = default;
Blowfish(std::string const &key); explicit Blowfish(std::string const &key);
Blowfish(Blowfish const &) = delete; Blowfish(Blowfish const &) = delete;
void initialize(std::string const &key); void initialize(const uint8_t *key, size_t keylen);
void initialize(const std::string &key);
void encrypt(uint32_t &xl, uint32_t &xr); void encrypt(uint32_t &xl, uint32_t &xr) noexcept;
void decrypt(uint32_t &xl, uint32_t &xr); void decrypt(uint32_t &xl, uint32_t &xr) noexcept;
~Blowfish();
}; };
#endif // BLOWFISH_BLOWFISH_H_ #endif // BLOWFISH_BLOWFISH_H_

View File

@@ -4,32 +4,35 @@
// Original Blowfish 2 Algorithm copyright: // Original Blowfish 2 Algorithm copyright:
// SPDX-FileCopyrightText: 2005 Alexander Pukall // SPDX-FileCopyrightText: 2005 Alexander Pukall
#include <algorithm> #pragma once
#include <array> #include <array>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#define MAXKEYBYTES 56 // 4224 bits max static constexpr uint64_t BF2_NUM_ROUNDS = 64;
#define N 64 static constexpr uint64_t BF2_MAX_KEYBYTES = 56;
#if !defined(BLOWFISH_BLOWFISH2_H_) #if !defined(BLOWFISH_BLOWFISH2_H_)
#define BLOWFISH_BLOWFISH2_H_ #define BLOWFISH_BLOWFISH2_H_
class Blowfish2 { class Blowfish2 {
private: private:
std::array<uint64_t, N + 2> PArray; std::array<uint64_t, BF2_NUM_ROUNDS + 2> PArray{};
std::array<std::array<uint64_t, 256>, 8> Sboxes; std::array<std::array<uint64_t, 256>, 8> Sboxes{};
uint64_t F(uint64_t x); uint64_t F(uint64_t x) const noexcept;
public: public:
Blowfish2() {} Blowfish2() = default;
Blowfish2(std::string const &key); explicit Blowfish2(const std::string &key) { initialize(key); }
Blowfish2(Blowfish2 const &) = delete;
Blowfish2(const Blowfish2 &) = delete;
Blowfish2 &operator=(const Blowfish2 &) = delete;
void initialize(std::string const &key); void initialize(std::string const &key);
void initialize(const uint8_t *key, size_t keylen);
void encrypt(uint64_t &xl, uint64_t &xr); void encrypt(uint64_t &xl, uint64_t &xr) noexcept;
void decrypt(uint64_t &xl, uint64_t &xr); void decrypt(uint64_t &xl, uint64_t &xr) noexcept;
}; };
#endif // BLOWFISH_BLOWFISH2_H_ #endif // BLOWFISH_BLOWFISH2_H_

View File

@@ -6,13 +6,13 @@
#include <blowfish/blowfish.h> #include <blowfish/blowfish.h>
static const std::array<uint32_t, 16 + 2> P = { static const std::array<uint32_t, 16 + 2> BF_PARRAY_INIT = {
0x243F6A88L, 0x85A308D3L, 0x13198A2EL, 0x03707344L, 0xA4093822L, 0x243F6A88L, 0x85A308D3L, 0x13198A2EL, 0x03707344L, 0xA4093822L,
0x299F31D0L, 0x082EFA98L, 0xEC4E6C89L, 0x452821E6L, 0x38D01377L, 0x299F31D0L, 0x082EFA98L, 0xEC4E6C89L, 0x452821E6L, 0x38D01377L,
0xBE5466CFL, 0x34E90C6CL, 0xC0AC29B7L, 0xC97C50DDL, 0x3F84D5B5L, 0xBE5466CFL, 0x34E90C6CL, 0xC0AC29B7L, 0xC97C50DDL, 0x3F84D5B5L,
0xB5470917L, 0x9216D5D9L, 0x8979FB1BL}; 0xB5470917L, 0x9216D5D9L, 0x8979FB1BL};
static const std::array<std::array<uint32_t, 256>, 4> S = { static const std::array<std::array<uint32_t, 256>, 4> BF_SBOX_INT = {
{{0xD1310BA6L, 0x98DFB5ACL, 0x2FFD72DBL, 0xD01ADFB7L, 0xB8E1AFEDL, {{0xD1310BA6L, 0x98DFB5ACL, 0x2FFD72DBL, 0xD01ADFB7L, 0xB8E1AFEDL,
0x6A267E96L, 0xBA7C9045L, 0xF12C7F99L, 0x24A19947L, 0xB3916CF7L, 0x6A267E96L, 0xBA7C9045L, 0xF12C7F99L, 0x24A19947L, 0xB3916CF7L,
0x0801F2E2L, 0x858EFC16L, 0x636920D8L, 0x71574E69L, 0xA458FEA3L, 0x0801F2E2L, 0x858EFC16L, 0x636920D8L, 0x71574E69L, 0xA458FEA3L,
@@ -225,24 +225,24 @@ static const std::array<std::array<uint32_t, 256>, 4> S = {
0x3F09252DL, 0xC208E69FL, 0xB74E6132L, 0xCE77E25BL, 0x578FDFE3L, 0x3F09252DL, 0xC208E69FL, 0xB74E6132L, 0xCE77E25BL, 0x578FDFE3L,
0x3AC372E6L}}}; 0x3AC372E6L}}};
void Blowfish::initialize(std::string const &key) { void Blowfish::initialize(const uint8_t *key, size_t keylen) {
uint32_t data, datal, datar; uint32_t data = 0;
Sboxes = S; uint32_t datal = 0;
uint32_t j = 0, keylength = key.length(); uint32_t datar = 0;
for (uint32_t i = 0; i < N + 2; ++i) {
data = 0x00000000; Sboxes = BF_SBOX_INT;
size_t j = 0;
for (uint32_t i = 0; i < BF_NUM_ROUNDS + 2; ++i) {
data = 0;
for (uint32_t k = 0; k < 4; ++k) { for (uint32_t k = 0; k < 4; ++k) {
data = (data << 8) | key[j]; data = (data << 8) | key[j];
if (++j >= keylength) { j = (j + 1) % keylen;
j = 0;
}
} }
PArray[i] = P[i] ^ data; PArray[i] = BF_PARRAY_INIT[i] ^ data;
} }
datal = 0x00000000;
datar = 0x00000000;
for (uint32_t i = 0; i < N + 2; i += 2) { for (uint32_t i = 0; i < BF_NUM_ROUNDS + 2; i += 2) {
encrypt(datal, datar); encrypt(datal, datar);
PArray[i] = datal; PArray[i] = datal;
PArray[i + 1] = datar; PArray[i + 1] = datar;
@@ -257,52 +257,51 @@ void Blowfish::initialize(std::string const &key) {
} }
} }
Blowfish::Blowfish(std::string const &key) { initialize(key); } void Blowfish::initialize(const std::string &key) {
initialize(reinterpret_cast<const uint8_t *>(key.data()), key.size());
}
uint32_t Blowfish::F(uint32_t x) { Blowfish::Blowfish(const std::string &key) : PArray{}, Sboxes{} {
uint16_t a, b, c, d; initialize(key);
uint32_t y; }
d = (unsigned int)(x & 0xFF); uint32_t Blowfish::F(uint32_t x) const noexcept {
x >>= 8; uint8_t a = (x >> 24) & 0xFF;
d = (unsigned int)(x & 0xFF); uint8_t b = (x >> 16) & 0xFF;
x >>= 8; uint8_t c = (x >> 8) & 0xFF;
c = (unsigned int)(x & 0xFF); uint8_t d = x & 0xFF;
x >>= 8;
b = (unsigned int)(x & 0xFF);
x >>= 8;
a = (unsigned int)(x & 0xFF);
y = Sboxes[0][a] + Sboxes[1][b]; uint32_t y = Sboxes[0][a] + Sboxes[1][b];
y ^= Sboxes[2][c]; y ^= Sboxes[2][c];
y += Sboxes[3][d]; y += Sboxes[3][d];
return y; return y;
} }
void Blowfish::encrypt(uint32_t &xl, uint32_t &xr) { void Blowfish::encrypt(uint32_t &xl, uint32_t &xr) noexcept {
uint32_t Xl = xl; uint32_t Xl = xl;
uint32_t Xr = xr; uint32_t Xr = xr;
for (uint32_t i = 0; i < N; ++i) { for (uint32_t i = 0; i < BF_NUM_ROUNDS; ++i) {
Xl ^= PArray[i]; Xl ^= PArray[i];
Xr = F(Xl) ^ Xr; Xr = F(Xl) ^ Xr;
std::swap(Xl, Xr); std::swap(Xl, Xr);
} }
std::swap(Xl, Xr); std::swap(Xl, Xr);
Xr ^= PArray[N]; Xr ^= PArray[BF_NUM_ROUNDS];
Xl ^= PArray[N + 1]; Xl ^= PArray[BF_NUM_ROUNDS + 1];
xl = Xl; xl = Xl;
xr = Xr; xr = Xr;
} }
void Blowfish::decrypt(uint32_t &xl, uint32_t &xr) { void Blowfish::decrypt(uint32_t &xl, uint32_t &xr) noexcept {
uint32_t Xl = xl; uint32_t Xl = xl;
uint32_t Xr = xr; uint32_t Xr = xr;
for (uint32_t i = N + 1; i > 1; --i) { for (int i = BF_NUM_ROUNDS + 1; i >= 2; --i) {
Xl ^= PArray[i]; Xl ^= PArray[i];
Xr = F(Xl) ^ Xr; Xr ^= F(Xl);
std::swap(Xl, Xr); std::swap(Xl, Xr);
} }
@@ -312,3 +311,9 @@ void Blowfish::decrypt(uint32_t &xl, uint32_t &xr) {
xl = Xl; xl = Xl;
xr = Xr; xr = Xr;
} }
Blowfish::~Blowfish() {
std::fill(PArray.begin(), PArray.end(), 0);
for (auto &row : Sboxes) {
std::fill(row.begin(), row.end(), 0);
}
}

View File

@@ -6,7 +6,7 @@
#include <blowfish/blowfish2.h> #include <blowfish/blowfish2.h>
static const std::array<uint64_t, 64 + 2> P = { static const std::array<uint64_t, 64 + 2> BF2_PARRAY_INIT = {
0x243F6A8885A308D3, 0x13198A2E03707344, 0xA4093822299F31D0, 0x243F6A8885A308D3, 0x13198A2E03707344, 0xA4093822299F31D0,
0x082EFA98EC4E6C89, 0x452821E638D01377, 0xBE5466CF34E90C6C, 0x082EFA98EC4E6C89, 0x452821E638D01377, 0xBE5466CF34E90C6C,
0xC0AC29B7C97C50DD, 0x3F84D5B5B5470917, 0x9216D5D98979FB1B, 0xC0AC29B7C97C50DD, 0x3F84D5B5B5470917, 0x9216D5D98979FB1B,
@@ -30,7 +30,7 @@ static const std::array<uint64_t, 64 + 2> P = {
0x1BFEDF72429B023D, 0x37D0D724D00A1248, 0xDB0FEAD349F1C09B, 0x1BFEDF72429B023D, 0x37D0D724D00A1248, 0xDB0FEAD349F1C09B,
0x075372C980991B7B, 0x25D479D8F6E8DEF7, 0xE3FE501AB6794C3B}; 0x075372C980991B7B, 0x25D479D8F6E8DEF7, 0xE3FE501AB6794C3B};
static const std::array<std::array<uint64_t, 256>, 8> S = { static const std::array<std::array<uint64_t, 256>, 8> BF2_SBOX_INIT = {
{{0x976CE0BD04C006BA, 0xC1A94FB6409F60C4, 0x5E5C9EC2196A2463, {{0x976CE0BD04C006BA, 0xC1A94FB6409F60C4, 0x5E5C9EC2196A2463,
0x68FB6FAF3E6C53B5, 0x1339B2EB3B52EC6F, 0x6DFC511F9B30952C, 0x68FB6FAF3E6C53B5, 0x1339B2EB3B52EC6F, 0x6DFC511F9B30952C,
0xCC814544AF5EBD09, 0xBEE3D004DE334AFD, 0x660F2807192E4BB3, 0xCC814544AF5EBD09, 0xBEE3D004DE334AFD, 0x660F2807192E4BB3,
@@ -727,24 +727,29 @@ static const std::array<std::array<uint64_t, 256>, 8> S = {
0xBF2FAD7CDA8F11B8, 0x9B14B76D08EF72BE, 0x8A0E0EEC190EBEBA, 0xBF2FAD7CDA8F11B8, 0x9B14B76D08EF72BE, 0x8A0E0EEC190EBEBA,
0x8CF97F6ECE339C68}}}; 0x8CF97F6ECE339C68}}};
void Blowfish2::initialize(std::string const &key) { void Blowfish2::initialize(const std::string &key) {
uint64_t data, datal, datar; initialize(reinterpret_cast<const uint8_t *>(key.data()), key.size());
Sboxes = S; }
uint64_t j = 0, keylength = key.length();
for (uint64_t i = 0; i < N + 2; ++i) { void Blowfish2::initialize(const uint8_t *key, size_t keylen) {
data = 0x00000000; uint64_t data = 0;
uint64_t datal = 0;
uint64_t datar = 0;
Sboxes = BF2_SBOX_INIT; // assumes static const S exists
size_t j = 0;
for (uint64_t i = 0; i < BF2_NUM_ROUNDS + 2; ++i) {
data = 0;
for (uint64_t k = 0; k < 8; ++k) { for (uint64_t k = 0; k < 8; ++k) {
data = (data << 8) | key[j]; data = (data << 8) | key[j];
if (++j >= keylength) { j = (j + 1) % keylen;
j = 0;
}
} }
PArray[i] = P[i] ^ data; PArray[i] = BF2_PARRAY_INIT[i] ^ data;
} }
datal = 0x00000000;
datar = 0x00000000;
for (uint64_t i = 0; i < N + 2; i += 2) { for (uint64_t i = 0; i < BF2_NUM_ROUNDS + 2; i += 2) {
encrypt(datal, datar); encrypt(datal, datar);
PArray[i] = datal; PArray[i] = datal;
PArray[i + 1] = datar; PArray[i + 1] = datar;
@@ -759,9 +764,7 @@ void Blowfish2::initialize(std::string const &key) {
} }
} }
Blowfish2::Blowfish2(std::string const &key) { initialize(key); } uint64_t Blowfish2::F(uint64_t x) const noexcept {
uint64_t Blowfish2::F(uint64_t x) {
unsigned int a, b, c, d, e, f, g, h; unsigned int a, b, c, d, e, f, g, h;
uint64_t y; uint64_t y;
@@ -791,28 +794,28 @@ uint64_t Blowfish2::F(uint64_t x) {
return y; return y;
} }
void Blowfish2::encrypt(uint64_t &xl, uint64_t &xr) { void Blowfish2::encrypt(uint64_t &xl, uint64_t &xr) noexcept {
uint64_t Xl = xl; uint64_t Xl = xl;
uint64_t Xr = xr; uint64_t Xr = xr;
for (uint64_t i = 0; i < N; ++i) { for (uint64_t i = 0; i < BF2_NUM_ROUNDS; ++i) {
Xl ^= PArray[i]; Xl ^= PArray[i];
Xr = F(Xl) ^ Xr; Xr = F(Xl) ^ Xr;
std::swap(Xl, Xr); std::swap(Xl, Xr);
} }
std::swap(Xl, Xr); std::swap(Xl, Xr);
Xr ^= PArray[N]; Xr ^= PArray[BF2_NUM_ROUNDS];
Xl ^= PArray[N + 1]; Xl ^= PArray[BF2_NUM_ROUNDS + 1];
xl = Xl; xl = Xl;
xr = Xr; xr = Xr;
} }
void Blowfish2::decrypt(uint64_t &xl, uint64_t &xr) { void Blowfish2::decrypt(uint64_t &xl, uint64_t &xr) noexcept {
uint64_t Xl = xl; uint64_t Xl = xl;
uint64_t Xr = xr; uint64_t Xr = xr;
for (uint64_t i = N + 1; i > 1; --i) { for (uint64_t i = BF2_NUM_ROUNDS + 1; i > 1; --i) {
Xl ^= PArray[i]; Xl ^= PArray[i];
Xr = F(Xl) ^ Xr; Xr = F(Xl) ^ Xr;
std::swap(Xl, Xr); std::swap(Xl, Xr);

View File

@@ -1,58 +1,6 @@
#include <iostream> // SPDX-FileCopyrightText: 2025 Avinal Kumar avinal.xlvii@gmail.com
// SPDX-License-Identifier: MIT
#include <blowfish/blowfish.h> #include "test_framework.h"
std::string from_uint(uint32_t sh) { int main() { return tests_summary(); }
std::string re("");
for (int i = 0; i < 4; i++) {
re += (unsigned char)(sh >> i * 8);
}
return re;
}
int main(int argc, char const *argv[]) {
std::string key("test@pass47");
std::string message("My name is Avinal and I am cute");
std::string cipher("");
int len = message.length();
int j = sizeof(uint32_t);
int rem =
((len > j * 2) ? (((len / j * 2) + 1) * j * 2 - len) : (j * 2 - len));
message.append(rem, '\0');
len = message.length();
Blowfish blowfish(key);
std::cout << "My message is: " << message << std::endl;
uint32_t lm, rm;
for (size_t i = 0; i < len; i += 8) {
lm = 0;
rm = 0;
lm = *reinterpret_cast<unsigned int *>(
const_cast<char *>(message.substr(i, j).c_str()));
rm = *reinterpret_cast<unsigned int *>(
const_cast<char *>(message.substr(i + 4, j).c_str()));
blowfish.encrypt(lm, rm);
cipher += from_uint(lm) + from_uint(rm);
}
std::cout << cipher << std::endl;
std::string decipher("");
len = cipher.length();
std::cout << "length: " << len << std::endl;
for (size_t i = 0; i < len; i += 8) {
lm = 0;
rm = 0;
lm = *reinterpret_cast<unsigned int *>(
const_cast<char *>(cipher.substr(i, 4).c_str()));
rm = *reinterpret_cast<unsigned int *>(
const_cast<char *>(cipher.substr(i + 4, 4).c_str()));
blowfish.decrypt(lm, rm);
decipher += from_uint(lm) + from_uint(rm);
}
std::cout << decipher << std::endl;
if (message == decipher) {
std::cout << "Test successful!" << std::endl;
return 0;
} else {
return 1;
}
}

View File

@@ -1,105 +1,6 @@
#include <cstdint> // SPDX-FileCopyrightText: 2025 Avinal Kumar avinal.xlvii@gmail.com
#include <iostream> // SPDX-License-Identifier: MIT
#include <blowfish/blowfish2.h> #include "test_framework.h"
#include <ostream>
std::string from_uint(uint64_t sh) { int main() { return tests_summary(); }
std::string re("");
for (int i = 0; i < 8; i++) {
re += (unsigned char)(sh >> i * 8);
}
return re;
}
uint64_t to_uint(std::string &s, size_t index, size_t size) {
return *reinterpret_cast<uint64_t *>(
const_cast<char *>(s.substr(index, size).c_str()));
}
int main(int argc, char const *argv[]) {
std::string key("test@pass47");
std::string message("My name is Avinal and I am cute and smart");
std::string cipher("");
int len = message.length();
int J = sizeof(uint64_t);
int rem =
((len > J * 2) ? (((len / J * 2) + 1) * J * 2 - len) : (J * 2 - len));
message.append(rem, '\0');
len = message.length();
Blowfish2 blowfish(key);
std::cout << "My message is: " << message << J << std::endl;
uint64_t L = 0, R = 0;
for (size_t i = 0; i < len; i += 16) {
L = to_uint(message, i, J);
R = to_uint(message, i + J, J);
blowfish.encrypt(L, R);
cipher += from_uint(L) + from_uint(R);
}
std::cout << "Cipher: " << cipher << std::endl;
std::string decipher("");
len = cipher.length();
std::cout << "length: " << len << std::endl;
for (size_t i = 0; i < len; i += 16) {
L = to_uint(cipher, i, J);
R = to_uint(cipher, i + J, J);
blowfish.decrypt(L, R);
decipher += from_uint(L) + from_uint(R);
}
if (message == decipher) {
std::cout << "Test OK." << std::endl;
} else {
std::cout << "Test failed." << std::endl;
}
// C Blowfish 2 tests
L = 0x0000000000000001, R = 0x0000000000000002;
blowfish.initialize("TESTKEY");
blowfish.encrypt(L, R);
if (L == 0x7B2B9DE71D1B1C62 && R == 0x91C230351177BEE8)
std::cout << "Test encryption OK." << std::endl;
else
std::cout << "Test encryption failed." << std::endl;
blowfish.decrypt(L, R);
if (L == 1 && R == 2)
std::cout << "Test decryption OK." << std::endl;
else
std::cout << "Test decryption failed." << std::endl;
L = 0x0102030405060708;
R = 0x0910111213141516;
blowfish.initialize("A");
blowfish.encrypt(L, R);
if (L == 0xCA38165603F9915C && R == 0x61F0776A0F55E807)
std::cout << "Test encryption OK." << std::endl;
else
std::cout << "Test encryption failed." << std::endl;
blowfish.decrypt(L, R);
if (L == 0x0102030405060708 && R == 0x0910111213141516)
std::cout << "Test decryption OK." << std::endl;
else
std::cout << "Test decryption failed." << std::endl;
L = 0x0102030405060708;
R = 0x0910111213141516;
blowfish.initialize("B");
blowfish.encrypt(L, R);
if (L == 0xD07690A78B109983 && R == 0x8DDF85826F2366C2)
std::cout << "Test encryption OK." << std::endl;
else
std::cout << "Test encryption failed." << std::endl;
blowfish.decrypt(L, R);
if (L == 0x0102030405060708 && R == 0x0910111213141516)
std::cout << "Test decryption OK." << std::endl;
else
std::cout << "Test decryption failed." << std::endl;
}

View File

@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2025 Avinal Kumar avinal.xlvii@gmail.com
// SPDX-License-Identifier: MIT
#include "test_framework.h"
#include <blowfish/blowfish2.h>
static int hamming128(uint64_t a1, uint64_t b1, uint64_t a2, uint64_t b2) {
return __builtin_popcountll(a1 ^ a2) + __builtin_popcountll(b1 ^ b2);
}
// Check that flipping one bit in plaintext or key
// causes large, unpredictable changes in ciphertext.
TEST("Blowfish2 Avalanche Effect") {
Blowfish2 bf("key-for-avalanche");
uint64_t L = 0x1122334455667788ULL;
uint64_t R = 0x99AABBCCDDEEFF00ULL;
uint64_t L0 = L, R0 = R;
bf.encrypt(L0, R0);
for (int bit = 0; bit < 64; ++bit) {
uint64_t Lf = L ^ (1ULL << bit);
uint64_t Rf = R;
uint64_t L1 = Lf, R1 = Rf;
bf.encrypt(L1, R1);
int hd = hamming128(L0, R0, L1, R1);
// Expect large hamming distance: ideally >40 for Blowfish2
EXPECT_TRUE(hd > 40);
}
}

View File

@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2025 Avinal Kumar avinal.xlvii@gmail.com
// SPDX-License-Identifier: MIT
#include "test_framework.h"
#include <blowfish/blowfish2.h>
TEST("Blowfish2 no fixed points") {
Blowfish2 bf("fixed-point-check");
for (uint64_t i = 1; i < 50; ++i) {
uint64_t L = i * 0x123456789ABCDEFULL;
uint64_t R = i * 0xFEDCBA987654321ULL;
uint64_t L2 = L, R2 = R;
bf.encrypt(L2, R2);
EXPECT_TRUE(!(L == L2 && R == R2));
}
}
TEST("Blowfish2 no short cycles") {
Blowfish2 bf("cycle-check");
for (uint64_t seed = 1; seed <= 5; ++seed) {
uint64_t L0 = seed;
uint64_t R0 = seed * 12345;
uint64_t L = L0, R = R0;
for (int iter = 0; iter < 12; ++iter) {
bf.encrypt(L, R);
EXPECT_TRUE(!(L == L0 && R == R0));
}
}
}

View File

@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: 2025 Avinal Kumar avinal.xlvii@gmail.com
// SPDX-License-Identifier: MIT
#include "test_framework.h"
#include <blowfish/blowfish2.h>
#include <random>
// Ensure encrypt -> decrypt returns original plaintext
// for fixed and randomized test blocks.
TEST("Blowfish2 encrypt/decrypt roundtrip") {
Blowfish2 bf("test-key-2");
std::mt19937_64 rng(987654321ULL);
for (int i = 0; i < 400; ++i) {
uint64_t L = rng();
uint64_t R = rng();
uint64_t L2 = L, R2 = R;
bf.encrypt(L2, R2);
bf.decrypt(L2, R2);
EXPECT_EQ(L, L2);
EXPECT_EQ(R, R2);
}
}

View File

@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2025 Avinal Kumar avinal.xlvii@gmail.com
// SPDX-License-Identifier: MIT
#include "test_framework.h"
#include <blowfish/blowfish2.h>
// Known Answer Tests (KAT): Validate Blowfish output against fixed reference
// vectors to ensure algorithmic correctness.
TEST("Blowfish2 Known Answer Vector")
{
Blowfish2 bf("abcdefghijklmnopqrstuvwxyz");
uint64_t L = 0x424C4F5742463231ULL; // "BLOWBF21" (just a stable block)
uint64_t R = 0x4649534832463231ULL; // "FISH2F21"
uint64_t Lc = L, Rc = R;
bf.encrypt(Lc, Rc);
EXPECT_EQ(Lc, 0xF69E30BC3A4E8B0AULL);
EXPECT_EQ(Rc, 0x31E5F507F5412293ULL);
bf.decrypt(Lc, Rc);
EXPECT_EQ(Lc, L);
EXPECT_EQ(Rc, R);
}

34
tests/test_avalanche.cpp Normal file
View File

@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2025 Avinal Kumar avinal.xlvii@gmail.com
// SPDX-License-Identifier: MIT
#include "test_framework.h"
#include <blowfish/blowfish.h>
static int hamming(uint64_t a, uint64_t b) {
return __builtin_popcountll(a ^ b);
}
// Check that flipping one bit in plaintext or key
// causes large, unpredictable changes in ciphertext.
TEST("Blowfish Avalanche Effect") {
Blowfish bf("key");
uint32_t L = 0x11223344, R = 0x55667788;
uint32_t Lc = L, Rc = R;
bf.encrypt(Lc, Rc);
uint64_t C1 = (uint64_t(Lc) << 32) | Rc;
for (int bit = 0; bit < 32; ++bit) {
uint32_t Lflip = L ^ (1u << bit);
uint32_t Rflip = R;
uint32_t L2 = Lflip, R2 = Rflip;
bf.encrypt(L2, R2);
uint64_t C2 = (uint64_t(L2) << 32) | R2;
int hd = hamming(C1, C2);
EXPECT_TRUE(hd > 20); // Strong avalanche threshold
}
}

87
tests/test_framework.h Normal file
View File

@@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: 2025 Avinal Kumar avinal.xlvii@gmail.com
// SPDX-License-Identifier: MIT
#pragma once
#include <functional>
#include <iostream>
#include <string>
#include <vector>
inline int g_failures = 0;
struct TestCase {
std::string name;
std::function<void()> fn;
};
inline std::vector<TestCase> &test_registry() {
static std::vector<TestCase> r;
return r;
}
/* helper to build unique identifiers from __LINE__ */
#define TEST_CONCAT_INNER(a, b) a##b
#define TEST_CONCAT(a, b) TEST_CONCAT_INNER(a, b)
/*
Usage:
TEST("Some name with spaces") { ... }
*/
#define TEST(name) \
static void TEST_CONCAT(test_func_, __LINE__)(); \
static bool TEST_CONCAT(test_reg_, __LINE__) = []() { \
test_registry().push_back({name, TEST_CONCAT(test_func_, __LINE__)}); \
return true; \
}(); \
static void TEST_CONCAT(test_func_, __LINE__)()
#define EXPECT_EQ(a, b) \
do { \
if ((a) != (b)) { \
std::cout << "[FAILED] " << __FILE__ << ":" << __LINE__ << ": expected " \
<< #a << " == " << #b << " but got " << (a) << "\n"; \
++g_failures; \
} \
} while (0)
#define EXPECT_NE(a, b) \
do { \
if ((a) == (b)) { \
std::cout << "[FAILED] " << __FILE__ << ":" << __LINE__ << ": expected " \
<< #a << " != " << #b << "\n"; \
++g_failures; \
} \
} while (0)
#define EXPECT_TRUE(a) \
do { \
if (!(a)) { \
std::cout << "[FAILED] " << __FILE__ << ":" << __LINE__ \
<< ": expected true: " << #a << "\n"; \
++g_failures; \
} \
} while (0)
inline int tests_summary() {
std::cout << "Running " << test_registry().size() << " tests...\n";
for (auto &t : test_registry()) {
std::cout << "[ RUN ] " << t.name << "\n";
try {
t.fn();
} catch (const std::exception &e) {
std::cout << "[ ERROR ] exception: " << e.what() << "\n";
++g_failures;
} catch (...) {
std::cout << "[ ERROR ] unknown exception\n";
++g_failures;
}
std::cout << "[ DONE ] " << t.name << "\n";
}
if (g_failures == 0)
std::cout << "\nAll tests passed \\^_^/\n";
else
std::cout << "\n" << g_failures << " tests failed /^.^\\\n";
return g_failures;
}

38
tests/test_properties.cpp Normal file
View File

@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: 2025 Avinal Kumar avinal.xlvii@gmail.com
// SPDX-License-Identifier: MIT
#include "test_framework.h"
#include <blowfish/blowfish.h>
// Test edge-case blocks, key lengths, symmetry,
// and consistency across instances.
TEST("Blowfish no fixed points") {
Blowfish bf("key1");
for (uint64_t x = 1; x < 20; ++x) {
uint32_t L = x >> 32;
uint32_t R = x & 0xFFFFFFFFu;
uint32_t L2 = L, R2 = R;
bf.encrypt(L2, R2);
EXPECT_TRUE(!(L2 == L && R2 == R));
}
}
TEST("Blowfish no short encryption cycles") {
Blowfish bf("another-key");
for (uint64_t seed = 1; seed < 10; ++seed) {
uint32_t L = seed >> 32;
uint32_t R = seed & 0xFFFFFFFFu;
uint32_t a = L, b = R;
for (int i = 0; i < 10; ++i) {
bf.encrypt(a, b);
EXPECT_TRUE(!(a == L && b == R));
}
}
}

27
tests/test_roundtrip.cpp Normal file
View File

@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: 2025 Avinal Kumar avinal.xlvii@gmail.com
// SPDX-License-Identifier: MIT
#include "test_framework.h"
#include <blowfish/blowfish.h>
#include <random>
// Ensure encrypt -> decrypt returns original plaintext
// for fixed and randomized test blocks.
TEST("Blowfish encrypt/decrypt roundtrip") {
Blowfish bf("test-key");
std::mt19937_64 rng(12345);
for (int i = 0; i < 500; ++i) {
uint64_t block = rng();
uint32_t L = block >> 32;
uint32_t R = block & 0xFFFFFFFFu;
uint32_t L2 = L, R2 = R;
bf.encrypt(L2, R2);
bf.decrypt(L2, R2);
EXPECT_EQ(L, L2);
EXPECT_EQ(R, R2);
}
}

22
tests/test_vectors.cpp Normal file
View File

@@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: 2025 Avinal Kumar avinal.xlvii@gmail.com
// SPDX-License-Identifier: MIT
#include "test_framework.h"
#include <blowfish/blowfish.h>
// Known Answer Tests (KAT): Validate Blowfish output against fixed reference
// vectors to ensure algorithmic correctness.
TEST("Blowfish Known Test Vectors") {
Blowfish bf("abcdefghijklmnopqrstuvwxyz");
uint32_t L = 0x424C4F57; // "BLOW"
uint32_t R = 0x46495348; // "FISH"
bf.encrypt(L, R);
EXPECT_EQ(L, 0x324ED0FE);
EXPECT_EQ(R, 0xF413A203);
bf.decrypt(L, R);
EXPECT_EQ(L, 0x424C4F57);
EXPECT_EQ(R, 0x46495348);
}