Hash Signing and Signature Validation in C# Using the YubiKey .NET SDK

Matt Heimlich
20 min readJun 22, 2023

--

In the realm of cybersecurity development, software engineers often experience a delicate balance between joy and frustration. Building robust software that strengthens digital defenses is undoubtedly exhilarating; there’s nothing quite like the feeling when we finally solve a tough security problem, but reaching that point can be a slog through limited tooling, poor API documentation, lack of domain expertise, and changing requirements.

One of the key tenets of cybersecurity is “don’t reinvent the wheel”. Trying to be clever by expanding or reimplementing tried and true algorithms for operations such as cryptographic signing or encryption is often a recipe for disaster in the form of weakened security, regardless of your level of training in the field. Established cybersecurity standards and practices have the benefit of not only the involvement of some of the finest minds in their respective fields, but often of rigorous real-world validation, especially in the realm of FIPS-certified tools.

Thankfully, we are spoiled for choice these days with absolutely amazing cybersecurity drop-in libraries. From old staples such as OpenSSL, to higher level offerings such as BouncyCastle, our toolkit has never been more flush with quality solutions to tough security problems. That’s fine in terms of software tooling, but what about hardware solutions?

In the past, high quality hardware security tokens were things reserved only for the enterprise security world. They were expensive to acquire, and the software to support them was difficult to create and maintain, often due to issues like poor (if any) SDK support, unclear standards, and a general lack of C-Suite support for the increased security budgets necessary to develop and roll out such solutions.

The past few years, however, have brought us an explosion of high-quality, low-cost, and well-supported solutions for physical security tokens, as well as a long-overdue recognition of the value of strong security standards from executives. The most widely adopted of these hardware options is the Yubikey from Yubico. Yubikeys come in many flavors, from bare-bones, USB plug-and-forget options, to FIPS certified, and even biometric-enabled solutions for higher end security needs.

In the sake of full disclosure, I should point out that I am in no way associated with Yubico, but I am quite fond of their offerings.

Most YubiKeys as of the time of this writing have several built-in “applications” as functions of the card itself. These include exciting new options like FIDO2 support for passwordless login, and support for more widely implemented options such as OTP and PIV. In addition to these applications, the YubiKey can operate as a general secure key and certificate storage device, from which it is (nearly) impossible to retrieve private keys once stored.

These features have made the YubiKey an indispensable tool for my development team. We use them internally for everything from protecting login to our development machines, to ensuring that our code commits are signed and validated, to using them for login to our various application accounts. So when we had a new security problem to solve with an upcoming software release, my natural first thought was, “Can we streamline this process with a YubiKey?”. The answer turned out to be a “Yes”, but it took some stumbling around their SDK documentation, digging into some buried GitHub issues on the SDK repository, and some good ol’ trial and error.

The Problem

As part of my software company’s offerings to customers, we deliver quarterly content updates that include the most up-to-date security definitions for use within our software tool. Because these definitions are used not only to validate the security posture of our clients’ endpoint infrastructure, but also to automatically fix issues as they’re discovered, it is of utmost importance that these definitions are not tampered with between our certification and customer implementation.

In our experience, tampering is seldom a malicious event, and is much more often done by well-intentioned admins who feel that need to “reinvent the wheel”, be clever, or who simply don’t want to comply with the added overhead of operating within a secure environment. There are, however, malicious parties that we must protect against as well who would like nothing more than to subtly tweak our content to give themselves a security opening to exploit once our content is deployed to the field.

In the spirit of not reinventing the wheel, there is an obvious solution to this issue that both protects our content and prevents customers from accidentally ingesting insecure content in one fell swoop: cryptographic signing.

The Solution

For the uninitiated, cryptographic signing involves taking some arbitrary data and, using some private key that is in the sole, protected control of Party A, creating a signature of that data (or a hash of that data). After signing, that key’s corresponding public key can then be freely distributed to Party B, along with the original data and its signature. Using the public key, Party B can then validate both that the data matches the signature, and that the signature was generated using that public key’s originating private key.

Now, this is hardly rocket science in the realm of cybersecurity, and there are hundreds of tools and libraries out there (if not more) than can help you accomplish this task. What this article will focus on is using the built in signing capabilities of the PIV module on the Yubikey to securely sign arbitrary data from a C# application.

Why bother with the Yubikey? Several reasons: they’re easy to acquire, they’re easy to use, they provide a simple SDK that does what we want, and they lend themselves well to other best security practices for the release of secure data.

Enough background talk, let’s get into the device setup and code:

