Skip to content

flicherr/pc-sign

Repository files navigation

pcsign

An educational desktop digital-signature application that combines a Flutter UI with a C++23 native cryptographic core through Dart FFI.

C++23 Flutter Dart FFI RSA and MD5 Windows

Document signing screen Signature verification screen
Signature creation Signature verification

Overview

pcsign is a desktop application for demonstrating the creation and verification of a digital signature with a custom RSA and MD5 implementation.

The project is split into two parts:

  • a Flutter/Dart desktop interface that presents the signing and verification workflow;
  • a C++23 shared library that implements hashing, arbitrary-precision decimal arithmetic, RSA key generation, signature creation, and verification.

The two parts communicate through Dart FFI. Flutter loads libpcsign.dll at runtime and calls exported C-compatible functions from the native library.

The application is intentionally small and explicit: intermediate values such as the message hash, RSA parameters, signature, and recovered hash are displayed directly in the UI so the complete signature flow can be inspected.

Warning

This is an educational cryptography project, not a production signing system. It uses MD5 and textbook-style RSA operations without a standardized signature padding scheme such as RSA-PSS or PKCS#1 v1.5. Do not use it to protect real data or secrets.

Features

  • Custom MD5 implementation in C++
  • Custom arbitrary-precision unsigned integer operations using decimal strings
  • RSA key generation
  • Probabilistic prime testing with Miller-Rabin
  • Parallel prime search using std::async
  • Selection of a public exponent from common Fermat-number candidates
  • Modular inverse calculation for the RSA private exponent
  • Modular exponentiation for signing and verification
  • Digital signature creation from a document hash
  • Digital signature verification by comparing the recovered and recalculated hashes
  • Flutter desktop UI with separate sender and recipient views
  • Dart FFI integration with a native Windows DLL
  • Option to generate a new RSA key pair or reuse existing key files
  • Intermediate cryptographic values displayed in the UI

How it works

flowchart LR
    DOC[Document] --> MD5[Custom MD5]
    MD5 --> HASH[Message hash m]

    KEYGEN[RSA key generation] --> PRIVATE[Private key D, N]
    KEYGEN --> PUBLIC[Public key E, N]

    HASH --> SIGN[Modular exponentiation]
    PRIVATE --> SIGN
    SIGN --> S[Signature S]

    S --> VERIFY[Modular exponentiation]
    PUBLIC --> VERIFY
    VERIFY --> RECOVERED[Recovered hash m']

    DOC --> MD5VERIFY[Recalculate hash]
    MD5VERIFY --> HASHVERIFY[Hash m]

    RECOVERED --> COMPARE{m' == m?}
    HASHVERIFY --> COMPARE

    COMPARE -->|yes| VALID[Signature confirmed]
    COMPARE -->|no| INVALID[Verification failed]
Loading

The native core follows a direct RSA signature demonstration.

For a document M, the application first computes a hash:

$$m = H(M)$$

The signature is created with the private exponent:

$$S = m^D \bmod N$$

Verification recovers a value from the signature with the public exponent:

$$m' = S^E \bmod N$$

The application recalculates the document hash and compares the two values:

m' == m  → signature confirmed
m' != m  → verification failed

Signature creation

pcsign signature creation screen

The sender view reads the document from:

pcsign/doc.txt

When Generate keys is enabled, the C++ library generates a new RSA key pair before creating the signature.

The result panel displays the current document and cryptographic values, including:

M  document contents
m  calculated hash
P  generated prime
Q  generated prime
N  RSA modulus
D  private exponent
E  public exponent
S  signature

When key generation is disabled, the existing key files are reused.

Signature verification

pcsign signature verification screen

The recipient view:

  1. recalculates the hash of pcsign/doc.txt;
  2. reads the signature from pcsign/sign.txt;
  3. reads the public key from pcsign/openkey.txt;
  4. calculates S^E mod N;
  5. compares the recovered value with the newly calculated document hash.

The UI displays the signature, public exponent, modulus, recovered hash, and final verification result.

Changing the document, signature, or key material causes the compared values to differ and verification to fail.

Architecture

flowchart LR
    UI[Flutter widgets] --> FFI[Dart FFI]
    FFI --> DLL[libpcsign.dll]

    DLL --> API[Exported C API]
    API --> MD5[MD5 implementation]
    API --> RSA[RSA implementation]
    RSA --> BBINT[Custom big-integer arithmetic]

    API --> FILES[Working files]
Loading

Flutter frontend

The Dart side consists of three main widgets:

File Responsibility
lib/main.dart Desktop window configuration and sender/recipient navigation
lib/sign.dart Signature creation UI and FFI calls
lib/verify.dart Signature verification UI and FFI calls

NavigationRail switches between the sender and recipient views.

The desktop window is constrained to a compact fixed-height layout and uses Flutter Material 3 styling.

Native C++ core

The C++ implementation is built as the pcsign shared library.

Component Responsibility
library.cpp Exported FFI functions and file-based orchestration
rsa.cpp / rsa.h RSA key generation, signing, and verification
md5.cpp / md5.h Custom MD5 hashing implementation
bbint.cpp / bbint.h Arbitrary-precision decimal arithmetic

The public native API exposed to Dart contains four functions:

extern "C" {
    __declspec(dllexport) char* gethash();
    __declspec(dllexport) char* getkeys();
    __declspec(dllexport) char* getsign();
    __declspec(dllexport) char* getverify();
}

Dart resolves these symbols from libpcsign.dll with DynamicLibrary.open() and lookupFunction().

Custom big-integer arithmetic

The RSA implementation does not use OpenSSL, Crypto++, GMP, or another arbitrary-precision library.

Instead, large positive integers are represented as decimal std::string values:

using bbint = std::string;

The project implements the required operations directly:

  • addition;
  • subtraction;
  • multiplication;
  • division;
  • modulo;
  • comparison;
  • greatest common divisor;
  • binary-to-decimal conversion;
  • modular exponentiation;
  • modular inverse.

Modular exponentiation uses the square-and-multiply approach and is shared by both signature creation and verification.

RSA key generation

The current native configuration uses:

#define BITLEN 256
#define ITERLEN 5

Prime candidates are generated from random binary strings and tested with Miller-Rabin.

Prime generation is parallelized across the number of hardware threads reported by:

std::thread::hardware_concurrency()

Each worker searches independently until one task finds a probable prime.

After P and Q are selected:

N    = P × Q
φ(N) = (P - 1) × (Q - 1)

The implementation selects the first exponent coprime with φ(N) from:

65537, 257, 17, 5, 3

The private exponent D is calculated as the modular inverse of E modulo φ(N).

Working files

The current implementation uses a small file-based exchange directory:

pcsign/
├── doc.txt
├── hashsign.txt
├── hashverify.txt
├── keys.txt
├── openkey.txt
├── secretkey.txt
└── sign.txt
File Purpose
doc.txt Document currently being signed or verified
hashsign.txt Hash calculated during signing
hashverify.txt Hash calculated during verification
keys.txt Human-readable generated key values
openkey.txt Public exponent and modulus
secretkey.txt Private exponent and modulus
sign.txt Generated signature

These files make the intermediate steps easy to inspect, but they also mean that the current application uses fixed relative paths instead of a file picker or a dedicated document format.

secretkey.txt contains private key material. The bundled values are demo data; never use this workflow for real private keys.

Project structure

.
├── lib/
│   ├── main.dart
│   ├── sign.dart
│   └── verify.dart
├── pcsign/
│   ├── doc.txt
│   ├── hashsign.txt
│   ├── hashverify.txt
│   ├── keys.txt
│   ├── openkey.txt
│   ├── secretkey.txt
│   └── sign.txt
├── signature/
│   ├── CMakeLists.txt
│   ├── bbint.cpp
│   ├── bbint.h
│   ├── library.cpp
│   ├── library.h
│   ├── md5.cpp
│   ├── md5.h
│   ├── rsa.cpp
│   └── rsa.h
├── libpcsign.dll
├── analysis_options.yaml
├── pubspec.lock
└── pubspec.yaml

Tech stack

  • C++23
  • CMake
  • Dart
  • Flutter
  • Dart FFI
  • Material 3

Flutter dependencies used by the source include:

  • ffi
  • window_size
  • cupertino_icons

The project declares Dart SDK ^3.6.0 in pubspec.yaml.

Build and run

Requirements

  • Windows
  • Flutter SDK with Windows desktop support
  • Dart compatible with the SDK constraint in pubspec.yaml
  • CMake 3.29+
  • A GCC-compatible Windows C++ toolchain with C++23 support, such as MinGW-w64 GCC
  • Ninja or another CMake generator supported by the selected toolchain

The current C++ source uses GCC-compatible facilities including __uint128_t and <bits/...> headers, so the native core is not directly MSVC-compatible without source changes.

1. Restore Flutter dependencies

flutter pub get

2. Generate the Windows Flutter runner if necessary

The current source snapshot contains the application code but does not include a generated windows/ runner directory.

Generate it with:

flutter create --platforms=windows .

Then restore dependencies again if Flutter modifies project metadata:

flutter pub get

For a maintained Flutter desktop repository, the generated windows/ directory should normally be committed.

3. Build the C++ shared library

Using Ninja and a MinGW-w64 GCC toolchain:

cmake -S signature -B signature/build \
  -G Ninja \
  -DCMAKE_CXX_COMPILER=g++

Build the library:

cmake --build signature/build

The produced DLL is expected to be named:

libpcsign.dll

Copy it to the repository root, replacing the bundled binary if necessary:

cp signature/build/libpcsign.dll .

4. Run the application

Run the Flutter desktop application from the repository root:

flutter run -d windows

The current code opens the native library and data files through relative paths:

./libpcsign.dll
./pcsign/doc.txt
./pcsign/...

Running from the repository root keeps those paths consistent during development.

Current scope

This project demonstrates the mechanics of digital-signature creation and verification and the integration of native C++ code with a Flutter desktop interface.

The implementation is intentionally educational and transparent rather than production-oriented:

  • MD5 is used as the project hash function;
  • RSA signing is implemented as direct modular exponentiation;
  • no standard RSA signature padding or signature container format is used;
  • key and signature data are stored in plain text files;
  • the document path is currently fixed to pcsign/doc.txt;
  • the FFI boundary exposes a small manually managed C interface.

The project is best treated as a cryptography and C++/Flutter FFI demonstration, not as a secure document-signing utility.

About

C++/Flutter desktop app demonstrating RSA digital signatures with a custom MD5 implementation, big-integer arithmetic, and Dart FFI integration.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors