Simple SHA256 Hashing with NSS in C
Here we present a program that calculates SHA256 of the string "abc"
as a demonstration of how to use Mozilla’s Network Security Services library. We assume the reader has compiled a working library, or installed one through a package manager. The build instruction in this tutorial assume Windows and Visual Studio 2019 with clang
.
My comments are overly verbose, and this should make it easier for beginners to follow along.
Jump down to the program if you’re eager to see the code.
The Readme
This is a very basic NSS program that calculates a SHA256 sum of a fixed vector.
License
The licenese for these files is explicitly WTFPL because it suits a small demonstration perfectly. See wtfpl.net for more info.
Usage
Just run the executable sha256.exe
and you will see
NSS initialisation successful. Hashed into 32 bytes. ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
and know your program is working correctly. You can compare it with the output given at test vectors.
Platform
NSS and NSPR are cross-platform environments, but these instructions assume Windows and the Visual Studio clang compiler. The makefile needs adjustments or a complete rewrite for other systems. Patches welcome.
Build Instructions
The following assumes you’ve built NSS according to instructions here, and the provided Makefile assumes the paths given there.
Edit the Makefile and adjust the following four lines to where you’ve installed the NSPR and NSS development libraries.
NSPRINC=C:\build\nss-3.45\dist\Debug\include NSPRLIB=C:\build\nss-3.45\dist\Debug\lib NSS3PUB=C:\build\nss-3.45\dist\public NSS3PRV=C:\build\nss-3.45\dist\private
Edit the following line if your clang.exe is in a different place.
CC="C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\Llvm\8.0.0\bin\clang.exe"
To build it, open up a x64 Native Tools Command Prompt for VS 2019. Navigate, this means use c:
and cd
as appropriate, to the location you’ve extracted, or typed in, the source code and makefile and run nmake
.
That’s it. Now you have a working NSS program.
Run it by typing sha256
and press return. Assuming everything’s normal, you’ll get the output given above.
The Makefile
A quick introduction to the flags used for the clang
command. First we prefix it with @
so it doesn’t echo the command line when running the makefile.
Then we add -Wall
and -Wextra
to give us all the usual warnings plus a few extra ones. We use -Wno-unknown-pragmas
and -Wno-unused-parameter
to turn off two warnings that aren’t relevant to the current code. The first one is because there’s an unknown pragma in the NSS headers, the second one is because we’re not using the command line arguments in this program.
We add -m64
to make a 64bit executable; and -g
for debug inforamtion, which we want while developing but not necessarily when making a production build.
We add -I$(NSPRINC)
, -I$(NSS3PUB)
, -I$(NSS3PRV)
, and -I$(NSPRINC)\nspr
for the include paths. We include $(NSPRINC)
twice, because this program prefixes the NSPR include files name with the directory, like so
#include <nspr/prio.h>
while NSS uses NSPR directly, like so
#include "plarena.h"
and that means we need the search path for the include files twice, once for the program, and once for NSS.
We add -L$(NSPRLIB)
to tell the linker where to find the .lib
files, and then -llibnspr4 -lnss3.dll
to tell it what libraries to link against.
We explictly name the source code, sha256.c
on the command line, and use -o$@
to name the output file according to the Makefile target.
In addition to all of that, we use makefile magic to copy over the DLL files into the current directory. The program depends on the entire suite of DLL files for convenience.
Virustotal
No engine detects the resulting executable as malicious.
Finally
Have all the fun making NSS applications,
Johann
The Program
Include the NSPR headers.
#include <nspr/prio.h> #include <nspr/prprf.h>
Include the NSS headers.
#include <nss/nss.h> #include <nss/pk11pub.h>
This program calculates the SHA256 of the string “abc” as a demonstration of how to use NSS to calculate a hash.
This code uses PR_fprintf() exclusively for printing messages. The first argument is the struct PrFileDesc * pointer where we want the message to output, and PR_STDOUT and PR_STDERR are macros that give us standard output and standard error pointers, respectively. Next is a format string, followed by arguments according to the format string, just like printf().
Notice that our success messages are printed to standard output, while our error messages are printed to standard error.
As a demonstration of actual production code, error checking is in place for all cryptographic functions. However, we’re not totally pedantic and don’t check the return value of PR_fprintf() which can actually fail if, say, standard output is piped to a file and there’s no space left on the file system. This is worth fixing for an actualy production system.
int main( int argc, char *argv[] ) {
Return value from main(), failure by default. This is arguably better style than it is to succeed by default. When all the steps have been completed successfully, we can set this to zero.
int ret = 1;
Initialise the NSS library, and check for the return value.
SECStatus s = NSS_NoDB_Init("."); if (s != SECSuccess) {
If the NSS libraly fails to initialise, we print a short message and return immediately. The shutdown code below is for when we’ve done at least this step successfully.
PR_fprintf( PR_STDERR, "NSS initialisation failed!\n" ); return ret; } else {
And on success, we print a nice message, but in production code we normally wouldn’t do this.
PR_fprintf( PR_STDOUT, "NSS initialisation successful.\n" ); }
We explicitly create a SHA256 hashing context.
PK11Context *context = PK11_CreateDigestContext( SEC_OID_SHA256 );
If the context creation fails, we print a short error message and jump to the shutdown code below.
if ( !context ) { PR_fprintf( PR_STDERR, "Unable to create digest context.\n" ); goto shutdown; }
We initialise the context before we begin to calculate the hash.
s = PK11_DigestBegin( context );
If the context initialisation fails, we print a short error message and jump to the shutdown code below.
if ( s != SECSuccess ) { PR_fprintf( PR_STDERR, "Error starting the digest context.\n" ); goto shutdown; }
Our test vector for the SHA256 hashing algorithm. Doing it like this, with an explicit array initialiser means there will be no zero byte at the end of the string.
unsigned char vector[] = { 'a', 'b', 'c' };
Calculate the digest of the vector; context
is the context of course, vector
is the buffer we want to hash, and sizeof vector
is the length of the buffer.
s = PK11_DigestOp( context, vector, sizeof vector );
If the hashing operation fails, we print a short error message and jump to the shutdown code below.
if ( s != SECSuccess ) { PR_fprintf( PR_STDERR, "Unable to hash test vector.\n" ); goto shutdown; }
The digest itself is 256 / 8 = 32 bytes long for SHA256. We let the computer calculate it. The digest and length are unsigned to fit the parameters of the PK11_DigestFinal()
function. The first parameter is the context, of course, digest
is the output buffer, the &length
parameter is where the actual length of the digest is stored, while the fourth parameter, sizeof digest
is the length of the digest
buffer.
unsigned char digest[ 256 / 8 ]; unsigned int length = 0; s = PK11_DigestFinal( context, digest, &length, sizeof digest );
If the digest finalisation fails, we print a short error message and jump to the shutdown code below.
if ( s != SECSuccess ) { PR_fprintf( PR_STDERR, "Unable to finalise the hash.\n" ); goto shutdown; }
We print out how many bytes we calculated for the SHA256 hash.
PR_fprintf( PR_STDOUT, "Hashed into %d bytes.\n", length );
Then print the hexadecmial value of the digest. PR_fprintf()
doesn’t have %hhx
for 8bit values, so we fake it with an integer.
Here we use an unsigned i
because length
is unsigned.
for( unsigned int i = 0; i < length; i++ ) { PR_fprintf( PR_STDOUT, "%02x", ( int ) digest[ i ] ); }
And finally print a newline.
PR_fprintf( PR_STDOUT, "\n" );
Now at the end of the program, we allow ourselves to return success to the operating system.
ret = 0; shutdown:
We destroy and free the context; context
is the context, and PR_TRUE
means we’ll also free it. And even though we’re at the end of the function, we still set the pointer to NULL
for safety.
if ( context ) { PK11_DestroyContext( context, PR_TRUE ); context = NULL; }
We shut down the NSS library. This can actually fail, and will do so if there are pending digest contexts that haven’t been destroyed yet.
We set the return value to fail because it could be success by now, and we use a different value from the other error messages.
if ( NSS_Shutdown() != SECSuccess ) { PR_fprintf( PR_STDERR, "NSS Shutdown failed!\n" ); ret = 2; } return ret; }
The Makefile
This Makefile assumes Windows, Visual Studio 2019 and clang
; patches welcome for other environments.
You will need to edit the following lines to suit your own environment.
NSPRINC=C:\build\nss-3.45\dist\Debug\include NSPRLIB=C:\build\nss-3.45\dist\Debug\lib NSS3PUB=C:\build\nss-3.45\dist\public NSS3PRV=C:\build\nss-3.45\dist\private CC="C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\Llvm\8.0.0\bin\clang.exe"
See the README file for an intrudoction to the command line parameters given here.
sha256.exe: sha256.c freebl3.dll libnspr4.dll libplc4.dll libplds4.dll \ nss3.dll nssckbi.dll nssdbm3.dll nssutil3.dll smime3.dll \ softokn3.dll sqlite3.dll ssl3.dll @$(CC) -Wall -Wextra -Wno-unknown-pragmas -Wno-unused-parameter \ -m64 -g \ -I$(NSPRINC) -I$(NSS3PUB) -I$(NSS3PRV) -I$(NSPRINC)\nspr \ -L$(NSPRLIB) sha256.c -llibnspr4 -lnss3.dll -o$@
We use the following rules to copy over the entire NSPR and NSS DLL files into the current directory. This means we don’t need it in our PATH
.
freebl3.dll: $(NSPRLIB)\$@ @copy $(NSPRLIB)\$@ $@ libnspr4.dll: $(NSPRLIB)\$@ @copy $(NSPRLIB)\$@ $@ libplc4.dll: $(NSPRLIB)\$@ @copy $(NSPRLIB)\$@ $@ libplds4.dll: $(NSPRLIB)\$@ @copy $(NSPRLIB)\$@ $@ nss3.dll: $(NSPRLIB)\$@ @copy $(NSPRLIB)\$@ $@ nssckbi.dll: $(NSPRLIB)\$@ @copy $(NSPRLIB)\$@ $@ nssdbm3.dll: $(NSPRLIB)\$@ @copy $(NSPRLIB)\$@ $@ nssutil3.dll: $(NSPRLIB)\$@ @copy $(NSPRLIB)\$@ $@ smime3.dll: $(NSPRLIB)\$@ @copy $(NSPRLIB)\$@ $@ softokn3.dll: $(NSPRLIB)\$@ @copy $(NSPRLIB)\$@ $@ sqlite3.dll: $(NSPRLIB)\$@ @copy $(NSPRLIB)\$@ $@ ssl3.dll: $(NSPRLIB)\$@ @copy $(NSPRLIB)\$@ $@
A simple rule to clean up temporary files.
clean: del *.exe *.dll *.ilk *.pdb *~
Downloads
Contact
The author can be contacted at johann@myrkraverk.com.