The Process

To get started, you will need:

  • A YubiKey that supports the PIV module (nearly all do at the time of writing)
  • A development environment configured to write, build, and debug C# code (I’m partial to Rider, but you can use whatever you like)
  • Access to NuGet (If you’re reading this on the internet, you should be good here)
  • The YubiKey Manager software (can be found here)

And that’s it!

Our first step will be to generate or add a certificate suitable for signing to our YubiKey.

Setting Up Your YubiKey

Open the YubiKey Manager application, and we should be greeted by something like this:

The splash screen for YubiKey Manager

If your YubiKey is not currently inserted into a USB slot, you’ll be asked to do so.

Click the ‘Applications’ dropdown at the top of the window, and choose ‘PIV’:

We now find ourselves in the PIV module settings workspace:

The PIV settings workspace

If you haven’t done so, I’d encourage you to set up PIN and PUK settings before continuing, but I won’t be covering that in this article. What we want is the ‘Configure Certificates’ option. Clicking it will take us to a workspace like this:

The PIV Certificate Manager

We want to switch to the ‘Digital Signature’ panel:

Once here, we can either generate a new certificate, or import one if we have already generated a suitable signing certificate outside of the YubiKey Manager. For this article, we will be simply generating one on-device. Click ‘Generate’.

At this point we will be given the option to generate either a Self-signed Certificate, or a Certificate Signing Request. For the sake of this article, we’ll be doing the more simple Self-signed Certificate:

Generate a self-signed certificate

We’ll first be presented a choice for the algorithm we’d like to use for our certificate/key generation. The default (and option we’ll be using here) is ECCP256. This means that we’ll be using Elliptic Curve Cryptography based on the NIST P256 curve for our cryptographic operations using this certificate. What that means on a lower level is beyond the scope of this article, but there is no shortage of information about any of these topics on the internet for those interested in learning more:

Choose your algorithm

Next we’ll set the Subject for your certificate. This can be pretty much anything you’d like, but I’d recommend setting it to something meaningful. In this case, I’ll simply be using my name:

Set the cert’s Subject

Next, we’ll pick an expiration date for the certificate. This is really going to depend on the individual needs of your application. If you’re signing data that will be meaningless in a week, you can pick a short expiration period. If you’re signing data that is used in a rarely updated software, you’ll likely want to choose a length with enough buffer to get folks updated when necessary. The default expiration date is one year from generation:

Set your cert’s expiration date

Finally, we’ll confirm all of the certificate information, and click ‘Generate’:

If all looks good, Generate!

We’ll be asked to put in both our YubiKey’s Management Key:

And our PIV PIN:

Once we’ve entered your credentials, our certificate will be generated, and we’ll see that the Digital Signature slot for PIV (Slot 9C) is now populated with our new certificate:

Success

We’re finally all set to start coding!

The Code

In your dev environment of choice, go ahead and create a new solution containing a Console Application. I’m going to call mine ‘YubiKeySignAndValidateExample’:

The first thing we’ll want to do on creation of our new project is to add a reference to the YubiKey SDK from NuGet. You can either do this with the command line from the root directory of your project with the command:

dotnet add package Yubico.Yubikey

Or you can use the built in NuGet package manager in your IDE if it’s available:

Once installed, we have access to the YubiKey .NET SDK in our project.

Our first order of business is to create a helper class that will allow us to interact with our YubiKey. Let’s create a static class called YubiKeyHelper:

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Yubico.YubiKey;
using Yubico.YubiKey.Piv;

namespace YubiKeySignAndValidateExample;

public static class YubiKeyHelper
{

}

Within this class, we need a method that can enumerate all of the YubiKeys currently connected to our system, and return one suitable for use in data signing. Thankfully, the YubiKey .NET SDK has a static method that takes care of most of the hard work for us:

YubiKeyDevice.FindAll()

We can wrap this method in a LINQ query that can return a suitable key for us. In this case, let’s create a static method that finds the first connected YubiKey with a PIV module that supports ECCP256, if any:

private static IYubiKeyDevice? GetAppropriateYubiKey()
{
return YubiKeyDevice.FindAll().FirstOrDefault(p_device => p_device.HasFeature(YubiKeyFeature.PivEccP256));
}

Now that we’re able to discover our YubiKey, let’s do something with it.

In order to work with our YubiKey programmatically, we need to start what is called a module Session. A Session creates a connection to the YubiKey itself and allows us to access the various functions of a given module from our code. In our case, we know that we want to use the PIV module in order to sign some data. We can create a small static method that creates one of these Sessions for us:

