I'm hoping you can help me with a weird error I've been getting while trying to implement a QueryString encryption module using RSA for my encryption type. My question is 2 fold:
Can you help me resolve the errors I'm getting?
Do you recommend something other than RSA to encrypt a QueryString?
Background/Important info:
I created an object called QueryString, which i store in the session (and which uses the SessionID to generate keys/salt). I instantiate it on Session Start, which generates the keys, and it naturally dies on Session.Abandon... I retrieve it in my BasePage and use it in my pages afterword much like i would a normal querystring (QueryString[key] for gets and stuff)... I store my Public and Private keys in the object itself, as internal properties to the object.
Another important thing is that my web site has a lot of grids, whithin which a record rows that contain links, and so they all have to be encrypted before they are set (href=...)... so the QueryString object i created can get taxed quite a bit and quite quickly (while looping with OnRowCreated or something to encrypt the hrefs).
The error(s):
i am currently getting intermittent errors, that cannot be reproduced (they happen at random... trust me... very random), of the following types when i try to either Encrypt or Decrypt:
Error type 1: CreateProvHandle
Error type 2: The specified file could not be found.
Error type 3: Attempted to perform an unauthorized operation.
For errors 1 and 2, i managed to deal with it so far by simply recursively calling the method (encrypt or decrypt) that caused it and they usually only recurse once (max i've had is 3 using my metrics) and the error magically disappears... so i blamed it on too many calls too fast to the object itself or something... but if someone has any clue as to why this would happen or how to solve this, i would love to take the recursing out of my methods and truly throw when a major exception occurs instead. On top of that i told my RSA params not to persist anything in the CSP store and so i thought the file thing didn't matter but apparently not...
For error 3, i simply cannot get my head around it! My RSA parameters say not to persist anything in the CSP so i don't know how, when or why it would even try to access files (yes, i am repeating myself!), let alone files that are restricted or that the user wouldn't have access to? Please help me!!
Here's some code for my RSA params... maybe you'll find something there that doesn't jive with what i'm trying to do (generate the keys once on object instantiation, store the object in the session, and use that from that point on/disconnect from anything remote/calls to server processes that are not part of the site or .NET)?
public static void AssignParameter()
{
const int PROVIDER_RSA_FULL = 1;
const string CONTAINER_NAME = "ICareContainer";
CspParameters cspParams;
cspParams = new CspParameters(PROVIDER_RSA_FULL);
cspParams.KeyContainerName = CONTAINER_NAME;
cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
cspParams.ProviderName = "Microsoft Strong Cryptographic Provider";
CryptoKeyAccessRule rule = new CryptoKeyAccessRule("everyone", CryptoKeyRights.FullControl, AccessControlType.Allow);
cspParams.CryptoKeySecurity = new CryptoKeySecurity();
cspParams.CryptoKeySecurity.SetAccessRule(rule);
rsa = new RSACryptoServiceProvider(cspParams);
rsa.PersistKeyInCsp = false;
rsa.KeySize = 1024;
}
public static string[] GetKeys()
{
AssignParameter();
string[] keys = new string[2];
//privatekey
keys[0] = rsa.ToXmlString(true);
//publickey
keys[1] = rsa.ToXmlString(false);
return keys;
}
public static string EncryptData(string data2Encrypt, string key)
{
AssignParameter();
string publicOnlyKeyXML = key;
rsa.FromXmlString(publicOnlyKeyXML);
//read plaintext, encrypt it to ciphertext
byte[] plainbytes = System.Text.Encoding.Default.GetBytes(data2Encrypt);
byte[] cipherbytes = rsa.Encrypt(plainbytes, false);
return Convert.ToBase64String(cipherbytes);
}
public static string DecryptData(string data2Decrypt, string key)
{
AssignParameter();
byte[] getpassword = Convert.FromBase64String(data2Decrypt);
string publicPrivateKeyXML = key;
rsa.FromXmlString(publicPrivateKeyXML);
//read ciphertext, decrypt it to plaintext
byte[] plain = rsa.Decrypt(getpassword, false);
return System.Text.Encoding.Default.GetString(plain);
}
Ah yes... stupid mistake (aren't they always?):
Normal Class
Static Crypto Class
End Static
End Normal
Can you find the problem and why i was getting collision errors? I've changed the Static to be Normal and all is well in my neck of the woods.
Cheers!
Related
i don't know if this question is very easy and I just didn't figure it out how to sign with HashiCorp-Vault´s Api VaultSharp, but I am despairing.
The entire Documentation with examples can be found here: https://github.com/rajanadar/VaultSharp
Encryption and Decryption works fine. Only Signing is a problem.
Code for Encryption:
public byte[] EncryptData(byte[] data, string keyName)
{
SecretsEngine transitSecretsEngine = new SecretsEngine
{
Type = SecretsEngineType.Transit,
Path = path
};
Client.V1.System.MountSecretBackendAsync(transitSecretsEngine).Wait();
Client.V1.Secrets.Transit.CreateEncryptionKeyAsync(keyName, new CreateKeyRequestOptions()
{
Exportable = true
}, path).Wait();
EncryptRequestOptions encryptOptions = new EncryptRequestOptions
{
Base64EncodedPlainText = Convert.ToBase64String(data),
ConvergentEncryption = true,
};
Secret<EncryptionResponse> encryptionResponse = Client.V1.Secrets.Transit.EncryptAsync(keyName,
encryptOptions, path).Result;
string cipherText = encryptionResponse.Data.CipherText;
return Encoding.Unicode.GetBytes(cipherText);
}
Code for Decryption:
public byte[] DecryptData(string ciphertext, string keyName)
{
DecryptRequestOptions decryptOptions = new DecryptRequestOptions
{
CipherText = ciphertext,
};
Secret<DecryptionResponse> decryptionResponse = Client.V1.Secrets.Transit.DecryptAsync(keyName,
decryptOptions, path).Result;
return Convert.FromBase64String(decryptionResponse.Data.Base64EncodedPlainText);
}
Here is my Code Trial for signing:
public byte[] Sign(byte[] plaintextBytes, string keyName)
{
byte[] hash = ComputeHash(plaintextBytes,SHA256.Create());
GCKMS.SignatureOptions options = new GCKMS.SignatureOptions()
{
Digest = Convert.ToBase64String(hash),
};
Secret<GCKMS.SignatureResponse> result = Client.V1.Secrets.GoogleCloudKMS.SignAsync(keyName,
options).Result;
return Encoding.Unicode.GetBytes(result.Data.Signature);
}
The Error is:
VaultSharp.Core.VaultApiException: {"errors":["no handler for route
'gcpkms/sign/Manuel'"]}
Last but not least my Code for validating the signature:
public bool ValidateSignature(byte[] plaintextByte, byte[] signature, string keyName)
{
GCKMS.VerificationOptions option = new GCKMS.VerificationOptions
{
Digest = Encoding.Unicode.GetString(ComputeHash(plaintextByte)),
Signature = Encoding.Unicode.GetString(signature)
};
Secret<GCKMS.VerificationResponse> result =
Client.V1.Secrets.GoogleCloudKMS.VerifyAsync(keyName, option).Result;
return result.Data.Valid;
}
I am not sure but this could be because I don't use a SecretsEngine with a Path. I could not find any SecretsEngine for GoogleCloudKms.
Useful information:
I generate the Path with Guid.NewGuid().ToString();.
ComputeHash is a self written Function that computes the Hash with a give Algorithm. The
default algorithm is SHA256.
GCMS is a short version of the Namespace VaultSharp.V1.SecretsEngines.GoogleCloudKMS
Any ideas and suggestions are very welcome.
Thanks in advance!
Although Vault offers convenient signature with Transit, the C# wrapper you are using does not support it.
Google KMS does offer signature, but its interface is more complex: you have to do the hash yourself and keep track of the key versions.
What I suggest is that you play a trick on your API wrapper:
Leave your encryption and decryption code as-is
Write to the the Transit backend as if it was a KV store version 1
Get your signature by sending your payload as the input parameter
You still have to base64 your data before sending it to Vault, to avoid binary encoding issues.
So assuming that:
You want to sign the text StackOverflow
The transit back-end is mounted under transit
Your signature key is named my-key
This should get you started:
var value = new Dictionary<string, object> { "input", Convert.ToBase64String(Encoding.UTF8.GetBytes("StackOverflow")) } };
var writtenValue = await vaultClient.V1.Secrets.KeyValue.V1.WriteSecretAsync("sign/my-key", value, "transit");
I have spent several hours trying to have a valid Encrypt/Decrypt function that uses Asymmetric encryption, and the best option seems to be RSA.
But The thing is that I want to be able to provide my own public/private key to the function as a string myself in the form : "769de1f1a9dd6e114f81b9490ea42a2967840353edd358a35c84e2c831dd40a2"
something very very similar to the 'eth-crypto' npm library for javascript.
but I haven't found any article or documentation that explains that.
so has anyone implemented it before or have an article that explains it.
keep in mind that when using asp.net core the FromXmlString doesn't work even when using 3.0
Here is my Encrypt function so far :
public static string EncryptAsymmetric(string textToEncrypt, string publicKeyString)
{
var bytesToEncrypt = Encoding.UTF8.GetBytes(textToEncrypt);
using (var rsa = new RSACryptoServiceProvider(2048))
{
try
{
rsa.FromXmlString(publicKeyString);
var encryptedData = rsa.Encrypt(bytesToEncrypt, true);
var base64Encrypted = Convert.ToBase64String(encryptedData);
return base64Encrypted;
}
finally
{
rsa.PersistKeyInCsp = false;
}
}
}
but it throws an error on the FromXmlString ( operation not supported on this platform )
so if there is any other way maybe......
any help is very appreciated because I have been looking into it for many hours with no result.
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.
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"))
I've read a nice guide a few days ago about generating a token on the server's side to have the time of the token's creation within the token, along with "Guid.NewGuid()" 's encryption.
However, I've tried to adjust the results to have a user's username within the token, rather than the date time. I'm close, but I cannot extract the username itself, I can only receive it with some random letters after it.
Code of the ASP.NET generic handler to GENERATE the token upon identification
ASCIIEncoding encoder = new ASCIIEncoding();
if(postType == "identify")
{
byte[] binName = encoder.GetBytes(name);
byte[] key = Guid.NewGuid().ToByteArray();
string _token = Convert.ToBase64String(binName.Concat(key).ToArray());
// The above creates a token with the name and the "key", works well
}
Code of the generic handler to decrypt the token (see example for result)
if(postType == "check")
{
string _token = dict["token"] as string;
byte[] data = Convert.FromBase64String(_token);
string theCode = encoder.GetString(data); // This will get both the username and the GUID key within
context.Response.Write(jss.Serialize(new Code { eCode = theCode })); // Returns in JSON, irrelevant to the question, it works well
}
EXAMPLE: If the name would be "user", then the varialbe "theCode" would hold the value of "userXyZxYzXyZ" (while XyZ stands for the GUID's "random" key).
I think it is fair to say that my question is how to separate this GUID's key from the username upon decryption
A guid is 38 characters long, so the name will be theCode.SubString(0, theCode.Length - 38). Alternately, you can compare the current user's name with theCode: theCode.StartsWith(name).