Converting a byte array to a X.509 certificate - c#

I'm trying to port a piece of Java code into .NET that takes a Base64 encoded string, converts it to a byte array, and then uses it to make a X.509 certificate to get the modulus & exponent for RSA encryption.
This is the Java code I'm trying to convert:
byte[] externalPublicKey = Base64.decode("base 64 encoded string");
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(externalPublicKey);
Key publicKey = keyFactory.generatePublic(publicKeySpec);
RSAPublicKey pbrtk = (java.security.interfaces.RSAPublicKey) publicKey;
BigInteger modulus = pbrtk.getModulus();
BigInteger pubExp = pbrtk.getPublicExponent();
I've been trying to figure out the best way to convert this into .NET. So far, I've come up with this:
byte[] bytes = Convert.FromBase64String("base 64 encoded string");
X509Certificate2 x509 = new X509Certificate2(bytes);
RSA rsa = (RSA)x509.PrivateKey;
RSAParameters rsaParams = rsa.ExportParameters(false);
byte[] modulus = rsaParams.Modulus;
byte[] exponent = rsaParams.Exponent;
Which to me looks like it should work, but it throws a CryptographicException when I use the base 64 encoded string from the Java code to generate the X509 certificate. The exact message I receive is:
Cannot find the requested object.
Is Java's X.509 implementation just incompatible with .NET's, or am I doing something wrong in my conversion from Java to .NET?
Or is there simply no conversion from Java to .NET in this case?

It seems your base64-encoded data does not represent an X.509 certificate:
[The X509EncodedKeySpec class] represents the ASN.1 encoding of a public key
Export the whole X.509 certificate in Java, or try to find an equivalent of the X509EncodedKeySpec class in the .NET framework.

I have encountered a similar issue, and in my case it boiled down to an 'endian' problem.
The solution was simply to reverse the byte array (Array.Reverse in .NET)
I don't have the 2 IDEs in front of me to show a proof, but if you get stuck, give it a try!

Related

Export RSA Public Key in DER Format and decrypt data

I need to get the bytes of my RSA Public Key in the DER format and send this key to an API. This API will then send me something encrypted using this Public Key back and i need to decrypt it.
First problem, it seems that C# doesn't have the ability to export RSA Keys in the DER Format. So it seems i have to rely on a 3rd party package like BouncyCastle. I also found a way to generate and export my Public Key in the DER Format with this package:
var generator = new RsaKeyPairGenerator();
generator.Init(new KeyGenerationParameters(new SecureRandom(), 2048));
var keyPair = generator.GenerateKeyPair();
var publicKeyParam = (RsaKeyParameters)keyPair.Public;
var publicKey = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(publicKeyParam).GetDerEncoded();
So now i only had to Base64 encode those bytes and send it to the API and the API is happy. But now i'm stuck trying to figure out how i can decrypt the dat i received. First of all i decoded the base64 string and now i'm stuck with the bytes of the data. I can get the private key from the BouncyCastle "thing", but i cant seem to convert it to RsaCryptoServiceProvider correctly and the BouncyCastle documentation is kind of weird.
If anyone has an idea or another approach i would be more than happy.
Based on the comments i came up with this solution:
var rsa = new RSACryptoServiceProvider(2048); // my rsa to decrypt and encrypt data
var publicKeyParam = DotNetUtilities.GetRsaPublicKey(rsa.ExportParameters(false));
var publicKey = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(publicKeyParam).GetDerEncoded(); // my exported public key in DER format
Thanks alot James K Polk!

Decrypting Crypto++ RSA cipher text in C# causes exception

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.

Encrypting mdm profile

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.

Port RSA encryption Java code to C#

I'm trying to port the following Java code to a C# equivalent:
public static String encrypt(String value, String key) throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
byte[] bytes = value.getBytes(Charset.forName("UTF-8"));
X509EncodedKeySpec x509 = new X509EncodedKeySpec(DatatypeConverter.parseBase64Binary(key));
KeyFactory factory = KeyFactory.getInstance("RSA");
PublicKey publicKey = factory.generatePublic(x509);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
bytes = cipher.doFinal(bytes);
return DatatypeConverter.printBase64Binary(bytes);
}
So far I managed to write the following in C#, using the BouncyCastle library for .NET:
public static string Encrypt(string value, string key)
{
var bytes = Encoding.UTF8.GetBytes(value);
var publicKeyBytes = Convert.FromBase64String(key);
var asymmetricKeyParameter = PublicKeyFactory.CreateKey(publicKeyBytes);
var rsaKeyParameters = (RsaKeyParameters) asymmetricKeyParameter;
var cipher = CipherUtilities.GetCipher("RSA");
cipher.Init(true, rsaKeyParameters);
var processBlock = cipher.DoFinal(bytes);
return Convert.ToBase64String(processBlock);
}
The two methods, though, produce different results even if called with the same parameters.
For testing purposes, I'm using the following public RSA key:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCLCZahTj/oz8mL6xsIfnX399Gt6bh8rDHx2ItTMjUhQrE/9kGznP5PVP19vFkQjHhcBBJ0Xi1C1wPWMKMfBsnCPwKTF/g4yga6yw26awEy4rvfjTCuFUsrShSPOz9OxwJ4t0ZIjuKxTRCDVUO7d/GZh2r7lx4zJCxACuHci0DvTQIDAQAB
Could you please help me to port the Java code successfully or suggest an alternative to get the same result in C#?
EDIT1: output in Java is different each time I run the program. I don't think that any padding was specified, so I don't understand what makes the output random.
EDIT2: Java uses PKCS1 by default, so it was enough to specify it in the C# cipher initialization to get the same encryption type (although not the same result, which was irrelevant at this point).
As an educated guess, I would say that Java adds random padding to create a stronger encryption.
Most practical implementations of RSA do this, and as the wiki puts it...
Because RSA encryption is a deterministic encryption algorithm – i.e., has no random component – an attacker can successfully launch a chosen plaintext attack against the cryptosystem, by encrypting likely plaintexts under the public key and test if they are equal to the ciphertext. A cryptosystem is called semantically secure if an attacker cannot distinguish two encryptions from each other even if the attacker knows (or has chosen) the corresponding plaintexts. As described above, RSA without padding is not semantically secure.
This is likely why your two methods don't output the same.

