I've been struggeling with this for days now and RFC 2315 is a bit hard to understand.
I'm trying to implement my own version of EnvelopedCms.Decrypt(), so that I can use the certificate operations of Azure Key Vault to UnwrapKey and/or Decrypt a PKCS#7 message (CMS Object) in a correct way. I use EnevelopedCms in .Net to Decode the message, then I try to Decrypt the EnvelopedCms.ContentInfo.Content.
This is what I try to do;
public static async Task<byte[]> DecryptCustom(string certificateId, string encryptedBase64Content)
{
var bytes = Convert.FromBase64String(encryptedBase64Content);
var contentInfo = new ContentInfo(bytes);
var envelopedCms = new EnvelopedCms(contentInfo);
envelopedCms.Decode(bytes);
// envelopedCms.Decrypt() <-- no go. Can't extract certificate from Key Vault
// My (naive) attempt to decrypt CMS content using Azure Key Vault certificates
byte[] decryptedContent;
using (var client = new KeyVaultClient(GetKeyVaultToken))
{
var decryptionresult = await client.DecryptAsync(GetKeyUrl(certificateId), "RSA1_5", envelopedCms.ContentInfo.Content);
decryptedContent = decryptionresult.Result;
}
return decryptedContent;
}
I was hoping it could be that easy, but it gives me the following error;
Unable to decrypt specified value with this key.
I read something about octets in RFC 2315, so maybe the stream (byte-array) needs some reordering before I decrypt. Do I need to unwrap some symmetric key to decrypt the real payload? I'm on thin ice here.
I'm not a cryptography professional so I might have missed something obvious, too. I was hoping someone knew what to do in this case as I really want to keep my certificates inside the Key Vault (HSM)
CMS envelope contents are encrypted using a session key, and this key is encrypted with each recipients (there can be many) public key before transmission.
What you need is to extract your recipient's encrypted session key, and unwrap it with the private key stored in key vault. I'm not near Visual Studio right now, but here is the pseudocode:
// Extract the first (and often only) receiver's encrypted session key
var key = envelopedCms.Receivers[0].EncryptionKey;
// Unwrap the sessionKey using the receiver's private key stored in key vault:
var sessionKey = (await keyVaultClient.Unwrap(uri, "certificatename", key)).Result;
Finally, using the sessionKey, you can decrypt the envelope contents (ContentInfo.Content). The encryption type is specified in the envelope's encryption algorithm-property.
Related
Recently I came across this c# code:
var dn = new X500DistinguishedName($"CN={_appSettings.CommonName};OU={_appSettings.OrganizationalUnit}", X500DistinguishedNameFlags.UseSemicolons);
SubjectAlternativeNameBuilder sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddUri(new Uri($"urn:{_appSettings.ApplicationUri}"));
using (RSA rsa = RSA.Create(2048))
{
var request = new CertificateRequest(dn, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(sanBuilder.Build());
var selfSignedCert = request.CreateSelfSigned(new DateTimeOffset(DateTime.UtcNow.AddDays(-1)), new DateTimeOffset(DateTime.UtcNow.AddDays(3650)));
...
}
...
Having a look closer at the CertificateRequest constructor parameters, the rsa key is described as:
A RSA key whose public key material will be included in the certificate or certificate request. If the CreateSelfSigned(DateTimeOffset, DateTimeOffset) method is called, this key is used as a private key.
The bold part is the one I don't really understand. Does that mean that when self signing the certificate, the certificate is signed using the given RSA key AND adds the same key as public key to the certificate?
In my understanding for TLS, we have two public-key pairs, one for signing and one for encryption. The CA signs a certificate with its private key and offers a public key to the clients to verify the signature by decrypting it with the public key, whereas the provider of a service offers a public key which the clients use to encrypt their keys first in the tls handshake which after that gets decrypted with the service providers private key.
However, in the above code sample, we create a certificate that contains what exactly? Server public key is for encryption, but what key for decryption of the signature?
Need an assist on an issue I am having with an RSA key. I generate a RSA key through C# code on a server with a key container name I define such as "CustKey". I get back the key XML string just fine and I can connect an application to it using that key. I have an API that is generating the key through an endpoint. If I redeploy the API my application that uses the public key stops working on decoding it. I have to run again the same code to create the key and the encrypted string is exactly the same every time I regenerate it and then the consuming app works fine again. It seems to happen when either the app pool recycles but I have it turned off recycling but the publish I stop and start the IIS API site. Any idea how to keep the key working and why I have to regenerate it but it uses the same key encrypted value each time.
Thanks for any info.
Some examples of your code would greatly help to understand what's going on. I can only speculate at this point that you're trying to decode your message with public key instead of private one.
Firstly, you have to have two paired keys:
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
string publicKey = rsa.ToXmlString(false);
string privateKey = rsa.ToXmlString(true);
Secondly, you have to give your public key to all people who wants to send a secret message to you:
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(publicKey);
string secretMessage = Convert.ToBase64String(rsa.Encrypt(Encoding.UTF8.GetBytes(openMessage), false));
And finally, when you recieve a secret message only you can decode it because only you have a private key which is paired to public one:
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(privateKey);
string revealedMessage = Encoding.UTF8.GetString(rsa.Decrypt(Convert.FromBase64String(secretMessage), false));
I've got some sensitive information that I want to be encrypted and stored in Azure Table Storage. Honestly, from a naive approach, using the same AES key for all values probably would be sufficient as I would nowhere near approach having enough data encrypted in order for someone to do any meaningful cryptanalysis. But, I know that the best practice is to limit usage of the same symmetric key.
Recently, Microsoft released client side encryption for Azure Table Storage via Azure KeyVault. It allows you to generate an RSA key and store it in KeyVault and the client library will generate a new symmetric key for every row in table storage and it encrypts the symmetric key with your RSA key. This is perfect because there is no way to do differential cryptanalysis on the ciphertext since they all used different keys. It is especially nice because their library does all the plumbing, all you have to do is grab your RSA key from KeyVault, decorate your designated properties with the EncryptPropertyAttribute and it handles everything else.
Therein lies the rub... I personally find KeyVault kind of obtuse to use and manage. You have to use powershell to set up oauth authentication between your app and keyvault and it looks like a tremendous amount of overhead for storing a single RSA key. If we have hundreds of keys to store, I can imagine it would be much more useful.
Is there any way to use all of Microsoft's client side encryption code without storing the RsaKey in the KeyVault?
It took me a while to find it, but yes, you can store your RSA key outside of KeyVault. You just need to use the RsaKey constructor overload that takes in an RSACryptoServiceProvider that you grab from wherever you deem prudent. I grab mine out of my web.config. However, I make sure that my production RsaCsp is not store in source control and I add it directly in the Azure Web App configuration screen.
IKey tableStorageKey = GetTableStorageKey()
_tableRequestOptions = new TableRequestOptions
{
EncryptionPolicy = new TableEncryptionPolicy(tableStorageKey, null)
};
...
private IKey GetTableStorageKey()
{
using (var rsaCsp = new RSACryptoServiceProvider(2048))
{
try
{
//it doesn't really matter where you get your RsaCsp from, I have mine in my webconfig
XmlDocument doc = new XmlDocument();
doc.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
XmlElement node = doc.SelectSingleNode("/configuration/MyTableStorageRsaKey") as XmlElement;
rsaCsp.FromXmlString(node.OuterXml);
return new RsaKey("MyTableStorageRsaKey", rsaCsp);
}
finally
{
rsaCsp.PersistKeyInCsp = false;
}
}
}
In Microsoft.Azure.KeyVault.Cryptography, there is a change in RsaKey constructor.
Now it does not import key from RSACryptoServiceProvider, but uses it directly, and disposes it in Dispose() method. So usage will change to:
public RsaKey GetFromXmlString(string xmlString)
{
try
{
var rsaCsp = new RSACryptoServiceProvider(2048, new CspParameters() { KeyContainerName = "MyTableStorageRsaKey" });
rsaCsp.FromXmlString(xmlString);
rsaCsp.PersistKeyInCsp = false;
return new RsaKey("MyTableStorageRsaKey", rsaCsp);
}
catch (Exception ex)
{
throw new InvalidOperationException("Invalid rsa key xmlString provided", ex);
}
}
Notice that instance of RSACryptoServiceProvider is not disposed.
Also notice that RsaKey is IDisposable.
So, I am creating a simple "web chat", using TcpClient and TcpListener class. I want all data sent to be encrpyted, and I am using AES encryption. So first I have to make sure AES key from server is securely sent to client. I am trying to achieve this, by encrypting AES key with RSA and then sending it to client and there decrypting it with RSA again.
So first of all I created an RSACryptoServiceProvider on server and extracted public key. I sent public key to client and there created RSACryptoServiceProvider and imported that key. When I call Decrpyt method I get an key does not exist exception. This is my code:
Server:
This is sending public key to client.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
string privateXml = rsa.ToXmlString(true);
string publicXml = rsa.ToXmlString(false);
Byte[] pubKey = Encoding.UTF8.GetBytes(publicXml);
clientStream.Write(pubKey, 0, pubKey.Length);
AesCryptoServiceProvider aes = new AesCryptoServiceProvider(); // simetrično kriptiranje
byte[] aesKey = aes.Key;
byte[] encryptedRSA = rsa.Encrypt(aesKey, false);
clientStream.Write(encryptedRSA, 0, encryptedRSA.Length);
Client:
Byte[] serverPublicKey = new Byte[1024];
Int32 bytes1 = stream.Read(serverPublicKey, 0, serverPublicKey.Length);
string serverKey = Encoding.UTF8.GetString(serverPublicKey, 0, bytes1);
serverKey = serverKey.Replace("\0", "");
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(serverKey);
Byte[] bytes2 = new Byte[128];
String aesKey = null;
stream.Read(bytes2, 0, bytes2.Length);
byte[] decryptedKey = rsa.Decrypt(bytes2, false);
Sorry this wasn't small enough to fit in a comment.
You've sent the public key to the client. This will only allow the client to encrypt data to be sent to the server. To decrypt data the client would need a private key (hence your exception)
Sending your public key to someone does not allow you to send them encrypted messages, it allows them to send you encrypted messages securely, so in your example only the client can send an encrypted message.
In your scenario this would mean that the client would need to generate an AES key, encrypt it using the public key it has been been sent and then the server can decrypt it and use the AES key. HOWEVER I would not recommend this as it has many flaws including being very susceptible to a man in the middle attack. This is because we have no way to verify the public key we receive is the one that belongs to the server (some one else could be intercepting and modifying the tcp stream to be inserting their own key pair and thereby gaining access to the AES key and being able to snoop on the rest of the communication).
You should consider looking into using the SslStream class http://msdn.microsoft.com/en-us/library/system.net.security.sslstream(v=vs.100).aspx
If you wanted to carry on as you have been then you would need to let the client generate the key and have some mechanism to verify the public key received.
The usually way of verifying public keys is by using certificates (i.e. You have a third party (certificate authority) that both the server and client trusts and that third party has signed the public key to say it does actually belong to the server)
If you don't want to get a certificate that is signed by a trusted certificate authority then you could use a self signed certificate but there is not much benefit over just hardcoding the public key into the client application as you would have to hardcode the certificate thumbprint of the self signed certificate anyway.
I was recently assigned to task to encrypt some data and send it to our server. I found some valuable resources on the internet but they all require a public key, private key and secret password for the encryption. Please is there any simple way I can encrypt with just pgp key, since that's what I was given?
I assume that you have been given a public OpenPGP key. This one is enough to do encryption of data, which is intended to be decrypted by the person who gave you his public key.
In .NET you can use BouncyCastle or OpenPGPBlackbox package of our SecureBlackbox product. SecureBlackbox comes with extensive samples and support is offered as well (unlike alternatives).
Public PGP keys can encrypt data and verify signatures. Private PGP keys can decrypt data and sign data. If you have someone's public key, just use it, it won't prompt you for a password.
PGP simply works with pairs of private and public keys. The secret password is optional as far as i know.
The standard PGP encryption process in any language works as follows:
Step 1: Generate your private / public key pair
First generate your OpenPGP key pair e.g. with gnupg. The pair consists of a public key, which is used by the sender to encrypt the data and the private key, which is used by the recipient to decrypt the data.
Step 2: Share your public key and collect public keys of other parties
Each side will need to have the public keys of all the other parties. To do this step, you can give your public key using an usb stick or you upload it to a public key server.
Step 3: Encrypt and send your data
You write your data and encrypt it for the recipients.
You might also sign the data, which guarantees that the recipient can verify that the data has been created by you. After the encryption you send the data to the recipients.
Step 4: Authentication of data
You don't have to do this step but another benefit of asymmetric encryption such as PGP is that it allows for authentication. After you have exchanged public keys with your partners, the private keys can be used to digitally sign the encrypted content, allowing the decrypting site to verify the authenticity of the sender.
After data encryption is completed with the private key, you encrypt the session key with the public key that's known to the recipient (and maybe other parties as well). After that you can optionally create a hash of the encrypted data and sign this hash with your private key, this is called a signature.
Save the data in, for example, OpenPGP format.
Step 5: Decrypt data and verify signature
If you receive a data you decrypt it and if the data is signed, you verify the signature to be sure the data is sent by the sender to whom you have the public key.
Recently, I'm doing the PGP Encryption and sending files over to SFTP server. Here's the simple steps I follow with Python:
pip install py-pgp
Keep public_key in the same directory
Get recipients info gpg --list-keys
Script:
import os, gnupg
key = '<public_key>.asc'
src = '<file_to_be_encrypted>'
dst = './' #destination, it could be current directory
def encrypt(key, src):
home = os.path.join(os.getcwd(), '')
gpg = gnupg.GPG(gnupghome=home)
with open(key, "rb") as f:
keys = gpg.import_keys(f.read())
with open(src, "rb") as f:
result = gpg.encrypt_file(f, recipients='<name_retrieved_from_public_key>', output='<file_name>.pgp', always_trust=True)
if not result:
raise RuntimeError(result.status)
encrypt(key, src)
This will provide you the Encrypted file within the same directory.