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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: