Encrypt/Decrypt using DataProtection.DataProtector c# - c#

I need to decrypt a conection that was created initially under an account that is no longer available.
In order to do that I made a simple app:
private void btnEncrypt_Click(object sender, EventArgs e)
{
DataProtection.DataProtector dp = new DataProtection.DataProtector(DataProtection.DataProtector.Store.USE_MACHINE_STORE);
try
{
byte[] dbToEncrypt = Encoding.ASCII.GetBytes(txtText.Text);
string resultEncrypted = Convert.ToBase64String(dp.Encrypt(dbToEncrypt, null));
txtEncrypt.Text = resultEncrypted;
}
catch (Exception ex)
{
MessageBox.Show("Error: " + ex.Message);
}
}
private void btnDecrypt_Click(object sender, EventArgs e)
{
DataProtection.DataProtector dp = new DataProtection.DataProtector(DataProtection.DataProtector.Store.USE_MACHINE_STORE);
try
{
byte[] dbToDecrypt = Convert.FromBase64String(txtEncrypt.Text);
string resultDecrypted = Encoding.ASCII.GetString(dp.Decrypt(dbToDecrypt, null));
txtDecrypt.Text = resultDecrypted;
}
catch (Exception ex)
{
MessageBox.Show("Error: " + ex.Message);
}
}
Now, I have noticed that when I do a test in my computer, and try to decrypt the encrypted result in a different computer I get:
Exception decrypting. Decryption failed. Key not valid for use in
specific state.
Then, I did some research and found out this:
Did you export the key from one server to the other so they are both
set up the same? If not, you are using mismatched keys, which will
cause an encryption/decryption error.
and I can find the keys here:
How to get the validationkey value and decryptionkey value?
decryption key can be found at "D:\Documents and Settings\All
Users\Application Data\Microsoft\Crypto\RSA\MachineKeys"
so my question is: If I export the keys in that location from my computer the the one I want to decrypt the data will that work? and by export mean just copy the key files or do another operation?

AFAIK this is not possible - and in any case is not desirable. DPAPI regularly creates new keys, so even if you could copy the keys between machines, they would become obsolete after a period of time.
If you want to decrypt data on more than one computer, use a different method, e.g. RSA.

Related

Can't extract private key from PCKS#12 encoded certificate file in .NET 6

I'm writing a .NET 6 application for Windows that is intended to extract the private key from a PFX file containing an RSA cert/key bundle.
public static Boolean ToCertAndKey(String pfxFilePath, String? unlockPassword, String certFilePath, String keyFilePath, String? keyPassword, out String error) {
try {
error = String.Empty;
using var bundle = new X509Certificate2(pfxFilePath, unlockPassword);
RSA key = bundle.GetRSAPrivateKey();
Byte[] publicKeyBytes = key.ExportSubjectPublicKeyInfo();
Byte[] privateKeyBytes;
//We fail here.
if (String.IsNullOrEmpty(keyPassword)) {
privateKeyBytes = key.ExportPkcs8PrivateKey();
} else {
privateKeyBytes = key.ExportEncryptedPkcs8PrivateKey(keyPassword,
new PbeParameters(
PbeEncryptionAlgorithm.Aes256Cbc,
HashAlgorithmName.SHA256,
iterationCount: 1));
}
String encodedCert = new(PemEncoding.Write("PUBLIC KEY", publicKeyBytes));
File.WriteAllText(certFilePath, encodedCert);
String encodedKey = new(PemEncoding.Write("PRIVATE KEY", privateKeyBytes));
File.WriteAllText(keyFilePath, encodedKey);
return true;
} catch (Exception ex) {
error = $"An exception occurred: '{ex.Message}'\r\n\r\nStack Trace:\r\n{ex.StackTrace}";
return false;
}
}
It fails at both ExportPkcs8PrivateKey (When I don't specify a password to encrypt the key) and ExportEncryptedPkcs8PrivateKey (when I do) with the same exception text:
WindowsCryptographicException: The requested operation is not supported
I came across this answer however, I'm still receiving the same exception at RSA.ExportEncryptedPkcs8PrivateKey.
There doesn't appear to be anything wrong with the PFX files I've been testing with; I'm able to import them into my certstore via the UI or PowerShell with no issues.
Hoping someone else has run into this issue.
You need to mark the keys as exportable.
Change
using var bundle = new X509Certificate2(pfxFilePath, unlockPassword);
to
using var bundle = new X509Certificate2(pfxFilePath, unlockPassword, X509KeyStorageFlags.Exportable);

How can I create a file that only my application can modify it?

How can I create a file that only my application can modify? I need it because I have an app that creates a .txt file where user information is stored and I don't want the user to be able to modify it through File Explorer, but my App should be able to create it, modify it and delete it.
this is my code:
public void Write(List<Queue> Queue)
{
try
{
CreateFile();
using (FileStream Stream = new FileStream(Path, FileMode.Open))
{
using (StreamWriter file = new StreamWriter(Stream))
{
string Data = JsonSerializer.Serialize(Queue);
file.Write(Data);
file.Flush();
file.Close();
}
}
}
catch (IOException ex)
{
Log.GetInstance().Write(ex.Message);
}
public void CreateFile()
{
if (!FileExist)
{
File.Create(Path).Close();
}
}
public List<Queue> ReadFile()
{
try
{
if (FileExist)
{
using (StreamReader file = new StreamReader(Path))
{
string Data= file.ReadToEnd();
return JsonSerializer.Deserialize<List<Cola>>(Data);
}
}
}
catch (JsonException ex)
{
Log.GetInstance().Write(ex.Message);
}
catch (IOException ex)
{
Log.GetInstance().Write(ex.Message);
}
return null;
}
You can encrypt the file to prevent tampering. Well, at least any tampering will corrupt the file.
The CLR has a mechanism to encrypt data to a specific user without having to generate and store a key separately. This uses the ProtectedData.Protect() and ProtectedData.Unprotect() mechanism in System.Security.Cryptography.
Here is a test program. Run it one, select 1 to store data, type in something secret then quit. Run it a second time, select 2 to read data and the secret text will appear.
class Program
{
static void Main(string[] args)
{
while (true)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Do you want to 1. store data, 2. read data or 0. exit ?");
Console.ForegroundColor = ConsoleColor.Gray;
var input = Console.ReadLine();
if (input.Length == 0 || input[0] == '0') break;
if (input[0] == '1')
{
Console.WriteLine("Type in a very secret message to store:");
Console.ForegroundColor = ConsoleColor.Yellow;
var text = Console.ReadLine();
Console.ForegroundColor = ConsoleColor.Gray;
if (text.Length > 0)
{
WriteToFile(text);
}
}
if (input[0] == '2')
{
if (ReadFromFile(out string text))
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(text);
Console.ForegroundColor = ConsoleColor.Gray;
}
}
}
}
const string filename = "data.enc";
static bool WriteToFile(string text)
{
var fn = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), filename);
var data = Encoding.Unicode.GetBytes(text);
try
{
var cipher = ProtectedData.Protect(data, null, DataProtectionScope.CurrentUser);
File.WriteAllBytes(fn, cipher);
Console.WriteLine($"Encrypted {data.Length} bytes in {fn}.");
return true;
}
catch (CryptographicException ex)
{
Console.WriteLine("Error encoding data: ");
Console.WriteLine(ex.ToString());
}
catch (IOException ex)
{
Console.WriteLine("Error creating file: ");
Console.WriteLine(ex.ToString());
}
return false;
}
static bool ReadFromFile(out string text)
{
var fn = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), filename);
try
{
var cipher = File.ReadAllBytes(fn);
var data = ProtectedData.Unprotect(cipher, null, DataProtectionScope.CurrentUser);
text = Encoding.Unicode.GetString(data);
Console.WriteLine($"Decrypted {data.Length} bytes from {fn}.");
return true;
}
catch (CryptographicException ex)
{
Console.WriteLine("Error decoding data: ");
Console.WriteLine(ex.ToString());
}
catch (IOException ex)
{
Console.WriteLine("Error reading file: ");
Console.WriteLine(ex.ToString());
}
text = null;
return false;
}
}
and the contents of data.enc are completely encryted:
You cannot as far as I know. Users and/or administrators will have access to that file either way. You can, however, detect modification by hashing the file and comparing the hash with the file
How can I create a file that only my application can modify it?
You can't. Simple as that.
The thing is that administrators can access all files. And the user your running your app with. And hackers of course. ;) Typically you would just store application files in some well established locations, e.g. it is a de facto standard to put app files in /var/lib/myapp in unix systems. Of course other users will still be able to access those files, but that way you at least lower accidental modifications risk. However this also means that you should never store sensitive data locally.
Encryption is not really an option, because you have to store the encryption key somewhere, which just moves the pile from one place to another. It does make read/write a bit harder, but it doesn't really solve the issue. Unless you don't store the key anywhere, e.g. you manually provide it on application start or even better during runtime and you ensure it doesn't live too long. Depending on your usecase this may be a valid option.
Modification detection, i.e. hashing the file, has exactly the same issue: you have to store the hash somewhere. You only force the malicious user to modify two locations instead of one.
Note that using a database engine locally won't help at all. At the end of the day a database is just a fancy file, and you still need to store access keys somewhere.
All in all: if you are storing a non-sensitive data, then put it in application data folder and don't worry about it.
Not sure exactly what your application should do, but the way to achieve this would be to create a new user or group on the computer and run your application as that user. You can then use SetAccessControl to make it so that the user or group as read/write permissions and everyone else has read permissions. This should actually be a Windows service rather then an application.

