No Legacy Crypto: Meet NSec.Cryptography

Prof Bill Buchanan OBE FRSE
5 min readMay 12, 2024

If you use OpenSSL, you are likely to find it blotted and full of legacy methods. Every single legacy method adds vulnerabilities to the code, even though few people are likely to use these methods. So how do we create lightweight cryptography code, and which does not bring in the baggage of the past? Well, one library is Nsec.cryptography and which runs on .NET. It uses Libsodium code platform and which has secure methods for handling keys (and which can be securely erased). Overall it contains the best in practice methods [here]:

The key derivation functions include HKDF and, for passwords, we have Argon2id and scrypt. For symmetric key, we have with either 256-bit AES GCM and ChaCha20. With hashing, we only see SHA-256, SHA-512 and Blake2b. We see here that we do not have secp256k1 and NIST P256 curves for key exchange and digital signatures, but use Curve 25519 to implement X25519 and Ed25519. So let’s implement Ed25519 and X25519 with NSec.cryptography.

Ed25519 with NSec.Cryptography

Ed25519 — Edwards-curve Digital Signature Algorithm (EdDSA) — uses Curve 25519 and SHA-512 to produce an EdDSA signature. In this case, we will perform the core operations in the signing and verification. With this, we calculate the hash of the message (h) and have a public key (pk) and a private key (sk). We then generate a signature of (R,s). The public key (pk) and (R,s) is then used to check the signature. It is standardised as RFC [RFC 8032] and is based on the Schnorr’s signature scheme and uses (possibly twisted) Edwards curves.

For Ed25519 we use 32 byte values for both the private key and the public key, and 64 bytes for the signature. Overall, SHA512 is used as the hashing method for the signature. in this case we will use the NSec.Cryptography C# library. With Ed25519, we can either use ed25519 and ed25519ph, and where ed25519ph signs the hash of the message.

First we create a folder named “nsec_ed25519”, and then go into that folder.We can create a Dotnet console project for .NET 8.0 with:

dotnet new console --framework net8.0

Next we can install the NSec.cryptography library with:

dotnet add package NSec.Cryptography --version 24.4.0

Next some code [here]:

namespace Ed25519
{
using System;
using NSec.Cryptography;
using System.Text;

class Program
{
static void Main(string[] args)
{
var msg="Hello";
if (args.Length >0) msg=args[0];
try {


var algorithm = SignatureAlgorithm.Ed25519;
//var algorithm = SignatureAlgorithm.Ed25519ph;

var creationParameters = new KeyCreationParameters;
creationParameters.ExportPolicy = KeyExportPolicies.AllowPlaintextArchiving;

var key = new Key(algorithm, creationParameters);

var data = Encoding.UTF8.GetBytes(msg);

var signature = algorithm.Sign(key, data);
var privblob = key.Export(KeyBlobFormat.NSecPrivateKey);
var pubBlob = key.Export(KeyBlobFormat.RawPublicKey);

Console.WriteLine("Private key: {0}",Convert.ToHexString(privblob));
Console.WriteLine("Public Key: {0}",Convert.ToHexString(pubBlob));
Console.WriteLine("Signature: {0}",Convert.ToHexString(signature));

if (algorithm.Verify(key.PublicKey, data, signature)) Console.WriteLine("Signature verified");


} catch (Exception e) {
Console.WriteLine("Error: {0}",e.Message);
}
}
}

}

A sample run gives [here]:

Message: Hello 1234
Private key: DE6442DE20004000EC3D031A7AF054CA923D2B599C2EEA3037467E65BCAD2D9160865A794F3E7AEF
Public Key: DCC3AED69E942BA7BDD53EFD9C63A411EC49EC47407D907D41BD07AFD4FF2960
Signature: 83DF92C1868857F8BDC97F700834B6155C9593A437095626432C00FAFD0F6A87E0BDD57514CE00FD9E78C199997A3D3124EDCA1A06E5A04E2492E40E0F588F09
Signature verified

If we try again [here]:

Message: Hello 1234
Private key: DE6442DE20004000B40939B62A4802CB720A1D2D7CFA7A762072DCC20BDCC66DCE289E712131B3DA
Public Key: 99E71B92362CDCFE83276D76B776E15409C8C2B2040A021DE9F5DCC97EE9DE47
Signature: 62EE2FE2A83CA941B302116DB36D7B30C8175C4C0B56C1AF8CACC52FB9A5526AF202A4B33C144EB3F8F656CD242EA877B88DFD798C8C24DDF92DD4A282E06508
Signature verified

We see the private key has 256 bits (64 hex characters):

99E71B92362CDCFE83276D76B776E15409C8C2B2040A021DE9F5DCC97EE9DE47

The public key has a shared element:

DE6442DE20004000 EC3D031A7AF054CA923D2B599C2EEA3037467E65BCAD2D9160865A794F3E7AEF
DE6442DE20004000 B40939B62A4802CB720A1D2D7CFA7A762072DCC20BDCC66DCE289E712131B3DA

We see that the public key also has a 256-bit key. We then have a 512-bit signature value, such as:

62EE2FE2A83CA941B302116DB36D7B30C8175C4C0B56C1AF8CACC52FB9A5526AF202A4B33C144EB3F8F656CD242EA877B88DFD798C8C24DDF92DD4A282E06508

