Do I have to encrypt SecureString when persisting to disk? - c#

For a C# console app, I need to persist a password in the application settings but when I create a setting of type System.Security.SecureString the setting itself is removed from the plain-text config file. Since I can't see the raw value anymore I can't validate whether or not the data is still encrypted when saved.
Is SecureString the best approach or should I use ProtectedData to simply encrypt the string?
--EDIT--
Here is the test code that I used to validate that a SecureString can be persisted.
[global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public global::System.Security.SecureString Password
{
get
{
return ((global::System.Security.SecureString)(this["Password"]));
}
set { this["Password"] = value; }
}
static void Main(string[] args)
{
PersistPassword("A-Test-Password");
Console.WriteLine(ReadPassword());
Console.ReadLine();
}
static void PersistPassword(string Password)
{
SecureString ss = new SecureString();
Password.ToCharArray().ToList().ForEach(ss.AppendChar);
Settings.Default.Password = ss;
}
static string ReadPassword()
{
SecureString ss = Settings.Default.Password;
IntPtr ptr = Marshal.SecureStringToCoTaskMemUnicode(ss);
return Marshal.PtrToStringUni(ptr);
}

You cannot persist data that is encrypted with SecureString. The key is held in memory, and only lasts as long as your program is alive. SecureString is a wrapper around the native CryptProtectMemory function.
If you need you need the encrypted data to be persistable (for longer than your program exists), you need the Data Protection API (DPAPI), and it's CryptProtectData function - which is exposed to C# users through the ProtectedData class.
SecureString has the advantage of being ephemeral; useful for:
passwords
credit card numbers
Social Insurance numbers
while they are being used by your program - and then deleted.
The DPAPI is better for long-term storage. But beware the protection levels, and you choose the one that is appropriate for what you need:
only decryptable by me
only decryptable on this PC
only decryptable by anyone on the domain
If you need encryption that can survive transport to different sites or different domains: CryptProtectData is not for you.
Four levels
CryptProtectMemory (SecureString in .NET): only useful in memory of your process
CryptProtectData (ProtectedData in .NET): encrypts a blob of data, and you can store it anywhere you like (memory, registry, hard disk) - but you have to store it yourself.
CredWrite: encrypts a password using CryptProtectData, and stores it for you in the Windows Password Vault (Start → Credential Manager)
CredUIPromptForCredentials/CredUIConfirmCredentials: prompt the user for a password, encrypt it, and saves it in the Password Vault (Start → Credential Manager) using CredWrite

As states in MSDN,
The value of an instance of SecureString is automatically protected using a mechanism supported by the underlying platform when the instance is initialized or when the value is modified.
If you want to provide the mechanism to keep it readonly once the password in store in securestring then you can invoke the method MarkAsReadonly() on it.
For persistence purpose you can also Hash the SecureString and create a salt for it. You can retreive the salt for later use e.g. comparison pupose. Check out this code snippet which is using the salt over Securestring.

Related

Does RSACryptoServiceProvider work when you try to sign using PIV card's private key and a global pin?

I am trying to use RSACryptoServiceProvider with CspParameters that point to a global pin.
It works correctly if I use an application pin but when I use the global pin it fails with:
"The card cannot be accessed because the wrong PIN was presented."
Will it work when I use a global pin? Is there an option that tells it what type of pin to look for?
Thanks in advance.
Update:
I am retrieving the discovery object from the smart card if it exists.
This will tell me two things I want to know.
1). If the card has both application and global pins. (first byte of pin usage >= 60)
2). Which pin is considered primary. (second byte 0x10 = app, 0x20 = global)
I have a card, the NIST Test Pivcard 3, which has both pins but the global pin is primary. For this card when I enter the global pin on my test form, I can do a verify against it and it validates the pin correctly. (CLA=0x00, INS=0x20, P1=0x00, P2=0x00, Lc=0x8)
I can do the same for this card if I enter the application pin instead (with P2 set to 0x80) and it verifies it correctly.
After I verify the pin, set the AID and get some other x509 data from the card, I attempt to sign some hashed data using the card's private key.
Using RSACryptoServiceProvider and CspParameters it fails whenever I pass it the global pin. I get "The card cannot be accessed because the wrong PIN was presented."
If I pass it a valid application pin, then it works fine.
My code looks like this:
try
{
SecureString ss = new SecureString();
char[] PINs = PIN.ToCharArray();
foreach (char a in PINs)
{
ss.AppendChar(a);
}
CspParameters csp = new CspParameters(1, "Microsoft Base Smart Card Crypto Provider");
csp.Flags = CspProviderFlags.UseDefaultKeyContainer;
csp.KeyPassword = ss;
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(csp);
byte[] data = File.ReadAllBytes(hashFile);
sig = rsa.SignHash(data, "SHA1");
bool verified = rsa.VerifyHash(data, CryptoConfig.MapNameToOID("SHA1"), sig);
}
catch (Exception ex)
{
txt_msg.Text = ex.Message;
etc...
}
Is there some flag I am missing here to say that the pin being used is a global pin? Or are we not allowed to use a global pin? Or am I missing some other thing here? This is my first attempt to use RSACryptoServiceProvider and I'm probably missing some fundamentals.
Any suggestions would be appreciated.
You seem to assume, that PINs are somewhat exchangeable in the sense, that a global PIN is an appropriate substitute for an application-specific one. This is not true.
While a card could have been set up this way (accepting global PIN say #1 OR application specific PIN #2), your card is obviously not. If the card does not offer the choice, the service provider can't succeed. Even if the service provider uses the PIN you want and the comparison succeeds, the card will not allow usage of the private key.
And no, there is no way that you change the ID of the PIN required for signature according to your liking, since this path would then also be available for an attacker.
(All of this answers assume, that you are not allowed to modify the card content, e. g. you are a normal card holder.)

Grant user permission to the private key

installing my WCF service I also install certificate with private key.
Since I will be running service as a different user, that user needs access to the private key. I extensively read other stackoverflow questions and they all suggest permission on private key file in file system needs to adjusted.
I do this by,
private static void AddUserPermissions(X509Certificate2 certificate, NTAccount user, StoreLocation storeLocation)
{
RSACryptoServiceProvider rsaProvider = (RSACryptoServiceProvider)certificate.PrivateKey;
// Find file
string keyPath = FindKeyLocation(rsaProvider.CspKeyContainerInfo.UniqueKeyContainerName, storeLocation);
FileInfo keyFileInfo = new FileInfo(keyPath);
// Create new FileSecurity
FileSecurity keyFileSecurity = keyFileInfo.GetAccessControl();
keyFileSecurity.AddAccessRule(new FileSystemAccessRule(user, FileSystemRights.Read, AccessControlType.Allow));
// Apply file security to the file
keyFileInfo.SetAccessControl(keyFileSecurity);
}
When I run my program and inspect the private key file I can see, for example "Network Service" has been added to the permissions list.
Great, that's working, but when WCF tries to use private key, it cannot access it.
Looking at certlm, certificate -> All Tasks -> Manage Private Keys..
I can see that my user is not on the list. Adding my user through GUI solves the issue, but I need to do it in code!!
Crypto Service Provider (Microsoft RSA SChannel Cryptographic Provider)
The keys are located in C:\ProgramData\Application Data\Microsoft\Crypto\RSA\MachineKeys and setting a normal file permission here is reflected in certlm.msc.
Crypto Next Generation (Microsoft Key Storage Provider)
The keys are located in C:\ProgramData\Application Data\Microsoft\Crypto\Keys and setting a normal file permission here is reflected in certlm.msc.
Summary
Ensure you modify the permissions on the right file in the right location.
Anyone who gets this far looking for a solution for ECDSA certificate keys, I found a way!
string keyUniqueName = (certificate.GetECDsaPrivateKey() as ECDsaCng)?.Key.UniqueName
?? (certificate.GetRSAPrivateKey() as RSACng)?.Key.UniqueName
?? throw new NotSupportedException("No ECDSA or RSA key found");
In the example code from the question, the FindKeyLocation gets passed rsaProvider.CspKeyContainerInfo.UniqueKeyContainerName, instead you would pass keyUniqueName as taken from my example.
One more good note, the accepted answer from Daniel Fisher lennybacon correctly documents where keys are stored based on where they were generated/installed. But you should use Environment.SpecialFolder.CommonApplicationData or Environment.SpecialFolder.ApplicationData for key file paths.
Also, I found my keys in c:\ProgramData\Microsoft\Crypto\, not c:\ProgramData\Application Data\Microsoft\Crypto\.

How to sign public PGP key with Bouncy Castle in C#

I want to create web of trust support in my application, allowing my users to use their private keys, to sign other user's public keys - Using C# and Bouncy Castle.
I've got most things figured out, such as creating PGP keys, submitting them to key servers using HTTP REST, encrypting MIME messages and cryptographically signing them (using MimeKit) - But the one remaining hurdle, is to figure out some piece of code that can use my private key, to sign for another person's public key, using Bouncy Castle.
Since the documentation for BC is horrendous, figuring out these parts, have previously proven close to impossible ...
For the record, I'm using GnuPG as my storage for keys.
If anybody wants to look at my code so far for what I have done, feel free to check it out here.
I am probably not supposed to ask this here, but I'd also love it if some BC gurus out there could have a general look at my code so far, and check if I've made a fool of myself with the stuff I've done so far ...
Found the answer after a lot of trial and error, here it is ...
private static byte[] SignPublicKey(
PgpSecretKey secretKey,
string password,
PgpPublicKey keyToBeSigned,
bool isCertain)
{
// Extracting private key, and getting ready to create a signature.
PgpPrivateKey pgpPrivKey = secretKey.ExtractPrivateKey (password.ToCharArray());
PgpSignatureGenerator sGen = new PgpSignatureGenerator (secretKey.PublicKey.Algorithm, HashAlgorithmTag.Sha1);
sGen.InitSign (isCertain ? PgpSignature.PositiveCertification : PgpSignature.CasualCertification, pgpPrivKey);
// Creating a stream to wrap the results of operation.
Stream os = new MemoryStream();
BcpgOutputStream bOut = new BcpgOutputStream (os);
sGen.GenerateOnePassVersion (false).Encode (bOut);
// Creating a generator.
PgpSignatureSubpacketGenerator spGen = new PgpSignatureSubpacketGenerator();
PgpSignatureSubpacketVector packetVector = spGen.Generate();
sGen.SetHashedSubpackets (packetVector);
bOut.Flush();
// Returning the signed public key.
return PgpPublicKey.AddCertification (keyToBeSigned, sGen.Generate()).GetEncoded();
}

Extract the shared secret from class ECDiffieHellmanCng

I am currently developing an SSH client and it is necessary that said client is able to exchange keys with the server via ECDH KEX (NIST-256, 384 and 521).
I did some (actually a lot) of research, found the .NET class ECDiffieHellmanCng, and was able to extract and import the public key of the server into the class.
The problem, however, is that I can't extract the shared secret without deriving it (ECDiffieHellmanCng.DeriveKeyMaterial(CngKey otherpartyPublicKey)).
Is there a way to directly access the shared secret ("k" as it's called in the RFC papers)?
Here is page 7 from the RFC of the ECDH implementation and why I need the shared secret:
The exchange hash H is computed as the hash of the concatenation of
the following.
string V_C, client's identification string (CR and LF excluded)
string V_S, server's identification string (CR and LF excluded)
string I_C, payload of the client's SSH_MSG_KEXINIT
string I_S, payload of the server's SSH_MSG_KEXINIT
string K_S, server's public host key
string Q_C, client's ephemeral public key octet string
string Q_S, server's ephemeral public key octet string
mpint K, shared secret <-- this is why I need the pure secret
before any derivation
Thanks for any solutions or hints!
You don't actually need k, then, you just need to compute H. The ECDiffieHellman class allows you to do that.
byte[] prepend = Concat(V_C, V_S, I_C, I_S, K_S, Q_C, Q_S);
byte[] exchangeHash = ecdh.DeriveKeyFromHash(otherPublic, new HashAlgorithmName("whatever your hash algorithm is"), prepend, null);
Though that is using .NET 4.6.2 (currently in preview) API: DeriveKeyFromHash
If you are on an older framework it's still possible, but requires using the ECDiffieHellmanCng type specifically:
ecdhCng.SecretPrepend = prepend;
ecdhCng.SecretAppend = null;
ecdhCng.HashAlgorithm = new CngAlgorithm("whatever your hash algorithm is");
ecdhCng.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
byte[] exchangeHash = ecdhCng.DeriveKeyMaterial(otherPublic);
Even after a lot of research i couldn't find a way to do it so the answer is no - you can not extract the secret.
My solution for the big picture was to discard the ECDiffieHellmanCng class altogether and instead wrap the OpenSSH library in C#.
Hope this at least helps someone else with the same idea.

DSA and Public Key Exchange

I am trying to implement a licensing solution with DSA Algorithms for my application. Now here is what I have done:
Generated a hardware key, taken its hash.
Generated public and private keys. And encrypted my hash function with private key.
I forward this encrypted value back to client along with the public key.
At client's system, I use the DSASignatureDeformatter's VerifySignature function to validate my encrypted key, and my hardware key. If equal I validate the client.
Now my problem is that how to send the public key over the network. I tried to store and forward various DSAParameters values e.g., J, G, P in a file but since the sizes of keys change, that is not viable. Please see if anyone can guide.
Updated:
When I try to do this at the client's machine
DSAParameters KeyInfo;
using (DSACryptoServiceProvider DSA = new DSACryptoServiceProvider())
{
// Import the key information.
KeyInfo = DSA.ExportParameters(false);
}
The key size it generates for its various members is different from the public key parameters I have sent it back from server.
Okay. A bit late. But maybe other ones will have the same question.
You should just export your key like this:
string publicKey = DSA.ToXmlString(false);
so you can import it like this:
using (DSACryptoServiceProvider dsa = new DSACryptoServiceProvider())
{
dsa.FromXmlString(publicKey);
return dsa.VerifySignature()
}

Categories

Resources