private static PivSession GetPivSession()
{
Console.Clear();

Console.Write("Initiating PIV Session, please wait...");

var yubiKey = GetAppropriateYubiKey();

if (yubiKey is null)
throw new CryptographicException("An appropriate YubiKey could not be found on this system.");

return new PivSession(yubiKey);
}

NOTE: YubiKey Session objects should be properly disposed of during the lifetime of your application once you are done with them. Either wrap the use of your PivSession object in a using block, or manually dispose of it when appropriate.

Notice that we write out a message telling the user that we’re initiating a PIV session. Initiation of a PIV session for the first time each time our application runs can take a few seconds. This is simply a quality of life message for our users.

Let’s take a moment to discuss one of the security advantages of using the YubiKey SDK: automatic handling of many things that would otherwise be time consuming to implement yourself. For example, within a PivSession, we have access to various utilities, include the Sign() method. When we generated our certificate on the YubiKey, the certificate was automatically marked with a flag indicating its PIN Policy. This flag indicates that whenever we attempt to perform a sensitive operation, we should be prompted for our configured PIV PIN before we are allowed access to the private key functionality of the certificate. It’s entirely possible to handle all of this logic by yourself within the SDK, but it is out of the scope of this article. Luckily, the built in methods of the PivSession take care of a lot of the minutiae of this process, including retries, lockout, and secure marshalling of data to and from the YubiKey. In return for this goodness, all we have to do is provide the PivSession with a callback/delegate method that will be called when the PIN needs to be requested from the user of our application in the form of what they call a KeyCollectorDelegate. This is a method that we create with a

Func<KeyEntryData,bool>?

method signature in which we gather some data from the user (usually the PIV PIN), and return a boolean value indicating whether or not the user canceled the operation. The delegate caller provides our method with a KeyEntryData object that contains some information about the request that has been made. In our case, it is only required that we handle the VerifyPivPin request, and optionally the Release request if we have sensitive data to clean up once the YubiKey is done with it.

How we retrieve the PIN from the user is left entirely up to us as developers using the SDK. Our solution could be as simple as reading user input from the console, or as complex as a full-fledged GUI window with field validation. It’s up to us how we implement this step. In our case, we’ll be using the route of least resistance and will simply ask the user via the console for their PIN.

All of this necessitates a new class for us, the KeyCollector class. In its entirety, it should look something like this:

using System.Text;
using Yubico.YubiKey;

namespace YubiKeySignAndValidateExample;

public class KeyCollector
{
public bool KeyCollectorDelegate(KeyEntryData? p_keyEntryData)
{
if (p_keyEntryData is null) return false;

switch (p_keyEntryData.Request)
{
case KeyEntryRequest.Release:
// Do something here if there is sensitive data to clean up.
break;
case KeyEntryRequest.VerifyPivPin:

var result = GetPinFromUser(p_keyEntryData.IsRetry, p_keyEntryData.RetriesRemaining ?? 0);

if (result.UserCanceled || result.PinBytes is null)
{
return false;
}

p_keyEntryData.SubmitValue(result.PinBytes);

return true;
}

return false;
}

private (byte[]? PinBytes, bool UserCanceled) GetPinFromUser(bool p_isRetry, int p_retriesRemaining)
{
Console.Clear();

if (p_isRetry)
{
Console.WriteLine("Invalid PIN. Please try again.");
Console.WriteLine($"{p_retriesRemaining} retries remaining before PIN is locked.");
}

Console.Write("Please input your PIV PIN (C to cancel): ");

var pinEntry = Console.ReadLine();

if ( pinEntry is null )
{
return (null, false);
}

if ( pinEntry.Equals("c", StringComparison.InvariantCultureIgnoreCase) )
{
return (null, true);
}

return (Encoding.Default.GetBytes(pinEntry), false);
}
}

Let’s break down what’s going on here. As mentioned before, this class basically has two jobs in our case:

  • Ask the user for a PIN
  • Submit it to the YubiKey for validation

The first goal is achieved by our method, GetPinFromUser. This method accepts a boolean value and an integer value as arguments. The boolean value tells us whether or not this is a retry attempt. The integer value tells us how many retries we have left before our PIN is locked on the YubiKey. We use these values to control whether or not we print the retry information to the console for the user.

You may also notice that this method returns a tuple of a byte array and a boolean. This is purely a laziness decision. We could just as easily create a new PinResponse class or struct to store these values (and in production we would likely want to opt for such a solution).

