Tripledes from hardware - c#

using System;
using System.Security.Cryptography;
namespace SmartCardSign
{
class SCSign
{
static void Main(string[] args)
{
// To idendify the Smart Card CryptoGraphic Providers on your
// computer, use the Microsoft Registry Editor (Regedit.exe).
// The available Smart Card CryptoGraphic Providers are listed
// in HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Defaults\Provider.
// Create a new CspParameters object that identifies a
// Smart Card CryptoGraphic Provider.
// The 1st parameter comes from HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Defaults\Provider Types.
// The 2nd parameter comes from HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Defaults\Provider.
CspParameters csp = new CspParameters(1, "Schlumberger Cryptographic Service Provider");
csp.Flags = CspProviderFlags.UseDefaultKeyContainer;
// Initialize an RSACryptoServiceProvider object using
// the CspParameters object.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(csp);
// Create some data to sign.
byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 };
Console.WriteLine("Data : " + BitConverter.ToString(data));
// Sign the data using the Smart Card CryptoGraphic Provider.
byte[] sig = rsa.SignData(data, "SHA1");
Console.WriteLine("Signature : " + BitConverter.ToString(sig));
// Verify the data using the Smart Card CryptoGraphic Provider.
bool verified = rsa.VerifyData(data, "SHA1", sig);
Console.WriteLine("Verified : " + verified);
}
}
}
Is it possible to implement same with tripledes? I tried but cant find a solution.
Link: https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-access-hardware-encryption-devices

Generally you cannot. And the reason is relatively simple: symmetric ciphers, such as triple-DES are not very useful for person to person cryptography such as signature generation (or MAC for symmetric algorithms) or encryption.
The advantage of asymmetric ciphers is that you can distribute the encryption key to other persons. To do this the other person needs to be certain that they receive the correct key: the key needs to be trusted. For this a Public Key Infrastructure or PKI needs to be setup. Known PKI's are PGP and PKIX which is based on X.509 certificates - the same that are used for your HTTPS connections.
So triple DES on a smart card only makes sense if other entities can use the same key. This would be just to gain a tiny performance enhancement because you can encrypt with a public key as well, even in software. Then you can decrypt using the private key on the card. Generally a hybrid cryptosystem is used where a symmetric key such as a triple-DES key is established using RSA, after which the message is encrypted with that key.
That's not to say it is impossible - I've actually designed a PKCS#11 compliant smart card, but most PKCS#11 and CSP's implementations won't support 3DES, and then you would need to have a compatible smart card with a 3DES key as well. Also see the answer from BartonJS.

using (Pkcs11 pkcs11 = new Pkcs11("cryptoki.dll", true))
{
// Get list of available slots with token present
List<Slot> slots = pkcs11.GetSlotList(true);
// Find first slot with token present
Slot slot = slots[0];
// Open RO session
using (Session session = slot.OpenSession(true))
{
session.Login(CKU.CKU_USER, "userPin");
// Prepare attribute template that defines search criteria
List<ObjectAttribute> objectAttributes = new List<ObjectAttribute>();
objectAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, "TestKey"));
// Initialize searching
session.FindObjectsInit(objectAttributes);
// Get search results
List<ObjectHandle> foundObjects = session.FindObjects(2);
// Terminate searching
session.FindObjectsFinal();
ObjectHandle objectHandle = foundObjects[0];
byte[] iv = Encoding.UTF8.GetBytes("00000000");
byte[] inputData = Encoding.UTF8.GetBytes("data to encrypt.");
Mechanism mechanism = new Mechanism(CKM.CKM_DES3_CBC, iv);
byte[] result = session.Encrypt(mechanism, objectHandle, inputData);
Console.WriteLine(Convert.ToBase64String(result));
}
}
İs that possbile solution?