C# RSA decrypt parameter is incorrect

I am trying to decrypt communication from web sockets secured with SSL certificate. The certificate is installed on the machine and the private key is present. I have this decrypt function
public static string DecryptTry(X509Certificate2 x509, string stringTodecrypt)
{
try
{
if (x509 == null || string.IsNullOrEmpty(stringTodecrypt))
throw new Exception("A x509 certificate and string for decryption must be provided");
if (!x509.HasPrivateKey)
throw new Exception("x509 certificate does not contain a private key for decryption");
if(x509 == null)
{
Console.WriteLine("certificate is null");
return "";
}
Console.WriteLine("decoding text with private key " + x509.HasPrivateKey);//true
using (RSA csp = (RSA)x509.PrivateKey)
{
byte[] bytestoDecrypt = Encoding.UTF8.GetBytes(stringTodecrypt).Take(256).ToArray();
Console.WriteLine("key size: " + x509.PrivateKey.KeySize + " received data length: " + stringTodecrypt.Length + " bytes length: " + bytestoDecrypt.Length);
Console.WriteLine(Encoding.UTF8.GetString(bytestoDecrypt));
byte[] bytesDecrypted = csp.Decrypt(bytestoDecrypt, RSAEncryptionPadding.OaepSHA256); //ERROR HERE
return Encoding.UTF8.GetString(bytesDecrypted);
}
}
catch(Exception e)
{
Console.WriteLine("Error while decrypting text: " + e.Message + e.HResult + e.StackTrace);
return "";
}
}
But the csp.Decrypt is throwing an error
parameter is incorrect
I have tried all padding parameters - none of the seemed to make a difference.
Does anybody know where the problem might be? Am I missing something?
**
EDIT 25.12.2020
**
To add some more background info: The website where the WebSocket client is listening is secured HTTPS, the SSL certificate is signed by CA with my full access to all of its information. The initial problem is handshake for the WebSocket communication which comes encrypted. I was thinking I would be able to decrypt it with the private key and that is where the problem occurs. The length of the incoming request (or handshake) is between 490 and 520 bytes, so that is the reason for .Take(256). I was thinking to split the text into multiple, decode them separately and put together after. That, however, brought me here.
One final thought: This is a .NET console application. Could the problem be possibly fixed by converting it to a format that IIS accepts? The IIS on the machine has the certificate installed... could it possibly make a difference?
Thanks in advance!
I'm not sure what you want to achieve:
Decrypt something using RSA
Decrypt TLS layer of HTTP
Decrypt something in Websocket protocol
I think that you should give some more informations how it works. Can you show example input for function function, means:
DecryptTry(X509Certificate2 x509, string stringTodecrypt) { ... }
How 'strindTodecypt' looks like? What do you put there?
How do you connect to this server to get data to decrypt, via TLS?

C# - Unable to read an HKLM Registry Key (Object reference not set to an instance of an object)

I've found similar posts and I've tried various combinations of examples but cannot get this working. I have the following registry setup and I'm trying to get the CONNECTWISEID value and set it to a string variable:
Here is the code (Note I'm opening the key in read only):
try
{
RegistryKey key = Registry.LocalMachine.OpenSubKey(#"SOFTWARE\CentraStage", false);
string connectwiseId = (string)key.GetValue("CONNECTWISEID");
key.Close();
MessageBox.Show(connectwiseId, "Reg Key Value", MessageBoxButtons.OK);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message,"Error",MessageBoxButtons.OK,MessageBoxIcon.Error);
}
An error is always caught and I get the following error:
[
The registry keys are not in the WOW6432Node branch either. I've also tried this code with no success:
string connectwiseId = (string)Registry.GetValue(#"HKEY_LOCAL_MACHINE\SOFTWARE\CentraStage", "CONNECTWISEID", null);
Any suggestions?
OpenSubKey appears to return the next key down the chain, so the following is probably what you need:
try
{
// Opens the software key
RegistryKey softwareKey = Registry.LocalMachine.OpenSubKey("SOFTWARE", false);
// Opens the CetraStage key
RegistryKey key = softwareKey.OpenSubKey("CentraStage", false);
string connectwiseId = (string)key.GetValue("CONNECTWISEID");
key.Close();
MessageBox.Show(connectwiseId, "Reg Key Value", MessageBoxButtons.OK);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message,"Error",MessageBoxButtons.OK,MessageBoxIcon.Error);
}
MSDN link for OpenSubKey: https://learn.microsoft.com/en-us/dotnet/api/microsoft.win32.registrykey.opensubkey?view=netframework-4.8

System.Diagnostics.Process.Start for .pdf file paths in SQL

Good day.
In my C# windows forms app, I would like to open .pdf files.
The code to do this is:
private void btnOpenPdf_Click(object sender, EventArgs e)
{
try
{
System.Diagnostics.Process.Start(lblPdf.Text);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
The .pdf file paths are storred in an SQL database.
They are stored in this manner: C:\Folder1\Folder2\File Name
In this example this means:
lblPdf.Text="C:\Folder1\Folder2\File Name";
*note: File Name(s) is/are stored without file type (so no .pdf at the end)
Of course this doesn't work, so I added the "#" and ".pdf" to the string:
lblTest.Text = ("#" + "\"" + lblPdf.Text + ".pdf" + "\"");
When I test this with a Message Box:
MessageBox.Show(lblTest);
I get:
#"C:\Folder1\Folder2\File Name.pdf"
The trouble I am experiencing is that this works:
private void btnOpenPdf_Click(object sender, EventArgs e)
{
try
{
MessageBox.Show(lblTest.Text);
System.Diagnostics.Process.Start(#"C:\Folder1\Folder2\File Name.pdf");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
and this does not work:
private void btnOpenPdf_Click(object sender, EventArgs e)
{
try
{
MessageBox.Show(lblTest.Text);
System.Diagnostics.Process.Start(lblTest.Text);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Can anyone shed a light on why this is so?
The error message I receive in the second example is:
"The system cannot find the file specified"
MessageBox.Show gives me the correct syntax and file path in both cases.
Thank you.
#Shovers and anyone else to whom it may concern:
private void btnOpenPdf_Click(object sender, EventArgs e)
{
lblTest.Text = ("#" +lblPdf.Text + ".pdf" );
try
{
MessageBox.Show(lblTest.Text);
System.Diagnostics.Process.Start(lblTest.Text);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Thanks guys.
Just adding the
+ ".pdf"
to the label (lblPdf.Text) is the answer.
private void btnOpenPdf_Click(object sender, EventArgs e)
{
try
{
System.Diagnostics.Process.Start(lblPdf.Text + ".pdf");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
I can give you a number of hints:
1 Exceptions
Proper exception handling is a pet peeve of mine and yours is problematic. Your code handles fatal exceptions. And never catching fatal exceptions is a very important part. Doing it will only get you more and less understandable followup errors. Here are two artciles on the mater I do link often:
https://blogs.msdn.microsoft.com/ericlippert/2008/09/10/vexing-exceptions/
https://www.codeproject.com/Articles/9538/Exception-Handling-Best-Practices-in-NET
2. How are those PDF stores with the Database?
PDFs are Binary Large Objects (BLOBS). Ther are two schools of tought on to store blobs with a DB and both affect wich path you have to give:
You store the blob itself in the DB. Usually that requires setting up a HTTP handler to provide the file for download/user consumption. In this case you need a URL to access those files
You store the files in the Filesystem. The DB only contains the paths. In this case you have to eitehr use the URL, Share path or whatever else way you got to download those files
SQL Filestream and equivalent attributes from other DB are a bit of a combination of the two. It works mostly like the 1st one, with performance closer to the 2nd one.
You might have some 4th bastardised version here. Or you simply did not understand how how to use the values in the DB.

Categories

Resources