With Ed25519, we can either use ed25519 and ed25519ph, and where ed25519ph signs the hash of the message.

X25519 with NSec.Cryptography

In a secure exchange, Bob and Alice must generate the same shared symmetric key. This is often achieved by using X25519, and uses ECDH (Elliptic Curve Diffie Hellman). With this we select a base x co-ordinate point of G, and then Bob and Alice generate random values, and determine their public keys. Alice generates a, and Bob generates b. Alice’s public key will be:

A=aG

and Bob’s public key becomes:

B=bG

The exchange their values, and Alice multiplies by the value received from Bob by a, and Bob multiplies the value he receives from Alice by b. They should then end up with the same value, and which should be:

K=abG

First we create a folder named “nsec_x25519”, and then go into that folder.We can create a Dotnet console project for .NET 8.0 with:

dotnet new console --framework net8.0

Next we can install the NSec.cryptography library with [here]:

dotnet add package NSec.Cryptography --version 24.4.0

Next some code [here]:

namespace Ed25519
{
using System;
using NSec.Cryptography;
using System.Text;

class Program
{

// dotnet add package NSec.Cryptography --version 24.4.0


static void Main(string[] args)
{

try {


var algorithm = KeyAgreementAlgorithm.X25519;
var creationParameters = new KeyCreationParameters();
creationParameters.ExportPolicy = KeyExportPolicies.AllowPlaintextArchiving;
var aliceKey = new Key(algorithm, creationParameters);
var bobKey = new Key(algorithm, creationParameters);

var aliceShared = algorithm.Agree(aliceKey,bobKey.PublicKey);
var bobShared = algorithm.Agree(bobKey,aliceKey.PublicKey);
var derivedkey1 = KeyDerivationAlgorithm.HkdfSha512.DeriveKey(aliceShared, ReadOnlySpan.Empty, ReadOnlySpan.Empty, AeadAlgorithm.ChaCha20Poly1305, new KeyCreationParameters {ExportPolicy = KeyExportPolicies.AllowPlaintextArchiving});
var derivedkey2 = KeyDerivationAlgorithm.HkdfSha512.DeriveKey(bobShared, ReadOnlySpan.Empty, ReadOnlySpan.Empty, AeadAlgorithm.ChaCha20Poly1305, new KeyCreationParameters {ExportPolicy = KeyExportPolicies.AllowPlaintextArchiving});

var privAlice = aliceKey.Export(KeyBlobFormat.NSecPrivateKey);
var pubAlice = aliceKey.Export(KeyBlobFormat.RawPublicKey);
var privBob = bobKey.Export(KeyBlobFormat.NSecPrivateKey);
var pubBob = bobKey.Export(KeyBlobFormat.RawPublicKey);

Console.WriteLine("Alice Private key:\t{0}",Convert.ToHexString(privAlice));
Console.WriteLine("Alice Public Key:\t{0}",Convert.ToHexString(pubAlice));
Console.WriteLine("Bob Private key:\t{0}",Convert.ToHexString(privBob));
Console.WriteLine("Bob Public Key:\t\t{0}",Convert.ToHexString(pubBob));

Console.WriteLine("\nKey (Alice):\t{0}",Convert.ToHexString(derivedkey1.Export(KeyBlobFormat.NSecSymmetricKey)));
Console.WriteLine("Key (Bob):\t{0}",Convert.ToHexString(derivedkey2.Export(KeyBlobFormat.NSecSymmetricKey)));


} catch (Exception e) {
Console.WriteLine("Error: {0}",e.Message);
}
}
}

}

A sample run is [here]:

Alice Private key:	DE6641DE20002000102086AE5D210B64C6333B34B1788CC05995F3DEC379AD13605551DF0748C6E7
Alice Public Key: 0A53B650E705AAFE7065858A4BD7C53AD15C8737490C54AE94092A204C6A3D3C
Bob Private key: DE6641DE200020002B0509673675B03AC0719E003DEC01AD145198EE2CC7E666D302D86889D7B4ED
Bob Public Key: E8D4CDC8C84BBB87F401D47E13B35F4F78C6CC6F43C7AA8D52C368FAF176203F

Key (Alice): DE6143DE200010000B2CFCBA5B626CA7F73CB195FA932E73C3848AA4092CDDDD469EE7E308E9F1FE
Key (Bob): DE6143DE200010000B2CFCBA5B626CA7F73CB195FA932E73C3848AA4092CDDDD469EE7E308E9F1FE

The first part of the key is the identifier: “DE6143DE20001000” and “0B2CFCBA5B626CA7F73CB195FA932E73C3848AA4092CDDDD469EE7E308E9F1FE” is the actual key. This is a 256-bit key.

Alice’s private key is “102086AE5D210B64C6333B34B1788CC05995F3DEC379AD13605551DF0748C6E7” and has an identifier of “DE6641DE20002000”. Bob’s private key is “2B0509673675B03AC0719E003DEC01AD145198EE2CC7E666D302D86889D7B4ED”.

Conclusions

.NET has many advantages from a security point-of-view. Here are some NSec.cryptography examples:

https://asecuritysite.com/nsec/

--

--

Prof Bill Buchanan OBE FRSE

Professor of Cryptography. Serial innovator. Believer in fairness, justice & freedom. Based in Edinburgh. Old World Breaker. New World Creator. Building trust.