I am hoping that this question can be met with some guidance for someone who is beginning to work with encryption/decryption in C#. There are existing examples on the web regarding this, but I am truthfully struggling to put it all into practice for my given situation.
If given a text file that has been encrypted using OpenPGP with RSA, what is the best method to decrypt this in C#?
This is what I am attempting:
Using Kleopatra OpenPGP, I am generating a key pair using 2048bit RSA. This generates a private and public key.
I am then encrypting/signing a text file with a few word in it as a test.
In C#, I want to decrypt this text file.
Current code:
byte[] encryptedData = File.ReadAllBytes("C:\\PGP Encryption\\test.txt.gpg"); // The encrypted text file generated by Kleopatra.
using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
{
//Import the RSA Key information. This needs
//to include the private key information.
RSA.ImportParameters(RSAKeyInfo);
//Decrypt the passed byte array and specify OAEP padding.
decryptedData = RSA.Decrypt(DataToDecrypt, DoOAEPPadding);
}
return decryptedData;
Unfortunately, the RSA.Decrypt() call throws an exception that reads "The data to be decrypted exceeds the maximum for this modulus of 128 bytes."
I also do not believe that my private key is actually being loaded, as I'm not explicitly stating where the key is. But I don't see how the RSAParameters object is supposed to get populated otherwise.
If anyone can point me in the right direction to decrypt a file in this way, thank you in advance for your time and information.
It's looks like you need this library (see Decrypt section) https://github.com/mattosaurus/PgpCore
Related
I am getting an error decrypting a message in go that was encrypted in C# (using corresponding public/private keys)
My client is written in C# and my server is written in Go. I generated a private and public key via go's crypto/rsa package (using rsa.GenerateKey(random Reader, bits int)). I then store the public key file generated where the client can access it and the private key where the server can access it. I encrypt on the client with the following code (using bouncy castle):
public static string Encrypt(string plainText)
{
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
PemReader pr = new PemReader(
new StringReader(m_publicKey)
);
RsaKeyParameters keys = (RsaKeyParameters)pr.ReadObject();
// PKCS1 OAEP paddings
OaepEncoding eng = new OaepEncoding(new RsaEngine());
eng.Init(true, keys);
int length = plainTextBytes.Length;
int blockSize = eng.GetInputBlockSize();
List<byte> cipherTextBytes = new List<byte>();
for (int chunkPosition = 0; chunkPosition < length; chunkPosition += blockSize)
{
int chunkSize = Math.Min(blockSize, length - chunkPosition);
cipherTextBytes.AddRange(eng.ProcessBlock(
plainTextBytes, chunkPosition, chunkSize
));
}
return Convert.ToBase64String(cipherTextBytes.ToArray());
}
The go server parses this string from the header and uses the private key to decrypt:
func DecryptWithPrivateKey(ciphertext []byte, priv *rsa.PrivateKey) []byte {
hash := sha512.New()
plaintext, err := rsa.DecryptOAEP(hash, rand.Reader, priv, ciphertext, nil)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
}
return plaintext
}
The decryption function throws crypto/rsa: decryption error. If I try pasting the cipher text directly into go (rather then sending from the client), the same error occurs.
NOTE: in order to get the public key to load, I needed to change the header from:
-----BEGIN RSA PUBLIC KEY-----
...
to
-----BEGIN PUBLIC KEY-----
...
and the same for the footer. I am assuming this is a formatting issue but not sure how to go about solving.
EDIT: it seems that golang OAEP uses sha256 and bouncy castle uses SHA-1. Go's documentation specifies that the hash for encryption and decryption must be the same. This seems likely to be the issue? If it is, how can I change the hashing algorithm used by either go or C#?
Yes, you need to match the hash. In GoLang you've already set it to SHA-512 if I take a look at your code. Using SHA-256 at minimum should probably be preferred, but using SHA-1 is relatively safe as the MGF1 function doesn't rely on the collision resistance of the underlying hash. It's also the default for most runtimes, I don't know why GoLang decided against that.
Probably the best is to set SHA-512 for both runtimes, so here is the necessary constant for .NET.
Note that the underlying story is even more complex as OAEP uses a hash over a label as well as a hash within MGF1 (mask generation function 1, the only one specified). Both need to be specified in advance and generally the same hash function is used, but sometimes it is not.
The label is generally empty and most runtimes don't even allow setting it, so the hash value over the label is basically a hash-function specific constant that doesn't matter for security. The constant just manages to make things incompatible; "More flexible" isn't always a good thing.
I've written 3 functions in C++ using Crypto++ to generate key pairs, encrypt and decrypt a string. Crypto++ side:
//Decode public key
RSA::PublicKey pbKeyDecoded;
StringSource ss2(publicKey, true, new Base64Decoder);
pbKeyDecoded.BERDecode(ss2);
Integer m = Integer((const byte*)plaintext.data(), plaintext.size());
Integer crypted = pbKeyDecoded.ApplyFunction(m);
...
What I do is, generate the key, DER Encode it, and then encode it to Base64. After than, I'm ciphering a plaintext via the public key and save both the private key and the cipher as base64 encoded strings in two separate files.
Now to C#. I'm reading the base64 string, decoding them and load them via AsnParser, which seem to load just fine. Then I call Decrypt. C# side:
AsnKeyParser keyParser = new AsnKeyParser("rsa-public.der");
RSAParameters publicKey = keyParser.ParseRSAPublicKey();
CspParameters csp = new CspParameters;
csp.KeyContainerName = "RSA Test (OK to Delete)";
csp.ProviderType = PROV_RSA_FULL; // 1
csp.KeyNumber = AT_KEYEXCHANGE; // 1
RSACryptoServiceProvider rsp = new RSACryptoServiceProvider(csp);
rsp.PersistKeyInCsp = false;
rsp.ImportParameters(privateKey);
//Causes exception here..
var data = rsp.Decrypt(cipherArr, true);
...
But I'm getting exception error when I try to decrypt it with fOAEP = true: CryptographicException: Error occurred while decoding OAEP padding. If I pass fOAEP = false then I get CryptographicException: The parameter is incorrect.
Why am I getting an exception in C# when attempting to decrypt the Crypto++ cipher text?
... I'm getting exception error when I try to decrypt it: CryptographicException: Error occurred while decoding OAEP padding. That's if I pass true for the fOAEP bool, if I pass false to it I get CryptographicException: The parameter is incorrect.
You are having the same problem as Encrypt and Decrypt a message using raw RSA algorithim in Crypto++? and How to sync Crypto++ RSA with C# RSA crypto service provider? It must be our month for the "Raw RSA" schemes...
On the Crypto++ side of the equation, you are performing raw RSA. You are simply applying the forward function, which is exponentiation, and you are not formatting the message:
//Decode public key
RSA::PublicKey pbKeyDecoded;
StringSource ss2(publicKey, true, new Base64Decoder);
pbKeyDecoded.BERDecode(ss2);
Integer m = Integer((const byte*)plaintext.data(), plaintext.size());
Integer crypted = pbKeyDecoded.ApplyFunction(m);
...
On the C# side of things, you are performing RSA decryption using PKCS #1 with either PKCS #1.5 padding or OAEP padding:
RSACryptoServiceProvider rsp = new RSACryptoServiceProvider(csp);
rsp.PersistKeyInCsp = false;
rsp.ImportParameters(privateKey);
//Causes exception here..
var data = rsp.Decrypt(cipherArr, true);
Its not clear to me if the C# version of your code can perform OAEP padding because its it requires a certain version of the CLR. You may only have PKCS padding available.
I believe you have two choices. First, you can use a standard RSA encryption method in Crypto++. The Crypto++ wiki lists them at RSA Cryptography and RSA Encryption Schemes:
typedef RSAES<PKCS1v15>::Decryptor RSAES_PKCS1v15_Decryptor;
typedef RSAES<PKCS1v15>::Encryptor RSAES_PKCS1v15_Encryptor;
typedef RSAES<OAEP<SHA> >::Decryptor RSAES_OAEP_SHA_Decryptor;
typedef RSAES<OAEP<SHA> >::Encryptor RSAES_OAEP_SHA_Encryptor;
Second, you need to perform Raw RSA in C#. To perform Raw RSA in C#, you will need to get a BigInteger class and apply the inverse function manually.
I would encourage you to use RSA Encryption with OAEP padding. If OAEP is not available, then the second choice would be PKCS padding. Finally, if all you have is Raw RSA, then I would look for another encryption system because Raw RSA is so insecure.
I want to verify a private key signed SHA256 hash using the CryptographicEngine in a UWP application. The hash is created externally and is signed with a private RSA key with passphrase. For this example however, I also generate the unsigned hash. Both hashes are then compared at the end to verify that they are the same.
I have created my private and public keys using OSX command line, specified in this blog.
This gave me two .pem files. My public key has the following structure:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3fasaNKpXDf4B4ObQ76X
qOaSRaedFCAHvsW4G0PzxL/...ETC ETC
-----END PUBLIC KEY-----
Here is my C# code to decrypt the hash:
//HASH THE INPUT STRING
var inputText = "stringtohash";
// put the string in a buffer, UTF-8 encoded...
IBuffer input = CryptographicBuffer.ConvertStringToBinary(inputText,
BinaryStringEncoding.Utf8);
// hash it...
var hasher = HashAlgorithmProvider.OpenAlgorithm("SHA256");
IBuffer hashed = hasher.HashData(input);
// format it...
string ourhash = CryptographicBuffer.EncodeToBase64String(hashed);
Debug.WriteLine(ourhash);
//CONVERT EXTERNAL HASH TO BUFFER
IBuffer data = CryptographicBuffer.DecodeFromBase64String("b18fbf9bc0fc7595af646155e18b71e1aeccf01719f9f293c72217d7b95cc2106edb419078c4c5c1c7f7d106b90198a4f26beb49ff4a714db4bface1f94fff193b8126ce05fe13825144a3dde97f55399846b6fd768f1fb152f1ba71bbf5cde8c1a7e58621a493070256e2444db36c346a88e870906529cf13c072ead50b6a01b2e74c7ef8c5d423e8ea25220f524b563ae2c3345b7837f9cd1a357540b1380c86287b9a240cf67f7518f11418352b665b657c5ffb6cbcb6126ec59e360de6304392b78cf4de79b52d73b8292df6a1e643d0c0f0945aae5949b391e2915772c996f03e6d1879192b7edf0f40c01b875e768358aa47a992070f628418ddf06472");
//CONVERT PUBLIC KEY TO BUFFER
IBuffer publickey = CryptographicBuffer.DecodeFromBase64String("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3fasaNKpXDf4B4ObQ76XqOaSRaedFCAHvsW4G0PzxL / RuAQFz80esZPyyDCps1PAbTKzQ + QblChPo7PJkbsU4HzNN4PIRGh5xum6SRmdvOowrlTUtyxdOkRJoFxmiR / VCea + PUspt26F7PLcK9ao5 + hVzMvPuqdYenqzd01f1t5hQEhFQ9qjB6Es8fpizHd / RSRfZ7n6rVKm9wYfCRLB7GJ7IHhWGuZrx9fjzsbW8eagu06qRhnUuR5oDVjXC8ZeazsRiw50xMuOzkhX9Oo081IYikwCgseJmQhT7vF4lZoyeB4qJpwTCA + glSy1w9N8ZfxyXK8QaT2RsrBrzl0ZCwIDAQAB");
// Open an asymmetric algorithm provider for the specified algorithm.
AsymmetricKeyAlgorithmProvider rsa = AsymmetricKeyAlgorithmProvider.OpenAlgorithm(AsymmetricAlgorithmNames.RsaPkcs1);
// Import Key
CryptographicKey key = rsa.ImportPublicKey(publickey, CryptographicPublicKeyBlobType.X509SubjectPublicKeyInfo);
// Decrypt the Hash using our Key
IBuffer result = CryptographicEngine.Decrypt(key, data, null);
Debug.WriteLine(result.ToString());
//Compare the two hashes
if (data == result) {
//Hash is verified!
}
Unfortunately when reaching the Decrypt method I get a NotImplementedException with error
The method or operation is not implemented
I researched online and I understand what needs to happen in theory but I don't know how to debug this further. What can I try?
Although both called PKCS#1 v1.5 padding, the padding for signature generation and encryption is not identical, see RFC 3447 for more details.
If you look at the RsaPkcs1 property you can see it is aimed at encryption:
Use the string retrieved by this property to set the asymmetric algorithm name when you call the OpenAlgorithm method. The string represents an RSA public key algorithm that uses PKCS1 to pad the plaintext. No hash algorithm is used.
As I don't see any option for "raw RSA", i.e. RSA without padding, it seems you are only able to verify your signature. However, RSA decryption expects an RSA private key. It's very likely that you get the error because of this: if you try and decrypt with a public key it will fail.
If you want to precompute the hash you can use VerifySignatureWithHashInput.
For other functionality you may have to use e.g. the C# lightweight API of Bouncy Castle. In the end you don't need platform provided cryptography to verify a signature.
I'm trying to write a service to SFTP to a server on a given interval, download all files in a directory, and then decrypt them for processing.
The decrypt method was working at one point, and I have no modified it since. I am still using the same keys as when it did successfully work.
I am using the PGPEncrypt, PGPDecrypt, and PGPEncryptionKeys classes for BouncyCastle found here: https://github.com/sledwith/PGP-Decryption-With-C-Sharp
Additionally, I have modified the code as shown here: Exception on decrypting file using BouncyCastle PGP
If you noticed, I commented on how that fixed my code and the decryption worked.
Now, it isn't.
Does anyone have some insight why this might have happened, or how I could go about fixing it?
I am going to make a guess that you are using a 1024 bit RSA public/private key and trying to encrypt 128 bytes of something with it? If I am right, use a larger RSA key (RSA 4096 will allow you to encrypt up to ~500 bytes).
I note in the linked post you say this is sporadic. Some googling indicates that if you have leading zeros in the to-be-encrypted bytes it may be able to handle a full 128 bytes.
Also - if you are just encrypting "data" with the keypair, you need to be certain it will not overrun these limitations (240 or so bytes for RSA 2048 and 500 or so for RSA 4096 accounting for padding). Good practice and future proofing would be to implement a symmetric key encryption of all your data and just use the RSA key to encrypt / decrypt your keys.
If you're not dead-set on using the PGP process explicitly, you might use my library here:
https://github.com/zenith-nz/ObscurCore
Its "banner feature" is not yet fully active (creating encrypted archives of a kind), but it does everything that you want it for, it appears - it does key derivation with a ECDHC scheme, UM1, and implements a large variety of block and stream ciphers. It's built on top of BouncyCastle.
I think I resolved this problem; please try this code.
public string StringToDecrypt(string text)
{
byte[] toDecrypt = Convert.FromBase64String(text);
AsymmetricCipherKeyPair keyPair;
using (var reader = File.OpenText(#"Private Key File Path"))
{
keyPair = (AsymmetricCipherKeyPair) new PemReader(reader).ReadObject();
}
var engine = new RsaEngine();
engine.Init(false, keyPair.Private);
return Encoding.UTF8.GetString(engine.ProcessBlock(toDecrypt, 0, toDecrypt.Length));
}
I have seen question on signing and encrypting final mdm profile here:
iOS MDM profile signing, which certificate to use?
I am using Bouncy Castle library for encryption. Currently I am stuck while encrypting the final profile using the scep identitiy certificate.
I am facing the following issue.
The public key retrieved from with scep response certificate is not 16byte(128 bit) so encryption is failing with a message Key should be 128 bit.
If I can change the public key to 16byte using the following code the device throws invalid profile dailog.
public static string getKeyMessageDigest(string key)
{
byte[] ByteData = Encoding.UTF8.GetBytes(key);
//MD5 creating MD5 object.
MD5 oMd5 = MD5.Create();
byte[] HashData = oMd5.ComputeHash(ByteData);
//convert byte array to hex format
StringBuilder oSb = new StringBuilder();
for (int x = 0; x < HashData.Length; x++)
{
//hexadecimal string value
oSb.Append(HashData[x].ToString("x2"));
}
return Convert.ToString(oSb);
}
Can some one help me with some blog or sample code to encrypt the profile? Appreciate your help.
I had a similar problem. PFB the working code that I'm using to encrypt now. I'm retrieving the signing certificate from the device response, retrieving the public key from it and using the same to encrypt.
byte[] request = StreamToByte(ResponseFromDevice);
var signer = new SignedCms();
signer.Decode(request);
X509Certificate2 certificate = signer.Certificates[0];
string xmlData = "payload string to encrypt";
Byte[] cleartextsbyte = UTF8Encoding.UTF8.GetBytes(xmlData);
ContentInfo contentinfo = new ContentInfo(cleartextsbyte);
EnvelopedCms envelopedCms = new EnvelopedCms(contentinfo);
CmsRecipient recipient = new CmsRecipient(certificate);
envelopedCms.Encrypt(recipient);
string data = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\"><plist version=\"1.0\"><dict><key>EncryptedPayloadContent</key><data>[ENCRYPTEDDATA]</data><key>PayloadDescription</key><string>For profile enrollment</string><key>PayloadDisplayName</key><string>ProfileName</string><key>PayloadIdentifier</key><string>YourIdentifier</string><key>PayloadOrganization</key><string>YourOrg</string><key>PayloadRemovalDisallowed</key><false/><key>PayloadType</key><string>Configuration</string><key>PayloadUUID</key><string>YourUDID/string><key>PayloadVersion</key><integer>1</integer></dict></plist>";
data = data.Replace("[ENCRYPTEDDATA]", Convert.ToBase64String(envelopedCms.Encode()));
HttpContext.Current.Response.Write(data);
WebOperationContext.Current.OutgoingResponse.ContentType = "application/x-apple-aspen-config";
WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.OK;
I answered in comments on your previous question:
"I would recommend to take a look on OS X Server MDM implementation.
Generally speaking to encrypt profile, as I remember you should use PKCS7 wrapping. So, you should look at this: http://www.cs.berkeley.edu/~jonah/bc/org/bouncycastle/jce/PKCS7SignedData.html
BTW. I would recommend to read up a little bit on cryptography, if you want to get general understanding. Very-very high level overview of your problem: you are trying to use RSA key directly to encrypt the data. However, it should be used to encrypt a symmetric key which in its turn is used to encrypt the data."
You can also take a look here:
PKCS#7 Encryption
Your code won't work, because it's
- not PKCS7
- you are trying to use MD5(public certificate key) which doesn't make any sense
I would really-really recommend to read again MDM documentation and something on cryptopraphy. It's quite easy to make it wrong (both non working or unsecure implementation).
In bouncycastle you have to encrypt it using CMSAlgorithm.DES_EDE3_CBC. Then signed the data as you done in the previous step. Make sure you Base64 encode the encrypted payload before signing.