The second goal is achieved by this line:

p_keyEntryData.SubmitValue(result.PinBytes);

Note that this is where we are actually submitting the PIN entered by the user to the YubiKey, and that the boolean returned by this delegate method is not indicative of the success of the PIN; it only indicates to the SDK whether or not the user completed the PIN entry process in order for the YubiKey to decide whether or not to increment the retry count.

You may also note some slight quality of life additions to the necessary code (“C to cancel”), but these are entirely unnecessary functionally.

We should take a close look at the switch statement within the delegate method:

switch (p_keyEntryData.Request)
{
case KeyEntryRequest.Release:
// Do something here if there is sensitive data to clean up.
break;
case KeyEntryRequest.VerifyPivPin:

var result = GetPinFromUser(p_keyEntryData.IsRetry, p_keyEntryData.RetriesRemaining ?? 0);

if (result.UserCanceled || result.PinBytes is null)
{
return false;
}

p_keyEntryData.SubmitValue(result.PinBytes);

return true;
}

Here, we are explicitly handling the Release and VerifyPivPin request types from the KeyDataEntry that is passed into our method by the SDK. There are many more request types that the SDK can hand us depending on the operation we’ve requested, but these are the only two we need to worry about in our case.

Now that we have our KeyCollector class established, we’re ready to hook it up to a PivSession and actually sign some data. Let’s make a new method in our YubiKeyHelper class to do just that:

public static string GetEncodedDataSignature(byte[] p_dataHash)
{
using var pivSession = GetPivSession();

var keyCollector = new KeyCollector();

pivSession.KeyCollector = keyCollector.KeyCollectorDelegate;

var signatureBytes = pivSession.Sign(PivSlot.Signing, p_dataHash);

return Convert.ToBase64String(signatureBytes);
}

This is a method that takes in the hash of some data and returns a string in base64 format representing the cryptographic signature of that hash. We can see that we have assigned our KeyCollectorDelegate method that we created in our KeyCollector class to the PIV session’s key collector delegate.

When

pivSession.Sign(PivSlot.Signing, p_dataHash)

is called, the Sign method will automatically use our KeyCollectorDelegate method to collect the user’s key, no additional work is needed on our end.

Also worth noting here is the use of the PivSlot.Signing enumeration. This is just telling the method that we want to use the certificate that we generated earlier in slot 9C on our YubiKey for the signing process. The enumeration is simply a user-friendly alias provided by the SDK to make our lives easier.

The keen-eyed among you may notice at this point that this method (and, indeed, the Sign method in the YubiKey SDK) expects the hash of some data, not the data itself. What does this mean?

Without getting too much into the technical weeds, the Sign method is expecting a “digest” of the data being hashed rather than the entirety of the data itself. This means that, since we chose a 256-bit algorithm when generating our certificate, the signing algorithm is expecting a 32-byte value to sign (32 * 8 bits = 256 bits). Luckily, there is a very quick and easy way for us to get this 32-byte “digest” from any arbitrary data, and that is via the Secure Hash Algorithm, or SHA.

SHA (specifically, SHA256 in our case) will ingest any arbitrary byte data and spit out some number of bytes (32, in the case of SHA256) representing a unique (well, very, very close to unique) “fingerprint” of that data.

You may wonder, why not just use this SHA256 hash to identify our data and be done with it? Well, while having a file hash publicly available for comparison allows us to validate that data hasn't been tampered with, we cannot also verify the source with just a hash. In addition, hashing the same content multiple times will always result in the same hash value, which has its own laundry list of security concerns outside of today’s scope.

With that knowledge in mind, let’s wrap the calculation of this hash value into our final Signing method, and let’s do it in a new static class called Signing:

using System.Security.Cryptography;

namespace YubiKeySignAndValidateExample;

public static class Signing
{
public static void PerformSigning(byte[] p_data)
{
var dataHash = SHA256.HashData(p_data);

var signature = YubiKeyHelper.GetEncodedDataSignature(dataHash);

Console.Clear();

Console.WriteLine("Data signed successfully.");
Console.WriteLine($"Encoded Signature: {signature}");
}
}

Here, we are calling the built-in SHA256 hashing method from Microsoft’s crypto library, but you can use any SHA256 implementation that properly outputs a 32-byte digest as long as you also have access to it wherever you’ll be attempting to validate your data later on down the line.

So, let’s go ahead and sign some data!

Back in our main method in Program.cs, let’s make sure we have the following:

using System.Text;
using YubiKeySignAndValidateExample;

