ft_ssl is a cross-platform .NET 9 command line tool that reimplements RFC-compliant MD5 and SHA-256 hashing with a focus on stream processing, low allocations, and parity with the original Unix-style interface.
- Pure C# implementations of MD5 (RFC 1321) and SHA-256 (FIPS 180-4)
- CLI-compatible options: stdin echo (
-p
), quiet (-q
), reverse output (-r
), and literal strings (-s
) - Stream-based hashing that handles arbitrarily large inputs without loading them fully into memory
- Zero-allocation fast path for MD5 to enable meaningful micro-benchmarks
- Extensive regression tests built from official test vectors and padding edge cases
Path | Notes |
---|---|
src/ft_ssl |
Console entry point, option parsing, and hash implementations |
tests/ft_ssl.Tests |
xUnit test suite covering RFC and edge-case vectors |
benchmarks/ft_ssl.Benchmarks |
BenchmarkDotNet harness for throughput measurements |
docs/performance |
Stored benchmark reports (latest SHA-256 run) |
ft_ssl.sln |
Convenience solution file for working in IDEs |
- .NET SDK 9.0 or newer (https://dotnet.microsoft.com/download)
dotnet restore
dotnet build ft_ssl.sln
The executable expects the OpenSSL-inspired syntax:
ft_ssl command [command_opts] [command_args]
Invoke it directly from the project root via the SDK:
dotnet run --project src/ft_ssl -- md5 -s "hello"
dotnet run --project src/ft_ssl -- sha256 path/to/file.txt
You can also publish a standalone executable if you prefer to ship a self-contained binary:
dotnet publish src/ft_ssl -c Release -r osx-arm64 --self-contained false
./src/ft_ssl/bin/Release/net9.0/osx-arm64/publish/ft_ssl sha256 file.bin
Option | Description |
---|---|
md5 , sha256 |
Selects the hashing algorithm to run |
-p |
Read from stdin, echo the exact input to stdout, then print the digest |
-q |
Quiet mode: only the lowercase hex digest is emitted |
-r |
Reverse mode: print digest followed by the subject ("string" or file path) |
-s <string> |
Hash the literal string immediately following the flag |
<file> |
Any non-flag argument is treated as a file path and hashed in order |
Behavioral notes:
- With no
-s
,-p
, or file arguments, the program reads a single message from stdin and prints(stdin)= <digest>
. - Multiple
-s
occurrences are allowed; they are processed in the order they appear on the command line before file inputs. - Missing files and permission errors are reported with the same wording as the original OpenSSL tool.
# Echo stdin and hash it twice in one go
printf 'BlaiseSegal\n' | dotnet run --project src/ft_ssl -- md5 -p -p
# Hash two literal strings and a file in a single invocation
dotnet run --project src/ft_ssl -- sha256 -s "abc" -s "" ./tests/testdata.bin
# Quiet checksum for scripting
ft_ssl md5 -q -s "so we beat on boats against the current borne back ceaselessly into the past"
Program
dispatches to the requested algorithm and orchestrates option parsing.CommandOptions.Parse(ReadOnlySpan<string>)
interprets flags without allocations and records pending strings/files.- Both algorithms implement
IHashAlgorithm
, exposingComputeHash(Stream)
andComputeHash(byte[])
so the CLI can operate over spans or streams. - Output formatting copies the conventions of the reference ft_ssl/openssl commands (quiet, reverse, stdin labels).
- Maintains a four-word state and a 512-bit working buffer, mirroring RFC 1321 step-by-step.
- Accepts streaming updates, accumulating bytes until it can process a full 64-byte block.
- Provides a
TryComputeHash(ReadOnlySpan<byte>, Span<byte>)
helper to compute digests without heap allocations, used by the benchmarks.
- Implements the FIPS 180-4 schedule with a 16-word ring buffer to avoid repeatedly allocating the 64-word message schedule.
- Uses
MemoryMarshal.Cast
andBinaryPrimitives.ReverseEndianness
to convert input to big-endian words efficiently. - Applies
Unsafe.Add
to minimize bounds checks during the round loop while staying in safe managed code.
- Option parsing keeps track of whether stdin should be read after processing
-s
and file inputs, matching the original project rubric. - Error conditions (unknown flag, missing
-s
value) surface as console warnings, which mirrors how the historical ft_ssl behaves.
Execute the test suite with:
dotnet test
Covered scenarios include:
- RFC 1321 Appendix A.5 MD5 vectors (
"", "a", "abc", ...
) and carefully chosen padding edge cases (55- and 64-byte messages). - FIPS 180-4 SHA-256 examples for empty input,
"abc"
, the multi-block"abcdbc...nopq"
, and long 448-bit-aligned inputs. - Boundary conditions around the padding logic for both algorithms.
Representative vectors verified by the tests:
Input | MD5 digest |
---|---|
"" |
d41d8cd98f00b204e9800998ecf8427e |
"abc" |
900150983cd24fb0d6963f7d28e17f72 |
"1234567890" * 8 |
57edf4a22be3c955ac49da2e2107b67a |
Input | SHA-256 digest |
---|---|
"" |
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 |
"abc" |
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad |
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" |
248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1 |
Refer to tests/ft_ssl.Tests/Md5Tests.cs
and tests/ft_ssl.Tests/Sha256Tests.cs
for the complete catalogue.
BenchmarkDotNet is used to monitor throughput and allocation behavior. Run a subset of the suite with:
dotnet run --project benchmarks/ft_ssl.Benchmarks -c Release -- --filter "*Sha256*"
Latest recorded numbers (see docs/performance/Sha256Benchmarks-report-github.md
):
Benchmark | Mean | StdDev | Allocated | Notes |
---|---|---|---|---|
SHA-256 on a 1 MiB block |
3.679 ms | 0.0043 ms | 56 B | Arm64, .NET 9.0.9, Apple M2 Pro |
The MD5 harness provides both an allocating and a zero-allocation variant to highlight the benefit of TryComputeHash
. If BenchmarkDotNet times out while building, rerun with a longer build timeout, e.g. --buildTimeout 300
.
- Add more digest algorithms (e.g., SHA-512) by implementing
IHashAlgorithm
. - Expand benchmarks with varying input sizes and cross-platform baselines.
- Wrap the CLI with scripting-friendly formats (JSON output, exit codes) if needed.
For further details, browse the source files in src/ft_ssl
or open the solution in an IDE using ft_ssl.sln
.