I'm using RSA (Bouncy Castle API) in my C# project. I generated the keypair with this method:
RsaKeyPairGenerator r = new RsaKeyPairGenerator();
r.Init(new KeyGenerationParameters(new SecureRandom(), 1024));
AsymmetricCipherKeyPair keys = r.GenerateKeyPair();
AsymmetricKeyParameter private_key = keys.Private;
AsymmetricKeyParameter public_key = keys.Public;
Now I want to save them in a txt file but the problem is that I can't convert them to a string format. I read in another post that keys must be serialized using:
PrivateKeyInfo k = PrivateKeyInfoFactory.CreatePrivateKeyInfo(private_key);
byte[] serializedKey = k.ToAsn1Object().GetDerEncoded();
Is it the right way? If yes, what should I do after this? Just convert them from byte[] to String?
You could also use PemWriter to store them in PEM format:
TextWriter textWriter = new StringWriter();
PemWriter pemWriter = new PemWriter(textWriter);
pemWriter.WriteObject(keys.Private);
pemWriter.Writer.Flush();
string privateKey = textWriter.ToString();
Now privateKey contain something like this:
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDFhB3xI1AzSMsKvt7rZ7gp2o/vd49zON89iL1ENvKkph7oFXa2
ew/hjzbAV33lHnFFlA/vA5SDCbggRyU1/SmjfuPJFEzFbgcPLuO1Sw6z+bWXiIFp
QNCOTIw11c0fbwk+kB2y4E1OkLv5f9atlNlekb4wBn8bMbFYtu6LGWjkFQIDAQAB
AoGANPoMteLM3hSGMSmkzOsS4Fb5CDe/SB5W8kA805ERX8wWuhUXtDYpukwZWVme
MNgLdagS5f7F1KfEtROgDW0Lv4/T/FWAbpgh8O2CPKYDY4ZXl8tmRH9dtYn/824l
veLxdgNjHwo5OMvuTSDMjC3tbg2UA3kmV4VAm5QotlRinUECQQDo4zvI5e81zWnS
kNrUARX80t432MOZyr0RvAaVJfBNQpJl0H0xp5LKP74hvPVO9WdJvJ0M4Z4QrmGO
bm2Hsfz5AkEA2R469YXxgPLDg/LvUQqfrl8Ji9+rip7eQP7iS/dt66NMS31/HymT
+HscEZ3qGlnQuyyyRR2rGQdhdjU42HNy/QJBAKbPTF1DxSmGbovyUauU84jaCW17
B++6dd6kDRr7x7mvO2lOAr50RwIy0h8cV6EsycTZIqy9VhigryP0GOQfKxECQA8T
uVZpab7hnNAh45qGGVabhOcwrhHfPGHZEU/jK7/sRBUN7vD0CzF7IxTaGXKhAAyv
auW/zKzdRVhXE858HeUCQQCGaaAg8GwM0qIS0nHRTLldu4hIGjKn7Sk0Z46Plfwr
oqPCtuP4ehX85EIhqCcoFnG6Ttr6AxSgNMZvErVxDBiD
-----END RSA PRIVATE KEY-----
Well, I don't know about the RSA-specific side, but once you've got an opaque binary string (i.e. it could contain any arbitrary data) the best bet for text conversion is Convert.ToBase64String(byte[]) which you can reverse with Convert.FromBase64String(string).
Do not use Encoding.GetString(byte[]) and Encoding.GetBytes(string) for this - the binary data isn't text in a particular encoding, and shouldn't be treated as such. You're almost bound to lose data if you try this.
This might be what you are looking out for:
http://www.rahulsingla.com/blog/2011/04/serializing-deserializing-rsa-public-private-keys-generated-using-bounty-castle-library
Try the following
RsaKeyPairGenerator rsaKeyPairGenerator = new RsaKeyPairGenerator();
rsaKeyPairGenerator.Init(new KeyGenerationParameters(new SecureRandom(), XXX));
AsymmetricCipherKeyPair keys = rsaKeyPairGenerator.GenerateKeyPair();
PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keys.Private);
// Write out an RSA private key with it's asscociated information as described in PKCS8.
byte[] serializedPrivateBytes = privateKeyInfo.ToAsn1Object().GetDerEncoded();
// Convert to Base64 ..
string serializedPrivateString = Convert.ToBase64String(serializedPrivateBytes);
SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keys.Public);
byte[] serializedPublicBytes = publicKeyInfo.ToAsn1Object().GetDerEncoded();
string serializedPublicString = Convert.ToBase64String(serializedPublicBytes);
If you convert the bouncycastle certificate to a .net certificate. The function to do this is in the bouncycastle lib (i believe it's in a class called DotNetUtilities). The RSACryptoServiceProvider has a function:
ToXmlString(bool includePrivateKey).
Which gives you an x representation of a certificate with if you want the private key containing all the components serialized to base64 seperately, exponent, modulus, and d (private exponent).
Related
When creating an ECDSA object, and trying to extract the private and public key objects it returns a byte[]. But when trying to convert it to a string, the output it gives doesn't look right.
// Creating the object with default parameters
ECDsa ecdSa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
// Export parameters
ECParameters ecParamters = ecdSa.ExportParameters(true);
// Private Key
byte[] privateKey = ecParamters.D;
// Public key params
ECPoint publicKey = ecParamters.Q;
// Coordinates
byte[] publicKeyX = publicKey.X; // What format? int, double, etc
byte[] publicKeyY = publicKey.Y; // What format? int, double, etc
Above ECParameters give access to the curve, D (private key) and Q (public key).
Any idea what sort of formatting is being used in these byte arrays? how can we convert it to a string format, eg: private and public keys to PKCS#8?
D is simply the raw private key, X and Y are the two coordinates of the raw public key. These are 32 bytes each for P-256.
Since .NET Core 3.0 the direct export of private ECDSA keys in PKCS#8 format (ExportPkcs8PrivateKey()) and SEC1 format (ExportECPrivateKey()) is supported and of public ECDSA keys in X.509 format (ExportSubjectPublicKeyInfo()). A detour via ExportParameters() is not necessary! The exported keys are DER encoded.
Example:
ECDsa ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
byte[] privatePkcs8Der = ecdsa.ExportPkcs8PrivateKey();
byte[] privateSec1Der = ecdsa.ExportECPrivateKey();
byte[] publicX509Der = ecdsa.ExportSubjectPublicKeyInfo();
The conversion from DER to PEM encoding is trivial: Base64 encode, insert a newline after every 64 characters and add the format specific header and footer. Alternatively you can use BouncyCastle (e.g. for PKCS#8 via Org.BouncyCastle.Utilities.IO.Pem.PemWriter):
using Org.BouncyCastle.Utilities.IO.Pem;
using System.IO;
...
StringWriter stringWriter = new StringWriter();
PemWriter pemWriter = new PemWriter(stringWriter);
pemWriter.WriteObject((PemObjectGenerator)new PemObject("PRIVATE KEY", privatePkcs8Der)); // SEC1: EC PRIVATE KEY, X.509: PUBLIC KEY
Console.WriteLine(stringWriter.ToString());
Since .NET 5 the PEM encoding is also directly supported, but only for the import: ImportFromPem().
Note similar support exists for ECDH and RSA keys.
I have my RSA public key defined in XML file
<RSAKeyValue>
<Modulus>+Rfd2cRvtV2Jez0gPuuEupPbA0wUN6GLYx/CasPe+X8dxG+KXtb/iRgB+mTW/ynxApOR/+GLS7rbcqq6cH76FnZH4l/Ualovd1CtY906EYGQ7ldxmt/1UfB+O4PiE3e4y2BGYshSdjQcJMCAyAwGL2vNmygUB/OPntZtnm1steX62TA1OG0VivsrCG+hDon4QrZN6XLNXHU0zpCDeVuuD5edVwRQCd2kuzNrLXuGjOaSXxfgzy1xgAPVDqKknr9doAJ4pGu3AILmjyWKldNLWzppqAbKFcmUjWUWbouMbaqDfs7JazxCgeY1DMSYkpSd0HOB6zl2u41xlpBSyLg1EGUOnp5KBPQSzOWqgJhbVy59LK1BhnkE4/eHZQjDsj95G9afmQnffk/td1qUf+MuX0Qo9L9Ls9Dlw3VQH52wnLchCBgdzaminFRMN0JbNe8IRf0ZAI87ES1ND0adMBo//QElV0J+YyPBjrGdYavNiI0jvBNq7x6ex405CrW5/J86R2LmdBSoD3knWFKQVszNN8jiA+Rl8at6qVBfoSgISzqLNoaad1B2J6gRJzBu3VHo1pkbFYz21I72orvhnMI9cL9pwtyayLPPC65nkvL2ichKJM2vtRY79z7IBf++Byq5y9L8vIDghEqwaPW3GT3574K7x4Rc4XETFMO4idRscsE=</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
Here is my method where I'm getting the RSA from XML file:
using (RSA rsa = RSA.Create())
{
var xml = await File.ReadAllTextAsync(keyConfig.File);
RSAExtensions.FromXmlString(rsa, xml);
var paremeters = rsa.ExportParameters(false);
var modulus = paremeters.Modulus;
}
At this moment it works.
But I have not idea how to convert modulus to e.g string? Modulus is a byte array but when I'm trying to convert this using Encoding.... it returns strange values. How is the modulus represented in this byte array? What format is it?
The value can't be converted directly to a string, but it can be represented by a Base64 string:
var myString = Convert.ToBase64String(modulus);
This will return a string that can later be decoded to get the original binary data.
I was given this C# code written by the web service team that exposes some web service that I'm planning to consume. My password needs to be encrypted with this code so that the web service knows how to decrypt it on their end.
using(RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.FromXmlString(publicKey);
byte[] plainBytes = Encoding.Unicode.GetBytes(clearText);
byte[] encryptedBytes = rsa.Encrypt(plainBytes, false);
return Convert.ToBase64String(encryptedBytes);
}
I'm using Java to consume this web service and right now, I'm having problem translating that #C code into Java code because that web service can't decrypt my password properly.
Here's my current failed attempt:-
// my clear text password
String clearTextPassword = "XXXXX";
// these values are provided by the web service team
String modulusString = "...";
String publicExponentString = "...";
BigInteger modulus = new BigInteger(1, Base64.decodeBase64(modulusString.getBytes("UTF-8")));
BigInteger publicExponent = new BigInteger(1, Base64.decodeBase64(publicExponentString.getBytes("UTF-8")));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(modulus, publicExponent);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
String encodedEncryptedPassword = new String(Base64.encodeBase64(cipher.doFinal(clearTextPassword.getBytes("UTF-8"))));
What did I do wrong? Thanks much.
2013-08-07 - UPDATE
I was reading this website and I realized that my modulus value and public exponent value are not in Hex. So, I modified my code a little bit and tried with RSA/ECB/PKCS1PADDING as mentioned by #Dev.
// my clear text password
String clearTextPassword = "XXXXX";
// these are the actual values I get from the web service team
String modulusString = "hm2oRCtP6usJKYpq7o1K20uUuL11j5xRrbV4FCQhn/JeXLT21laKK9901P69YUS3bLo64x8G1PkCfRtjbbZCIaa1Ci/BCQX8nF2kZVfrPyzcmeAkq4wsDthuZ+jPInknzUI3TQPAzdj6gim97E731i6WP0MHFqW6ODeQ6Dsp8pc=";
String publicExponentString = "AQAB";
Base64 base64Encoder = new Base64();
String modulusHex = new String(Hex.encodeHex(modulusString.getBytes("UTF-8")));
String publicExponentHex = new String(Hex.encodeHex(publicExponentString.getBytes("UTF-8")));
BigInteger modulus = new BigInteger(modulusHex, 16);
BigInteger publicExponent = new BigInteger(publicExponentHex);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(modulus, publicExponent);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
String encodedEncryptedPassword = new String(base64Encoder.encode(cipher.doFinal(clearTextPassword.getBytes("UTF-8"))));
When I hit the webservice, I'm getting this error: "The data to be decrypted exceeds the maximum for this modulus of 128 bytes." It seems like the clear text password is still not encrypted properly.
Any help or suggestion is greatly appreciated. Thanks.
2013-08-09 - SOLUTION
I posted my final working solution below.
Found the solution.
String modulusString = "hm2oRCtP6usJKYpq7o1K20uUuL11j5xRrbV4FCQhn/JeXLT21laKK9901P69YUS3bLo64x8G1PkCfRtjbbZCIaa1Ci/BCQX8nF2kZVfrPyzcmeAkq4wsDthuZ+jPInknzUI3TQPAzdj6gim97E731i6WP0MHFqW6ODeQ6Dsp8pc=";
String publicExponentString = "AQAB";
byte[] modulusBytes = Base64.decodeBase64(modulusString);
byte[] exponentBytes = Base64.decodeBase64(publicExponentString);
BigInteger modulus = new BigInteger(1, modulusBytes);
BigInteger publicExponent = new BigInteger(1, exponentBytes);
RSAPublicKeySpec rsaPubKey = new RSAPublicKeySpec(modulus, publicExponent);
KeyFactory fact = KeyFactory.getInstance("RSA");
PublicKey pubKey = fact.generatePublic(rsaPubKey);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] plainBytes = clearTextPassword.getBytes("UTF-16LE");
byte[] cipherData = cipher.doFinal(plainBytes);
String encryptedStringBase64 = Base64.encodeBase64String(cipherData);
According to MSDN docs on RSACryptoServiceProvider.Encrypt, when the second argument is false the cipher uses PKCS#1 v1.5 padding. So right off the bat your cipher spec is incorrect.
Try RSA/ECB/PKCS1PADDING instead.
You are converting your key material to much in your second code example and you corrupted it which ends up making your cipher think you have more key material than you actually have and makes your message too long (which is triggering your error) as well as unintelligible to the decryption cipher on the other end. Convert directly to byte arrays and pass those to BigInteger.
String modulusString = "...";
String publicExponentString = "...";
byte[] mod = Base64.decodeBase64(modulusString);
byte[] e = Base64.decodeBase64(publicExponentString);
BigInteger modulus = new BigInteger(1, mod);
BigInteger publicExponent = new BigInteger(1, e);
Using RSA/ECB/OAEPWithSHA-1AndMGF1Padding seems to be the solution for this issue.
Don't forget these replacements.
Encoding to Base64
Use System.Convert to convert the input to Base64.
Replace + by - and / by _. Example: Foo+bar/=== becomes Foo-bar_===.
Replace any number of = at the end of the string, with an integer denoting how many they were. Example: Foo-bar_=== becomes Foo-bar_3.
Decoding from Base64
Replace the digit at the end of the string by the same number of = signs. Example: Foo-bar_3 becomes Foo-bar_===.
Replace - by + and _ by /. Example: Foo-bar_=== becomes Foo+bar/===.
Use System.Convert to decode the preprocessed input from Base64.
Could anyone clarify the "whole" story with Rsa-related thing. (I guess, that's because of padding or something).
Ok. My general aim is to encrypt passwords (one way only) to store in database in hashed state.
My approach is:
I create private/public keys with this How to Generate Unique Public and Private Key via RSA (common) approach.
const int PROVIDER_RSA_FULL = 1;
const string CONTAINER_NAME = "KeyContainer";
CspParameters cspParams;
cspParams = new CspParameters(PROVIDER_RSA_FULL);
cspParams.KeyContainerName = CONTAINER_NAME;
cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
cspParams.ProviderName = "Microsoft Strong Cryptographic Provider";
rsa = new RSACryptoServiceProvider(cspParams);
string publicPrivateKeyXML = rsa.ToXmlString(true);
string publicOnlyKeyXML = rsa.ToXmlString(false);
// do stuff with keys...
and when needed, do this:
var rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(publicOnlyKeyXML);
var valueToEncode = Console.ReadLine();
Console.WriteLine(Convert.ToBase64String(rsa.Encrypt(Encoding.UTF8.GetBytes(valueToEncode), false)));
// initially that's rsa.Encrypt(...
and every new time I do the same value encryption, I receive different values, which is not suitable to store password in such way (as I don't decrypt it for users' authentication, but just compare hashed value).
Could anyone clarify the situation with the encoding stuff?
And also, what are the common practices for passwords encoding to hashes these days (.NET/C#).
Thank you!
I am using the Bouncycastle C# crypto library and I want to convert an AsymmetricKeyParameter object that represents a public key to DER format. I know how to do this with an AsymmetricKeyParameter object that represents a private key but I can't figure out do it for a public key.
Here is my code:
PrivateKeyInfo infoPrivate = PrivateKeyInfoFactory.CreatePrivateKeyInfo(asymeterickey);
byte[] serializedPrivateKey = infoPrivate.PrivateKey.ToAsn1Object().GetDerEncoded();
string derPrivateKey = Convert.ToBase64String(serializedPrivateKey);
I think the class you are looking for is Org.BouncyCastle.X509.SubjectPublicKeyInfoFactory
thanks friend
I found that!
using Org.BouncyCastle.X509;
SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(asymetericKey);