If have a TripleDES key on a hardware device and know the CNG KSP and key name, you should be able to use TripleDESCng:
using (TripleDES des3 = new TripleDESCng("YourKeyIdentifier", new CngProvider("Your KSP Name"))
{
// So long as you use CreateEncryptor() or CreateDecryptor() (the 0-argument ones)
// the operation is done on the hardware.
}
This, of course, requires that you have a hardware device that supports DES3-EDE.
Getting such a key onto the device is an exercise left to the reader.

Related

How to validate an RSA public key?

If I receive a PEM-encoded key, I can import it like so:
using var rsa = new RSACryptoServiceProvider(2048);
rsa.ImportFromPem(keyString.AsSpan());
// do stuff with it
However, the documentation for ImportFromPem states that it will indiscriminately accept four key types: PUBLIC KEY, PRIVATE KEY, RSA PRIVATE KEY, RSA PUBLIC KEY, and that "Unsupported or malformed PEM-encoded objects will be ignored."
If I want to verify that 1) what I received is a public key and not a private key, and 2) it's a valid public key and not an "Unsupported or malformed PEM-encoded object," how would I go about doing that?
The successful import with the posted code also means a formal validation of the RSA key. The key can be imported:
if it is an RSA key.
if the key contains one of the labels supported for RSA (i.e. PUBLIC KEY (SPKI), RSA PUBLIC KEY (public PKCS#1), PRIVATE KEY (PKCS#8), RSA PRIVTE KEY (private PKCS#1)).
if the body consists of a valid (Base64 encoded) ASN.1/DER.
if the label is consistent with the key contained in the body (both by type (public/private) and format (PKCS#1/PKCS#8/SPKI)).
There is no guarantee that the parameters are validated contentwise for consistency (e.g. in the case of a private key, the modulus might well not be equal to the product of p and q). Among other things, this depends on the platform (e.g. Windows or Unix). Thus, if guaranteed validation of the parameters is required, the parameters must be explicitly validated.
Explicit validation of the parameters is possible by exporting the RSA parameters with ExportParameters() and then validating them. This is relevant for private keys, where the parameters are dependent on each other, so that these consistencies can be tested (e.g. whether the modulus is equal to the product of the two primes). There are no such consistency checks for public keys.
Since this question is about public keys, the checks for private keys are not necessary (and could be omitted).
For a key imported this way, PublicOnly can be used to check whether it is a private or public key.
This makes it relatively easy to create a logic for RSA key validation, e.g.:
using System.Numerics;
using System.Security.Cryptography;
...
private static void ImportPublicKey(string key)
{
using var rsa = new RSACryptoServiceProvider();
try
{
rsa.ImportFromPem(key.AsSpan());
if (rsa.PublicOnly)
{
Console.WriteLine("Public RSA key");
}
else
{
// Explicit check of the consistency of the parameters for private keys, for example N = p*q
var rsaParams = rsa.ExportParameters(true);
BigInteger m = new BigInteger(rsaParams.Modulus, true, true);
BigInteger p = new BigInteger(rsaParams.P, true, true);
BigInteger q = new BigInteger(rsaParams.Q, true, true);
Console.WriteLine("Private RSA key - params " + (p*q==m ? "consistent" : "not consistent"));
}
}
catch
{
Console.WriteLine("Invalid or inconsistent RSA key");
}
}
First of all, it depends on why you want to validate the public key. You should not expect an adversary to deliberately send a bad key. If they can do that they can simply send you the wrong key. To prevent that you need a better way of managing keys, such as using certificates within a Public Key Infrastructure or PKI.
Great, that out of the way, I'd like to indicate that all the defined PEM keys contain a public key. RSA PUBLIC KEY is a PKCS#1 defined public key and RSA PRIVATE KEY is a PKCS#1 private key. However, the definition of the private key also contains the public exponent. Similarly, PUBLIC KEY is a SubjectPublicKeyInfo structure and PRIVATE KEY is a PKCS#8 defined private key. Both contain PKCS#1 key structures inside and therefore the public key.
Maybe you suspect that the public key in the instance is not replaced. That is certainly a possible option if I read the documentation correctly (the quality of the Microsoft documentation is often, uh, questionable though). In that case you might want to validate that the modulus changes value. The modulus is specific for each key pair and it is contained in both public and private keys. You can access the modulus using ExportParameters(false).Modulus.

Create CSR with Bouncy Castle (C#) inclusive EC Curve Name

I create a CSR with Bouncy Castle and sign it on HSM (EC Key). It works fine! But in the CSR content (and in the certificate later) there is no EC curve name. Instead, there are the parameters of the curve. Some clients have problems with this, e.g .: Phyton. On the screenshot you can see how OpenSSL does it. How can I force Bouncy Castle to write the curve name in the CSR?
The code for the Adding the Public Key to CSR:
IList oids = new ArrayList();
IList values = new ArrayList();
..........
SubjectKeyIdentifier subjectKeyIdentifier = new SubjectKeyIdentifierStructure(publicKey);
X509Extension ski = new X509Extension(true, new DerOctetString(subjectKeyIdentifier));
oids.Add(X509Extensions.SubjectKeyIdentifier);
values.Add(ski);
AttributePkcs attribute = new AttributePkcs(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest,
new DerSet(new X509Extensions(oids, values)));
//End Extensions
// SIGN on HSM
Pkcs10CertificationRequestDelaySigned csr = new Pkcs10CertificationRequestDelaySigned(
signatureAlgorithmStr,
subject,
publicKey,
new DerSet(attribute)
);
......
Screenshot: CSR with Bouncy Castle und OpenSSL
The issue isn't with BouncyCastle, but rather how the public key is being generated / initialized.
You can either opt for named curve or as expanded group parameters, but not both.
Technically they are equivalent, but usage wise, they don't mix well with one another. So you will have to generate both if you want to cater to both of those scenarios.
I don't know how you are initializing the public key, but you can convert to a different public key type before signing.
AsymmetricKeyParameter publicKey = null;
// ... existing public key by some means
var castedPublicKey = (ECPublicKeyParameters)publicKey;
var newPublicKey = new ECPublicKeyParameters(
castedPublicKey.AlgorithmName
,castedPublicKey.Q,
X9ObjectIdentifiers.Prime256v1);
// ... rest of code
// SIGN on HSM
Pkcs10CertificationRequestDelaySigned csr = new Pkcs10CertificationRequestDelaySigned(
signatureAlgorithmStr,
subject,
newPublicKey,
new DerSet(attribute)
);
Change Prime256v1 to whatever curve you are using

PKCS Library for Safenet 5100

static string pkcs11LibraryPath = #"C:\Windows\System32\eTPKCS11.dll";
using (Pkcs11 pkcs11 = new Pkcs11(pkcs11LibraryPath, AppType.SingleThreaded))
{
// Get list of available slots with token present
List<Slot> slots = pkcs11.GetSlotList(SlotsType.WithTokenPresent);
// Find first slot with token present
Slot slot = slots[0];
// Open RO session
using (Session session = slot.OpenSession(SessionType.ReadWrite))
{
session.Login(CKU.CKU_USER, "654321");//HSM:123456
for (int i = 0; i <= slot.GetMechanismList().Count - 1; i++)
{
Console.WriteLine(slot.GetMechanismList()[i].ToString());
}
// Prepare attribute template that defines search criteria
List<ObjectAttribute> objectAttributes = new List<ObjectAttribute>();
//objectAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, "test"));//HSM:KEY_028_04
objectAttributes.Add(new ObjectAttribute(CKA.CKA_TOKEN, true));
// Initialize searching
session.FindObjectsInit(objectAttributes);
// Get search results
List<ObjectHandle> foundObjects = session.FindObjects(2);
// Terminate searching
session.FindObjectsFinal();
ObjectHandle objectHandle = foundObjects[0];
byte[] iv = Encoding.UTF8.GetBytes("00000000");
byte[] inputData = Encoding.UTF8.GetBytes("data to encrypt.");
Mechanism mechanism = new Mechanism(CKM.CKM_DES3_CBC, iv);;//HSM: CKM_DES3_CBC
byte[] result = session.Encrypt(mechanism, objectHandle, inputData);
Console.WriteLine(Convert.ToBase64String(result));
}
}
I got the error Net.Pkcs11Interop.Common.Pkcs11Exception: 'Method C_EncryptInit returned CKR_KEY_TYPE_INCONSISTENT' in byte[] result = session.Encrypt(mechanism, objectHandle, inputData);
I am using safenet 5100 etoken could you please help?
It seems you are hitting the most common issue and as a result you are using search template which is too broad. With your search template you'll find all objects that have CKA_TOKEN set to CK_TRUE value. That means all certificates, all asymmetric (e.g. RSA) keys, all symmetric keys (e.g. AES), all data objects and all the other stuff that is stored in your device.
You are then using the first found object (RSA key? AES key? no one knows...) with CKM_DES3_CBC mechanism which requires key of type CKK_DES3 and C_EncryptInit functions complains that you have provided incorrect type of key by returning CKR_KEY_TYPE_INCONSISTENT error.
If you want to be sure that you will find just 3DES keys than you need to use more specific search template:
List<ObjectAttribute> objectAttributes = new List<ObjectAttribute>();
objectAttributes.Add(new ObjectAttribute(CKA.CKA_TOKEN, true));
objectAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK_DES3));
Even better use CKA_LABEL and/or CKA_ID to specify exactly one key that you want to use. And don't forget to read at least "Chapter 2 - Scope", "Chapter 6 - General overview" and "Chapter 10 - Objects" of PKCS#11 v2.20 specification.

Convert from RSACryptoServiceProvider to RSACng

I am currently using RSACryptoServiceProvider and I want to change to RSACng. I am using it to sign data. The reason for the change is that I am using Pkcs1 padding and I understand that Pss padding is preferred. We are undergoing security audits.
My question is how do I instantiate RSACng so that it uses the same private / public key each time?
With RSACryptoServiceProvider I am doing:
CspParameters cp = new CspParameters();
cp.KeyContainerName = "ContainerName";
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(cp);
passing in the container name means it uses the key that persists in the in the container store on the machine.
With RSACng, I tried this, but I get an exception: "The requested operation is not supported"
RSACng RSA = new RSACng(CngKey.Create(CngAlgorithm.Sha256, ContainerName));
I just need to be able to pass the store key name so it uses the same key each time instead of generating a new key.
If you want to create a named/persisted RSA key with CNG:
private static RSA CreatePersistedRSAKey(string name, int keySizeInBits)
{
CngKeyCreationParameters creationParameters = new CngKeyCreationParameters
{
// This is what an ephemeral key would have had
// (allows ExportParameters(true) to succeed). Adjust as desired.
//
// The default is not exportable (only applies to the private key)
ExportPolicy =
CngExportPolicies.AllowExport | CngExportPolicies.AllowPlaintextExport,
};
creationParameters.Parameters.Add(
new CngProperty(
"Length",
BitConverter.GetBytes(keySizeInBits),
CngPropertyOptions.Persist));
// RSACng will extract the data it needs from this key object,
// but doesn't take ownership
using (CngKey key = CngKey.Create(CngAlgorithm.Rsa, name, creationParameters))
{
return new RSACng(key);
}
}
This skips the parts where you would do a try/catch around a call to CngKey.Open, or might want to delete the key (open it with CngKey.Open, and call Delete on the CngKey instance).
(CngAlgorithm.Rsa was added in net46. If you're on an older version then an equivalent would be new CngAlgorithm("RSA"))

Verify Private Key Protection before signing with RSACryptoServiceProvider

When signing data with RSACryptoServiceProvider in C#, I have a requirement to ensure the certificate was imported with strong key protection and a high security level to require the user enters the password every time they sign with the key. Here's a quick simplified sample of the signing code:
X509Store myCurrentUserStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
myCurrentUserStore.Open(OpenFlags.MaxAllowed);
X509Certificate2 currentCertificate = myCurrentUserStore.Certificates[4];
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
key.FromXmlString(currentCertificate.PrivateKey.ToXmlString(true));
byte[] signedData = Encoding.UTF8.GetBytes(originalFileContent);
byte[] signature = key.SignData(signedData, CryptoConfig2.CreateFromName("SHA256CryptoServiceProvider") as HashAlgorithm);
So what's the best way to go about checking how the certificate was installed so I can display an error message if it was not installed with strong private key protection with a high security level?
There are a couple things in your snippet that I don't understand.
Why you're opening with MaxAllowed. If you just want to read, use ReadOnly.
Why you're reading store.Certificates[4]. But presumably this is just a placeholder for "read a cert".
Why you're exporting and re-importing the key. (Especially since that would have had to prompt, which would defeat your "it needs to prompt" goal).
For #3 I'm assuming you are just looking to have a unique instance, in which case: Good news! .NET 4.6 added a GetRSAPrivateKey (extension) method to X509Certificate2 which always returns a unique instance. (And you might be excited to know about the new overload to SignData which doesn't encourage sending objects to the finalizer queue: https://msdn.microsoft.com/en-us/library/mt132675(v=vs.110).aspx)
Anyways, what I wrote here works for medium (consent) or high (password) protection. The CngKey based approach can distinguish medium from high, but the classic CAPI fallback can't tell which is which. (The classic CAPI fallback will only happen with obscure HSMs which don't have a CNG-compatible driver).
private static bool HasProtectedKey(X509Certificate2 cert)
{
if (!cert.HasPrivateKey)
{
return false;
}
using (RSA rsa = cert.GetRSAPrivateKey())
{
return HasProtectedKey(rsa);
}
}
private static bool HasProtectedKey(RSA rsa)
{
RSACng rsaCng = rsa as RSACng;
if (rsaCng != null)
{
return rsaCng.Key.UIPolicy.ProtectionLevel != CngUIProtectionLevels.None;
}
RSACryptoServiceProvider rsaCsp = rsa as RSACryptoServiceProvider;
if (rsaCsp != null)
{
CspKeyContainerInfo info = rsaCsp.CspKeyContainerInfo;
// First, try with the CNG API, it can answer the question directly:
try
{
var openOptions = info.MachineKeyStore
? CngKeyOpenOptions.MachineKey
: CngKeyOpenOptions.UserKey;
var cngProvider = new CngProvider(info.ProviderName);
using (CngKey cngKey =
CngKey.Open(info.KeyContainerName, cngProvider, openOptions))
{
return cngKey.UIPolicy.ProtectionLevel != CngUIProtectionLevels.None;
}
}
catch (CryptographicException)
{
}
// Fallback for CSP modules which CNG cannot load:
try
{
CspParameters silentParams = new CspParameters
{
KeyContainerName = info.KeyContainerName,
KeyNumber = (int)info.KeyNumber,
ProviderType = info.ProviderType,
ProviderName = info.ProviderName,
Flags = CspProviderFlags.UseExistingKey | CspProviderFlags.NoPrompt,
};
if (info.MachineKeyStore)
{
silentParams.Flags |= CspProviderFlags.UseMachineKeyStore;
}
using (new RSACryptoServiceProvider(silentParams))
{
}
return false;
}
catch (CryptographicException e)
{
const int NTE_SILENT_CONTEXT = unchecked((int)0x80090022);
if (e.HResult == NTE_SILENT_CONTEXT)
{
return true;
}
throw;
}
}
// Some sort of RSA we don't know about, assume false.
return false;
}

Categories