Encryption with PGP key - c#

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.

Related

Using public key in asymmetric encryption with X.509 certificates

Sometimes ago I've read an article about using asymmetric keys like public and private keys to send data securely. What I've understood was that the server has 1 key (private key) that it use to encrypt data and all clients use the second key (public key) to decrypt it.
Now how should I expect to receive the key and how should I work with?
If I receive a Certificate from the server, wouldn't it contain both public and private keys?!
X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
var cert = GetCertificate(certStore);
var privatekey= cert.PrivateKey;
var publicKey= cert.PublicKey;
Is it possbile to remove the private key from the certificate? How? and how can I understand if the certificate has the private key?
First a little bit of clarification:
In Public-key cryptography the public key is used to encrypt the data and the private key is used (by the server) to decrypt the data.
The owner of the private key stores private key and only shares a public key.
Any certificate from a server should contain only a public key.
(It would be a big security issue if the certificate contains the private Key. You could decrypt the messages from any other user)
To check if the cerificate has a private key you can use the HasPrivateKey-Property
cert.HasPrivateKey;
And to get a certificate with only the public key you can use:
byte[] bytes = cert.Export(X509ContentType.Cert);
var publicCert = new X509Certificate2(bytes);
If I receive a Certificate from the server, wouldn't it contain both public and private keys?!
No, not necessarily.
There are two different ways of obtaining the certificate
PKCS#7/PEM - the file usually contains only the public part of the certificate
PKCS#12/PFX - the store contains certificates with private keys
Is it possbile to remove the private key from the certificate?
By exporting it to a format that lets you store only the public part of the certificate.
Open a web browser, navigate to any site that uses SSL.
Now click the lock icon and, from there, the certificate information. What you have at the client side (in the browser) is the certificate without the private key. You can save it and even import into the system cert store but still without the private key.
and how can I understand if the certificate has the private key?
If you load it with your C# code, accessing the HasPrivateKey property will be true only for certs with private keys available.

Using RSASSA-PSS and RSAES-OAEP with MailKit

I have to exchange encrypted & signed e-mails with some business partners. Specific algorithms are required, such as :
for signature, RSASSA-PSS as the signature algorithm,
for encryption, RSAES-OAEP for key encryption & AES-128 CBC for content encryption
I am having troubles setting this up with Mailkit, and actually behind it MailKit & BouncyCastle.
Here is where I am so far :
For decryption & signature verification
Decrypting the body is ok, I do it by using a WindowsSecureMimeContext, after setting up my private key in the windows store
Verifying the signature is not ok
case MultipartSigned signedBody:
try
{
using (var ctx = new WindowsSecureMimeContext(StoreLocation.LocalMachine))
{
var verifiedData = signedBody.Verify(ctx);
return verifiedData.All(o => o.Verify());
}
}
catch (Exception e)
{
throw new Exception("Error during signature verification.", e);
}
Certificate of the sender is signed by a common CA, so I'm using again a WindowsSecureMimeContext, but verifiedData.All(o => o.Verify()) throws a DigitalSignatureVerifyException ("Failed to verify digital signature: Unknown error "-1073700864".")
For signature and encryption
Well, that looks tough...
For signature, it seems that I need somewhere a BouncyCastle's PssSigner, which I can get by overriding DkimSigner, and especially the DigestSigner property
class TestSigner : DkimSigner
{
protected TestSigner(string domain, string selector, DkimSignatureAlgorithm algorithm = DkimSignatureAlgorithm.RsaSha256)
: base(domain, selector, algorithm)
{
}
public TestSigner(AsymmetricKeyParameter key, string domain, string selector, DkimSignatureAlgorithm algorithm = DkimSignatureAlgorithm.RsaSha256)
: base(key, domain, selector, algorithm)
{
}
public TestSigner(string fileName, string domain, string selector, DkimSignatureAlgorithm algorithm = DkimSignatureAlgorithm.RsaSha256)
: base(fileName, domain, selector, algorithm)
{
}
public TestSigner(Stream stream, string domain, string selector, DkimSignatureAlgorithm algorithm = DkimSignatureAlgorithm.RsaSha256)
: base(stream, domain, selector, algorithm)
{
}
public override ISigner DigestSigner => SignerUtilities.GetSigner(PkcsObjectIdentifiers.IdRsassaPss);
}
However I don't know exactly where to use it. Maybe when using MimeMessage.Sign(), however I am a bit lost with the required parameters in the signature of the method
For encryption, I could find my way up to a RsaesOaepParameters in BouncyCastle's library, by I can't figure out how to use it.
Any help by a mail expert would be much appreciated !
A DkimSigner is used for generating DKIM signatures which is not what you want to do. DKIM signatures have nothing to do with S/MIME.
S/MIME Signing using RSASSA-PSS
Currently, the WindowsSecureMimeContext (which uses System.Security as the backend) does NOT support RSASSA-PSS, so you'll need to use the Bouncy Castle backend.
To use the Bouncy Castle backend, you will need to use one of the BouncyCastleSecureMimeContext derivatives (or create your own). As a temporary solution for playing around with things, I might suggest using the TemporarySecureMimeContext, but for long-term use, I would suggest looking at the DefaultSecureMimeContext - although you will still probably want to subclass that to get it working.
Now that you are using a Bouncy Castle S/MIME context, in order to specify that you want to use RSASSA-PSS padding, you'll need to use the APIs that take a CmsSigner parameter such as MultipartSigned.Create() or ApplicationPkcs7Mime.Sign().
Here's an example code snippet:
var signer = new CmsSigner ("certificate.pfx", "password");
// Specify that we want to use RSASSA-PSS
signer.RsaSignaturePaddingScheme = RsaSignaturePaddingScheme.Pss;
// Sign the message body
var signed = MultipartSigned.Create (ctx, signer, message.Body);
// replace the message body with the signed body
message.Body = signed;
S/MIME Encryption Using AES-128 CBC (or any other specific algorithm) with RSAES-OAEP
First, to encrypt using S/MIME, you'll want to use one of the ApplicationPkcs7Mime.Encrypt() methods.[2]
The Encrypt() methods that take a MailboxAddress will automatically create the CmsRecipients and CmsRecipientCollection for you by doing certificate lookups based on the email address provided (or, if any of those mailboxes are actually a SecureMailboxAddress, the Fingerprint is used instead, which is useful if that user has more than 1 certificate in your database or you want to be extra sure that MimeKit picks the right one).
The other thing that MimeKit will do for you when you feed it a list of MailboxAddresses, is that it will look up the supported encryption algorithms that are stored in the database for said user.
For the WindowsSecureMimeContext, this involves looking at the S/MIME Capabilities X509 Certificate Extension attribute and decoding the supported encryption algorithms. In my experience, however, many times this extension is not present on X509 Certificates in the Windows certificate store and so MimeKit will have to assume that only 3DES CBC is supported.
For the DefaultSecureMimeContext, if you have verified any S/MIME signed message by said recipient, then that user's certificate (chain) and advertised encryption algorithms will be stored in MimeKit's custom SQL database (when you sign a message using S/MIME, it's fairly common practice for clients to include the S/MIME Capabilities attribute in the S/MIME signature data).
Now that you understand how that works, if you want to force the use of AES-128 CBC, the way to do that is to manually construct the CmsRecipientCollection yourself.
Naturally, this involves creating a new CmsRecipient for each recipient. To create this class, all you really need is the X509 certificate for that recipient.
var recipient = new CmsRecipient (certificate);
Since you want to force the use of AES-128 CBC, now you just need to override the encryption algorithms that this recipient supports:
recipient.EncryptionAlgorithms = new EncryptionAlgorithm[] {
EncryptionAlgorithm.Aes128
};
(By default, the EncryptionAlgorithms property will be set to the algorithms listed in the certificate's S/MIME Capabilities Extension attribute (in preferential order), if present, otherwise it'll just contain 3DES CBC).
If you also want to force RSAES-OAEP, you'll need to set:
recipient.RsaEncryptionPadding = RsaEncryptionPadding.OaepSha1;
Add each CmsRecipient to your CmsRecipientCollection and then pass that off to your preferred Encrypt() method and whallah, it will be encrypted using AES-128 CBC.
Notes:
MultipartSigned.Create() will produce a multipart/signed MIME part while ApplicationPkcs7Mime.Sign() will create an application/pkcs7-mime MIME part. Whichever one you want to use is up to you to decide, just keep in mind that your choice may impact compatibility with whatever client your recipients are using (I think most clients support both forms, but you might want to check to make sure).
If you've registered your custom SecureMimeContext class with MimeKit (as briefly described in the README), then you can feel free to use the various Encrypt/Decrypt/Sign/Verify/etc methods that do not take a cryptography context argument as MimeKit will instantiate the default context for you. Otherwise you will need to pass them a context.

EnvelopedCms decryption does not work with Azure Key Vault

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.

How to work with (and create) X509 Certificates for private/public key encryption of JWT Tokens

I am trying to figure out a way of authentication between two distributed services.
I don't want to have a shared secret distributed on every service host, because it would mean that once one host has been compromised, all hosts are compromised.
So my scenario is:
Host A knows the public key of Host B
Host A encodes and encryptes the jwt using Host B´s public key
Host B receives and decrypts the jwt using its private key, that it only knows itself.
The jose-jwt package:
https://github.com/dvsekhvalnov/jose-jwt
seems like a good option to me. Beside the signing of the jwt, it also supports encryption using private/public keys.
On the page there are the following examples for encoding and decoding a jwt:
Encode:
var publicKey=new X509Certificate2("my-key.p12", "password").PublicKey.Key as RSACryptoServiceProvider;
string token = Jose.JWT.Encode(payload, publicKey, JweAlgorithm.RSA_OAEP, JweEncryption.A256GCM);
Decode:
var privateKey=new X509Certificate2("my-key.p12", "password", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet).PrivateKey as RSACryptoServiceProvider;
string json = Jose.JWT.Decode(token,privateKey);
Now, here is what i don´t understand:
How can I create a .p12 certificate file that only contains the public key information (for the host/service A that encodes the jwt) ?
.. and how can I create a .p12 certificate file that contains both, the public and the private key information (for the host/service B that decodes the jwt) ?
From all the research that I have done, i get the impression that you can either only make a .p12 file that contains both, or one that contains only the public key. But it seems there is no way to create two .p12 files, one with both information and one with only the public key. What am I missing?
Thanks for your answers.
Normally a PKCS12/PFX is not used for public-only, but you can do it if you like.
Assuming that cert.HasPrivateKey is true: cert.Export(X509ContentType.Pkcs12, somePassword) will produce a byte[] that you can write to "publicAndPrivate.p12" (or whatever).
Normally for a public-only certificate you'll write it down just as the X.509 data, either DER-binary or PEM-DER encoded. .NET doesn't make PEM-DER easy, so we'll stick with DER-binary. You can get that data by either cert.RawData, or cert.Export(X509ContentType.Cert) (both will produce identical results, since this export form has no random data in it). (publicOnly.cer)
If you really want a PKCS12 blob which has just the public certificate:
using (X509Certificate2 publicOnly = new X509Certificate2(publicPrivate.RawData))
{
return publicOnly.Export(X509ContentType.Pkcs12, somePassword);
}
The resulting byte[] could then be publicOnly.p12.

How to get the private key from a separate file?

I have an Apache (xampp/wamp) server that provides a SSL connection on port 443.
It uses two certificate files: server.cert and server.key when the latter conains the private key.
I have another server configured to listen to requests on port 843 (flash policy stuff) and response to a certain request with some text reply written in C# which runs separately.
In order to achieve SSL connectivity, i use a flex object called SecureSocket which allowes that, however, it uses the original servers certificate in order to encrypt the request.
My goal is to teach my 843 C# server to decrypt the sent data and encrypt the reply and for this i'm using the X509Certificate object in C#.
However, since the pub and priv keys are on different files, i'm getting FALSE on the following:
string text = System.IO.File.ReadAllText(#"C:\xampp\apache\conf\ssl.crt\server.crt");
UTF8Encoding encoding = new System.Text.UTF8Encoding();
byte[] byteCert = encoding.GetBytes(text);
X509Certificate2 uberCert = new X509Certificate2();
uberCert.Import(byteCert);
Console.WriteLine("Has privateKey:" + uberCert.HasPrivateKey.ToString());
Console.WriteLine("PrivateKey: \n" + uberCert.PrivateKey);
Obviously, the False on uberCert.HasPrivateKey comes from the fact that the private key is on a different file, so my questions are:
1.How can i read the private key using the X509CErtificate2 object?
2.How can i use the public key in order to decrypt the received message and how to re-encrypt it with the private key (in order to send the encrypted response back) ?
Thanks in advance,
Mike.
I've created a small helper NuGet package (based on opensslkey) to create a X509 certificate based on public key and private (rsa) key.
// Generate with: openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout private.key -out certificate_pub.crt
string certificateText = File.ReadAllText("certificate_pub.crt");
string privateKeyText = File.ReadAllText("private.key");
ICertificateProvider provider = new CertificateFromFileProvider(certificateText, privateKeyText);
X509Certificate2 certificate = provider.Certificate;
// Example: use the PrivateKey from the certificate above for signing a JWT token using Jose.Jwt:
string token = Jose.JWT.Encode(payload, certificate.PrivateKey, JwsAlgorithm.RS256);
See NuGet and Github-project for functionality and code-examples.
The private key is likely PEM encoded PKCS#8 structure.
The Mono project provides code to read this format (among other) in the Mono.Security.dll assembly. This assembly is purely managed and will work on Windows, Linux or OSX.
You can't in straight .NET.
You can either use BouncyCastle (see this answer: How to read a PEM RSA private key from .NET) or use a PKCS12 container instead for the username + password, which you can create using OpenSSL's tools from the PEM files.

Categories

Resources