How to validate X.509 Certificate in C# using Compact Framework

I am trying to validate an X.509 certificate using C# and .NetCF. I have the CA certificate, and if I understand correctly, I need to use the public key from this CA certificate to decrypt the signature of the untrusted certificate. This should give me the computed hash value of the untrusted certificate. I should then compute the hash of the certificate myself and make sure the two values match.
I've been playing with this for a few days and I'm not getting very far. I've been using the X509Certificate and RSACryptoServiceProvider classes. First, I tried to get the public key and signature out of the X509Certificate class. I was able to get the public key but not the signature. Next, I tried parsing the binary data that made up the certificate, which allowed me to get the signature (and any other data I wanted), but I was unable to decrypt the signature using the RSACryptoServiceProvider. I tried things like this but kept getting exceptions saying "Bad Key" when I tried to decrypt:
RSAParameters rsaParams = new RSAParameters();
rsaParams.Exponent = exp;
rsaParams.Modulus = mod;
RSACryptoServiceProvider rsaServ = new RSACryptoServiceProvider();
rsaServ.ImportParameters(rsaParams);
byte[] decryptedSig = rsaServ.Decrypt(encryptedSig, false);
Any advice would be greatly appreciated.
Edit:
I tried something that seems to be better but is returning a strange result. I'm working with the X509Certificate2 class here because it's a little easier for testing, but I will need to switch to X509Certificate for .NetCF later. I think that RSACryptoServiceProvider.VerifyData might be what I need. I tried the following code.
X509Certificate2 cert = new X509Certificate2(certBytes);
X509Certificate2 certCA1 = new X509Certificate2(#"C:\certs\certCA1.cer");
byte[] encryptedSig = new byte[256];
Array.Copy(certBytes, certBytes.Length - 256, encryptedSig, 0, 256);
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)certA1.PublicKey.Key;
bool good = rsa.VerifyData(cert.RawData, "1.3.14.3.2.26", encryptedSig);
As I said, I am able to manually decode and interpret the binary data of the certificate, so I'm pretty sure the cert.RawData is the certificate's signed data and the last 256 bytes are the encrypted signature. The string is the OID of the hash algorithm, which I got from certificate, but I'm not 100% sure that it's correct. VerifyData returns false, but I'm not sure why yet.
Thoughts?
Here is my code.
RSACryptoServiceProvider rsa = signingCertificate_GetPublicKey();
return rsa.VerifyData( SignedValue(), CryptoConfig.MapNameToOID( "SHA1" ), Signature() );
RSACryptoServiceProvider signingCertificate_GetPublicKey()
{
RSACryptoServiceProvider publicKey = new RSACryptoServiceProvider();
RSAParameters publicKeyParams = new RSAParameters();
publicKeyParams.Modulus = GetPublicKeyModulus();
publicKeyParams.Exponent = GetPublicKeyExponent();
publicKey.ImportParameters( publicKeyParams );
return publicKey;
}
byte[] GetPublicKeyExponent()
{
// The value of the second TLV in your Public Key
}
byte[] GetPublicKeyModulus()
{
// The value of the first TLV in your Public Key
}
byte[] SignedValue()
{
// The first TLV in your Ceritificate
}
byte[] Signature()
{
// The value of the third TLV in your Certificate
}
I hope that helps anyone who is working on this problem.
Does WinCE support something compatible with the Win32 MSCrypto.dll? If yes, take a look at the .NET X509Certificate2 class and also the CLR Security library on Codeplex. It contains a lot of helpful routines for .NET that sit on top of the core OS crypto library. You can download the source and see how it compiles for .NetCF
To load and validate an X509 certificate, do something like this (untested):
var cert = new X509Certificate2("mycert.cer");
if (!cert.Verify())
{
<fail>
}
There are nearly a dozen constructors for X509Certificate2 to construct from a wide variety of sources - file on disk, byte array in memory, load from local cert store, etc.
The root CA used to sign the certificate will need to be installed in the local cert store. If the certificate does not include the intermediate CAs in the trust chain, those intermediates will need to be on the local machine too, all the way up to a root CA that is in the trusted cert store in the local machine.
Unfortunately, I can't tell from the MSDN docs whether X509Certificate2 is available on .NetCF.
It works for me in win32, but in Compact Framework I'm facing the same problem, there is no X509Certificate2 so I'm actually blocked, with win32 we can do:
X509Certificate2 l__PublicKeyCertificate = new X509Certificate2("cert.cer");
RSACryptoServiceProvider l__rsaCspPublic = (RSACryptoServiceProvider)l__PublicKeyCertificate .PublicKey.Key;
//...
l__isVerified = l__rsaCspPublic.VerifyData(l__fileData, CryptoConfig.MapNameToOID("SHA1"), l__fileSignature);

Categories

Resources