const string data = "Hello, World!";

var dataBytes = Encoding.Default.GetBytes(data);

Signing.PerformSigning(dataBytes);

Console.WriteLine("Press any key to exit...");

Console.ReadKey(true);

return 0;

Go ahead and run, and if everything goes as expected, you should be prompted for your PIN. After providing it, you should see something like this:

Note that running the program multiple times will result in different signatures, despite the fact that we are always feeding it the same input (“Hello, World!”). This is intended and is a desired function of good cryptographic signing.

Go ahead and copy that signature string somewhere for later use.

We now have half of the problem solved: we can input some arbitrary data into our method, get its 32-byte hash, and create a unique signature of that hash. But what about the other side of the equation? How can we (or our users) later validate that data we’ve provided to them is both untouched and from us?

That brings us to validation, which thankfully is a much easier task with much less set up. It is also the part of this process that you likely ended up reading this article to figure out, because there is very little information in the SDK documentation about how to perform this step.

If you poked around with the PivSession class to any extent, you likely noticed that while it includes a signing method, it does not include any corresponding verification method. This may feel strange to those of you who have worked with other cryptographic libraries in the past. It certainly did to me. Eventually I was able to dig up a long-closed GitHub issue from the SDK’s repository where a Yubico developer states the following:

Note that there is no PivSession.Verify method as public key functions can happen anywhere (and doesn't require the secure environment that the YubiKey provides.)

Which I suppose makes sense. The SDK utilities are intended to facilitate the secure interaction with the keys stored in the YubiKey’s secure storage. Signature validation is an “insecure” task that can be performed anywhere the public key exists. What I wish they provided some more guidance on is the matter of retrieving said public key and putting it to use. But I suppose that’s where this article comes in.

As we know, we need to be able to retrieve and distribute the public key in order for any of this to be of use to us. Let’s create a new static method in our YubiKeyHelper class that can get this for us:

public static void RetrieveEncodedPublicKey()
{
using var pivSession = GetPivSession();

var publicKey = pivSession.GetCertificate(PivSlot.Signing).GetECDsaPublicKey();

if (publicKey is null)
{
Console.WriteLine("Public key could not be retrieved.");
return;
}

var publicKeyPem = publicKey.ExportSubjectPublicKeyInfoPem();

var pemBytes = Encoding.Default.GetBytes(publicKeyPem);

Console.Clear();

Console.WriteLine($"Encoded Public Key: {Convert.ToBase64String(pemBytes)}");
}

In this method, we create another PivSession in order to retrieve the ECDSA-formatted public key from our certificate. There are various ways to do this, but I think you’ll find that the above method is the most simple. Once we have retrieved the public key from our YubiKey, we export it to the PEM format.

Again, without bogging down this article with too much technical information, just be aware that when exported, our key is in something called DER format. The YubiKey SDK docs do a better job of explaining the intricacies of this issue better than I ever could, for those interested in such things.

We then convert our PEM-formatted key into a base64 format (Why? Personal preference) and print it to the command line. Let’s change our main program slightly to call this method:

// See https://aka.ms/new-console-template for more information

using System.Text;
using YubiKeySignAndValidateExample;

const string data = "Hello, World!";

var dataBytes = Encoding.Default.GetBytes(data);

YubiKeyHelper.RetrieveEncodedPublicKey();

Console.WriteLine("Press any key to exit...");

Console.ReadKey(true);

return 0;

When run, you’ll note that even though we’re using a PivSession to retrieve the data, because getting the public key is not a secure operation, we’re never queried for our PIN.

You should get an output like this:

Again, go ahead and copy that key somewhere for later use, being sure to get rid of any newline that may have been copied over due to the width constraints on your console window. When we paste it back later, we need to ensure we are pasting it back as one whole string of characters.

So, now we’ve got our signature, we’ve got our public key, we’re now ready to do some validation. Let’s create a validation class like the following:

using System.Security.Cryptography;
using System.Text;

namespace YubiKeySignAndValidateExample;

public static class Validation
{
public static void PerformValidation(byte[] p_data)
{
var dataHash = SHA256.HashData(p_data);

var encodedPublicKey = GetPublicKeyFromUser();

if (string.IsNullOrWhiteSpace(encodedPublicKey)) return;

var signatureString = GetSignatureFromUser();

if (string.IsNullOrWhiteSpace(signatureString)) return;

var decodedPublicKey = Convert.FromBase64String(encodedPublicKey);

var recoveredPublicKey = Encoding.Default.GetString(decodedPublicKey);

var ecdsa = ECDsa.Create();

ecdsa.ImportFromPem(recoveredPublicKey);

var signature = Convert.FromBase64String(signatureString);

var result = ecdsa.VerifyHash(dataHash, signature, DSASignatureFormat.Rfc3279DerSequence);

Console.Clear();

Console.WriteLine(result ? "Signature is valid." : "Signature is invalid.");
}

private static string? GetSignatureFromUser()
{
Console.Clear();

var signatureString = string.Empty;

while (string.IsNullOrWhiteSpace(signatureString))
{
Console.Write("Please input the signature to validate (Q to exit): ");

signatureString = Console.ReadLine();

if (!string.IsNullOrWhiteSpace(signatureString) &&
signatureString.Equals("Q", StringComparison.InvariantCultureIgnoreCase))
{
Console.WriteLine("Exiting.");
return null;
}
}

return signatureString;
}

private static string? GetPublicKeyFromUser()
{
Console.Clear();

var encodedPublicKey = string.Empty;

while (string.IsNullOrWhiteSpace(encodedPublicKey))
{
Console.Write("Please input the encoded public key (Q to exit): ");

encodedPublicKey = Console.ReadLine();

if (!string.IsNullOrWhiteSpace(encodedPublicKey) &&
encodedPublicKey.Equals("Q", StringComparison.InvariantCultureIgnoreCase))
{
Console.WriteLine("Exiting.");
return null;
}
}

return encodedPublicKey;
}
}

You can more or less ignore GetSignatureFromUser() and GetPublicKeyFromUser(), as they’re basically just quality of life fluff, you can implement those steps however you’d like.

Note that, just like when signing, we are calculating the SHA256 hash of the provided data for use in our validation step. After that, we’re basically just reversing the process we went through to encode both the public key and the cryptographic signature, then using that decoded public key to spawn a new ECDSA handler. With that created, we simply use the built-in VerifyHash() method of the ECDSA library and feed it not only our data hash and our signature, but it is also very important that we manually specify the DSASignatureFormat as being DER encoded. I stumbled around so close to this solution for many hours before finally making that connection.

We’re ready to test our validation component now. Let’s make one more small change to the main program to test it before we pretty up our program:

using System.Text;
using YubiKeySignAndValidateExample;

const string data = "Hello, World!";

var dataBytes = Encoding.Default.GetBytes(data);

Validation.PerformValidation(dataBytes);

Console.WriteLine("Press any key to exit...");

Console.ReadKey(true);

return 0;

When run, after providing both your copied public key and your copied signature (again, make sure you’re pasting in the whole public key), you should see the following:

Congratulations

And with that, you’ve got an end-to-end solution for securely signing data using your YubiKey. What might you use this for? The most common case I can think of is file validation, especially for those who distribute or generate sensitive files for use within their own software (think updates, save files, logs, etc.). Using this method, you could sign some file, embed the signature within the file, embed your public key within your application, and then when a customer loads that file into your application, you can retrieve and strip out the signature, then validate the integrity and source of the rest of the file.

Let’s do a final bit of cleanup on our main program to make it easier to play around with:

using System.Text;
using YubiKeySignAndValidateExample;

const string data = "Hello, World!";

var dataBytes = Encoding.Default.GetBytes(data);

Console.Write("Would you like to (S)ign, (V)alidate, Get (P)ublic Key, or (Q)uit?: ");

var response = Console.ReadLine()?.ToUpper().First();

switch (response)
{
case 'S':
try
{
Signing.PerformSigning(dataBytes);
break;
}
catch ( OperationCanceledException )
{
Console.WriteLine("User canceled PIN entry. Exiting.");
return 1;
}
case 'V':
try
{
Validation.PerformValidation(dataBytes);
break;
}
catch ( ArgumentException )
{
Console.WriteLine("There was an error with either the public key or the signature. Exiting.");
return 1;
}
case 'P':
YubiKeyHelper.RetrieveEncodedPublicKey();
break;
case 'Q':
Console.WriteLine("Exiting.");
break;
default:
return 1;
}

Console.WriteLine("Press any key to exit...");

Console.ReadKey(true);

return 0;

And with that, we’re done!

I hope this article has taught you how to effectively get started with the YubiKey .NET SDK, and given you a basic understanding of the requirements and uses of cryptographic signing in the real world.

Thanks for reading!

--

--

Matt Heimlich

I am the Director of Software Engineering for a cybersecurity software company in Northern Virginia