I want du encrypt and sign a mail with MimeKit.
Generating the Message works:
CmsRecipient CmsRecipient = new CmsRecipient("mail.cer");
CmsRecipient.EncryptionAlgorithms = new EncryptionAlgorithm[] { EncryptionAlgorithm.Aes192 };
to1.Add(CmsRecipient);
var signed = MultipartSigned.Create(ctx, signer, multipart);
var encrypted = ApplicationPkcs7Mime.Encrypt(ctx, to1, signed);
message.Body = MultipartSigned.Create(ctx, signer, encrypted);
In Outlook they arrive encrypted with: 168 Bit 3DES
But I need AES 265. How can I manage this?
I can set signing to RSA/SHA512 with
signer.DigestAlgorithm = DigestAlgorithm.Sha512;
but not the encryption.
Edit: Updated to my Solution
The CmsSigner specifies the DigestAlgorithm, but it's the CmsRecipient that specifies which encryption algorithm(s) that the recipient supports.
The way that the encryption algorithm is chosen by MimeKit is that it figures out the best encryption algorithm supported by each recipient and then uses that.
In other words, if you are encrypting to 5 recipients and all of them support AES256, then MimeKit will use AES256. But if one of the recipients only supports, for example, 3DES (and all of the other recipients also support 3DES as well as AES256), then 3DES is chosen instead.
Related
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.
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.
I am currently working on a program with Google's Directory API to reset the password of someone in my domain. I have it all working, but I would like to send an encrypted password to Google instead of plaintext.
Since the API seems limited in what I can use to encrypt, I'm trying to use SHA-1 to do so. The problem is, when I encrypt it in SHA-1, Google doesn't accept it.
Here is my original code of what I was sending to Google:
//create a template of the user to update
var body = new Google.Apis.Admin.Directory.directory_v1.Data.User();
//Encrypt the password using SHA1
byte[] bytes = System.Text.Encoding.Unicode.GetBytes(model.NewPassword);
SHA1 sha = new SHA1CryptoServiceProvider();
byte[] password = sha.ComputeHash(bytes);
//Put the password and hash function into the request body
body.HashFunction = "SHA-1";
body.Password = password.ToString();
//send the request
var request = users.Update(body, email);
request.execute();
When I run this, it throws an error saying the password is invalid.
when I change it so that it is sending strictly hex, like so
//create a template of the user to update
var body = new Google.Apis.Admin.Directory.directory_v1.Data.User();
//Encrypt the password using SHA1
byte[] bytes = System.Text.Encoding.Unicode.GetBytes(model.NewPassword);
SHA1 sha = new SHA1CryptoServiceProvider();
byte[] password = sha.ComputeHash(bytes);
//Convert the hashcode to Hex
System.Text.StringBuilder builder = new System.Text.StringBuilder();
for (int i = 0; i < password.Length; i++)
{
builder.Append(password[i].ToString("x2"));
}
//Put the password and hash function into the request
body.HashFunction = "SHA-1";
body.Password = builder.ToString();
//send the request
var request = users.Update(body, email);
request.execute();
Then Google accepts what I've given it, but going into the account, I can't access it, because the password was changed to something completely different.
Am I just encrypting the password incorrectly, or is there something I'm missing?
(Disclaimer: I work for Google, but I haven't looked at this API before.)
Well, the problem when you call password.ToString() is that it's not providing the hex representation - so that's why the first piece of code fails. It looks like it's basically expecting it to be hex. The documentation states:
We recommend sending the password property value as a base 16 bit encoded hash value. If a hashFunction is specified, the password must be a valid hash key.
Now, I suspect the problem with the second piece of code is the way you're converting the original text password to bytes. You're using:
Encoding.Unicode.GetBytes(model.NewPassword)
That's using little-endian UTF-16. While the documentation doesn't state the expected encoding, using UTF-8 would be more common. So I would suggest using
Encoding.UTF8.GetBytes(model.NewPassword)
instead... and then hashing the result and hex-encoding that hash as before.
That's only an educated guess, but it's worth a try.
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 want my application to encrypt a user password, and at one time password will be decrypted to be sent to the server for authentication. A friend advise me to use HMAC. I wrote the following code in C#:
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
byte[] key = encoding.GetBytes("secret");
HMACSHA256 myhmacsha256 = new HMACSHA256(key);
byte[] hashValue = myhmacsha256.ComputeHash(encoding.GetBytes("text"));
string resultSTR = Convert.ToBase64String(hashValue);
myhmacsha256.Clear();
How to decode the password (resultSTR, in this case)?
An HMAC (Hashed Message Authentication Code) is not encryption, it's hash function (in this case SHA-256) plus some secret key. It's lossy, there is no way to derive the plaintext from the HMAC.
If you want to encrypt some secret data, you should consider using the ProtectedData class instead. More infom including sample code at http://msdn.microsoft.com/en-us/library/system.security.cryptography.protecteddata.aspx