ImportPkcs8PrivateKey and ImportRSAPrivateKey alternative in .net framework - c#

in .net core 3.1 I am using ImportPkcs8PrivateKey and ImportRSAPrivateKey for some RSA private Key import as per following function
private RSA RsaKeyAsPerContent()
{
//https://csfieldguide.org.nz/en/interactives/rsa-key-generator/
//https://travistidwell.com/jsencrypt/demo/
RSA rSA = RSA.Create();
string privateKeyContent = "...."
bool isPkcsprivateKey = privateKeyContent.Contains("BEGIN PRIVATE KEY");
if (isPkcsprivateKey)
{
var privateKey = privateKeyContent.Replace("-----BEGIN PRIVATE KEY-----", string.Empty).Replace("-----END PRIVATE KEY-----", string.Empty);
privateKey = privateKey.Replace(Environment.NewLine, string.Empty);
var privateKeyBytes = Convert.FromBase64String(privateKey);
rSA.ImportPkcs8PrivateKey(privateKeyBytes, out int _);
}
else
{
var privateKey = privateKeyContent.Replace("-----BEGIN RSA PRIVATE KEY-----", string.Empty).Replace("-----END RSA PRIVATE KEY-----", string.Empty);
privateKey = privateKey.Replace(Environment.NewLine, string.Empty);
var privateKeyBytes = Convert.FromBase64String(privateKey);
rSA.ImportRSAPrivateKey(privateKeyBytes, out int _);
}
return rSA;
}
now I need same import functionality in traditional .net framework version 4.6/4.7 but it is not available
Any idea how can i do it in .net framework

You can try the following. It has been tested on .net framework 4.8 and it uses a 3rd party package "BouncyCastle.Crypto".
public static RSACryptoServiceProvider ImportPrivateKey(string pem) {
PemReader pr = new PemReader(new StringReader(pem));
AsymmetricCipherKeyPair KeyPair = (AsymmetricCipherKeyPair)pr.ReadObject();
RSAParameters rsaParams =
DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)KeyPair.Private);
RSACryptoServiceProvider csp = new RSACryptoServiceProvider();// cspParams);
csp.ImportParameters(rsaParams);
return csp;
}
You can refer to https://gist.github.com/ststeiger/f4b29a140b1e3fd618679f89b7f3ff4a for more details.

Following is been used in .net framework 4.6
private static RSA RsaKeyAsPerContent()
{
RSA rSA = RSA.Create();
string privateKeyFromConfig = ConfigurationManager.AppSettings["privateKey"];
rSA.ImportParameters(ImportPrivateKey(privateKeyFromConfig));
return rSA;
}
public static RSAParameters ImportPrivateKey(string pem)
{
PemReader pr = new PemReader(new StringReader(pem));
RsaPrivateCrtKeyParameters privKey = (RsaPrivateCrtKeyParameters)pr.ReadObject();
RSAParameters rp = new RSAParameters();
rp.Modulus = privKey.Modulus.ToByteArrayUnsigned();
rp.Exponent = privKey.PublicExponent.ToByteArrayUnsigned();
rp.P = privKey.P.ToByteArrayUnsigned();
rp.Q = privKey.Q.ToByteArrayUnsigned();
rp.D = ConvertRSAParametersField(privKey.Exponent, rp.Modulus.Length);
rp.DP = ConvertRSAParametersField(privKey.DP, rp.P.Length);
rp.DQ = ConvertRSAParametersField(privKey.DQ, rp.Q.Length);
rp.InverseQ = ConvertRSAParametersField(privKey.QInv, rp.Q.Length);
return rp;
}
private static byte[] ConvertRSAParametersField(BigInteger n, int size)
{
byte[] bs = n.ToByteArrayUnsigned();
if (bs.Length == size)
return bs;
if (bs.Length > size)
throw new ArgumentException("Specified size too small", "size");
byte[] padded = new byte[size];
Array.Copy(bs, 0, padded, size - bs.Length, bs.Length);
return padded;
}

Related

RSA encryption using modulus and exponent public key

I need to encrypt my steam login password. Before sending the auth data, steam sends this: "publickey_mod":"c511d72db5ebbba01977983eec2...","publickey_exp":"010001".
The browser encrypts password with this script:
var pubKey = RSA.getPublicKey(results.publickey_mod, results.publickey_exp);
password = password.replace(/[^\x00-\x7F]/g, ''); // remove non-standard-ASCII characters
var encryptedPassword = RSA.encrypt(password, pubKey);
I can't write a working algorithm in c# which will encrypt the password using modulus and exponent.
Here is what i tried:
static async Task Main()
{
var pubKey = SetPublicKey($"{response["publickey_mod"]}", $"{response["publickey_exp"]}");
string password = "123456";
byte[] password_byte = Encoding.ASCII.GetBytes(password);
byte[] encryptedPassword;
using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
{
RSA.ImportParameters(pubKey);
encryptedPassword = RSA.Encrypt(password_byte, false);
}
string encodingPassword = Convert.ToHexString(encryptedPassword);
Console.WriteLine(encodingPassword);
}
public static RSAParameters SetPublicKey(string modulus, string exponent)
{
RSAParameters result = new RSAParameters();
result.Modulus = Convert.FromHexString(modulus);
result.Exponent = Convert.FromHexString(exponent);
return result;
}
working algorithm:
byte[] encryptedPasswordBytes;
using (var rsaEncryptor = new RSACryptoServiceProvider())
{
var passwordBytes = Encoding.ASCII.GetBytes(password);
var rsaParameters = rsaEncryptor.ExportParameters(false);
rsaParameters.Exponent = Convert.FromHexString($"{_response_getrsakey["publickey_exp"]}");
rsaParameters.Modulus = Convert.FromHexString($"{_response_getrsakey["publickey_mod"]}");
rsaEncryptor.ImportParameters(rsaParameters);
encryptedPasswordBytes = rsaEncryptor.Encrypt(passwordBytes, false);
}
string encryptedPassword = Convert.ToBase64String(encryptedPasswordBytes);

C# BouncyCastle FIPS RSA keys to RSACryptoServiceProvider

BouncyCastle has FIPS DLLs for C# that I need to use for encryption instead of normal DLLs because of compliance. How do you import public and private keys and covert them to a RSACryptoServiceProvider in order to encrypt and decrypt.
This is how I encrypt and decrypt using the regular BouncyCastle. I just need to change the functions ImportPrivateKey and ImportPublicKey
public static string Decrypt(string privateKey, string base64Encrypted)
{
string ret = null;
using (var rsa = ImportPrivateKey(privateKey))
{
var cipherBytes = Convert.FromBase64String(base64Encrypted);
RSA rsaCng = new RSACng();
rsaCng.ImportParameters(rsa.ExportParameters(true));
byte[] plainBytes = rsaCng.Decrypt(cipherBytes, RSAEncryptionPadding.OaepSHA256);
string plaintext = Encoding.UTF8.GetString(plainBytes);
ret = plaintext;
}
return ret;
}
public static string Encrypt(string publicKey, string toEncrypt)
{
string cipherText = null;
using (var rsa = ImportPublicKey(publicKey))
{
var data = Encoding.UTF8.GetBytes(toEncrypt);
RSA rsaCng = new RSACng();
rsaCng.ImportParameters(rsa.ExportParameters(false));
byte[] cipherTextBytes = rsaCng.Encrypt(data, RSAEncryptionPadding.OaepSHA256);
cipherText = Convert.ToBase64String(cipherTextBytes);
}
return cipherText;
}
public RSACryptoServiceProvider ImportPrivateKey(string pem)
{
PemReader pr = new PemReader(new StringReader(pem));
AsymmetricCipherKeyPair KeyPair = (AsymmetricCipherKeyPair)pr.ReadObject();
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)KeyPair.Private);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(rsaParams);
return rsa;
}
public RSACryptoServiceProvider ImportPublicKey(string pem)
{
PemReader pr = new PemReader(new StringReader(pem));
AsymmetricKeyParameter publicKey = (AsymmetricKeyParameter)pr.ReadObject();
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaKeyParameters)publicKey);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(rsaParams);
return rsa;
}
Here is how I got it working. If someone else has something more simple that would be awesome!. Import the keys turn them into AsymmetricRsaPublicKey then covert them to RSAParameters and then import those.
using System.IO;
using System.Security.Cryptography;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto.Asymmetric;
using Org.BouncyCastle.Crypto.Fips;
using Org.BouncyCastle.OpenSsl;
public RSACryptoServiceProvider ImportPublicKey(string key)
{
var reader = new OpenSslPemReader(new StringReader(key));
var publicKeyInfo = (SubjectPublicKeyInfo)reader.ReadObject();
var rpckp = new AsymmetricRsaPublicKey(FipsRsa.Pkcs1v15.Algorithm, publicKeyInfo);
RSAParameters parms = new RSAParameters
{
Modulus = rpckp.Modulus.ToByteArrayUnsigned(),
Exponent = rpckp.PublicExponent.ToByteArrayUnsigned()
};
RSACryptoServiceProvider rcsp = new RSACryptoServiceProvider();
rcsp.ImportParameters(parms);
return rcsp;
}
public RSACryptoServiceProvider ImportPrivateKey(string key)
{
var reader = new OpenSslPemReader(new StringReader(key));
var keyPair = (PemKeyPair)reader.ReadObject();
var rpckp = new AsymmetricRsaPrivateKey(FipsRsa.Pkcs1v15.Algorithm, keyPair.PrivateKeyInfo);
RSAParameters parms = new RSAParameters
{
Modulus = rpckp.Modulus.ToByteArrayUnsigned(),
P = rpckp.P.ToByteArrayUnsigned(),
Q = rpckp.Q.ToByteArrayUnsigned(),
DP = rpckp.DP.ToByteArrayUnsigned(),
DQ = rpckp.DQ.ToByteArrayUnsigned(),
InverseQ = rpckp.QInv.ToByteArrayUnsigned(),
D = rpckp.PrivateExponent.ToByteArrayUnsigned(),
Exponent = rpckp.PublicExponent.ToByteArrayUnsigned()
};
RSACryptoServiceProvider rcsp = new RSACryptoServiceProvider();
rcsp.ImportParameters(parms);
return rcsp;
}

Key not valid for use in specified state in RSA encryption

here is my code.get error in encryption method
encryptedData = RSA.Encrypt(Data, DoOAEPPadding);
The method throws "Key not valid for use in specified state."
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
private void button1_Click(object sender, EventArgs e)
{
var rsa = new RSACryptoServiceProvider();
var rsaKeyPair = DotNetUtilities.GetRsaKeyPair(rsa);
var writer = new StringWriter();
var writer2 = new StringWriter();
var pemWriter = new PemWriter(writer);
var pemWriter2 = new PemWriter(writer2);
pemWriter.WriteObject(rsaKeyPair.Public);
pemWriter2.WriteObject(rsaKeyPair.Private);
string Pub = writer.ToString();
string Prv = writer2.ToString();
plaintext = ByteConverter.GetBytes(txtplain.Text);
encryptedtext = Encryption(plaintext, Pub, false);
}
get error here when encrypt data using by RSA encryption method.here pass DATA as a byte and RSA public key as a string.after that convert it to RSAParanetrs "RSA.ImportParameters(GetRSAParameters(RSAKey));"
then encrypt data get this error.
static public byte[] Encryption(byte[] Data, string RSAKey, bool DoOAEPPadding)
{
try
{
byte[] encryptedData;
using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
{
RSA.ImportParameters(GetRSAParameters(RSAKey));
encryptedData = RSA.Encrypt(Data, DoOAEPPadding); // get error "Key not valid for use in specified state"
}
return encryptedData;
}
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
return null;
}
}
private static RSAParameters GetRSAParameters(string pPublicKey)
{
byte[] lDer;
int lBeginStart = "-----BEGIN PUBLIC KEY-----".Length;
int lEndLenght = "-----END PUBLIC KEY-------".Length;
string KeyString = pPublicKey.Substring(lBeginStart, (pPublicKey.Length - lBeginStart - lEndLenght));
lDer = Convert.FromBase64String(KeyString);
RSAParameters lRSAKeyInfo = new RSAParameters();
lRSAKeyInfo.Modulus = GetModulus(lDer);
lRSAKeyInfo.Exponent = GetExponent(lDer);
return lRSAKeyInfo;
}
private static byte[] GetModulus(byte[] pDer)
{
string lModulus = BitConverter.ToString(pDer).Replace("-", "").Substring(58, 256);
return StringHexToByteArray(lModulus);
}
private static byte[] GetExponent(byte[] pDer)
{
int lExponentLenght = pDer[pDer.Length - 3];
string lExponent = BitConverter.ToString(pDer).Replace("-", "").Substring((pDer.Length * 2) - lExponentLenght * 2, lExponentLenght * 2);
return StringHexToByteArray(lExponent);
}
public static byte[] StringHexToByteArray(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}

Cannot Export PrivateKey Before Import Using RSACng and RsaParameter

the next unit test export a privatekey and save it in bytes arrays using the rsa instance then encrypt the "hi" message, all is fine here, but the problem occur when It make rsa2 instance and import the previous privatekey in RSAParameter, then message can be decrypt after import privatekey, but It throw exception when you try to export privatekey of rsa2.
Please could you tell me why It can't extract Imported Private key
[TestMethod]
public void TestRsa()
{
var rsa = new RSACng(2048);
///Export private key to arrays
var rsaParam = rsa.ExportParameters(true);
byte[] yQ = new byte[rsaParam.Q.Length];
byte[] yP = new byte[rsaParam.P.Length];
byte[] yInverseQ = new byte[rsaParam.InverseQ.Length];
byte[] yDP = new byte[rsaParam.DP.Length];
byte[] yDQ = new byte[rsaParam.DQ.Length];
//Public Part Key
byte[] yPm = new byte[rsaParam.Modulus.Length];
byte[] yPe = new byte[rsaParam.Exponent.Length];
byte[] yD = new byte[rsaParam.D.Length];
rsaParam.Q.CopyTo(yQ, 0);
rsaParam.P.CopyTo(yP, 0);
rsaParam.InverseQ.CopyTo(yInverseQ, 0);
rsaParam.DP.CopyTo(yDP, 0);
rsaParam.DQ.CopyTo(yDQ, 0);
rsaParam.Modulus.CopyTo(yPm, 0);
rsaParam.Exponent.CopyTo(yPe, 0);
rsaParam.D.CopyTo(yD, 0);
var encrypt = rsa.Encrypt(Encoding.UTF8.GetBytes("hi"), RSAEncryptionPadding.Pkcs1);
///Importing private key in another instance of RSACng
var rsa2 = new RSACng(2048);
RSAParameters rsaParameters = new RSAParameters()
{
Q = yQ,
P = yP,
InverseQ = yInverseQ,
DP = yDP,
D = yD,
DQ = yDQ,
Exponent = yPe,
Modulus = yPm
};
rsa2.ImportParameters(rsaParameters);
var decryptData = rsa2.Decrypt(encrypt, RSAEncryptionPadding.Pkcs1);
Assert.AreEqual(Encoding.UTF8.GetString(decryptData), "hi");
rsa2.ExportParameters(true);///How can I prevent exception here
}
Thanks all!
In .NET Core the RSACng object should be in an exportable state when you use ImportParameters, and it should be the case in .NET Framework 4.7.2 as well.
You can put into an exportable state as long as you change the export policy before using the key (by trying to call Export or doing a sign/decrypt/encrypt/verify operation). For example, this works:
using (RSA rsa1 = new RSACng(2048))
using (RSACng rsa2 = new RSACng())
{
rsa2.ImportParameters(rsa1.ExportParameters(true));
rsa2.Key.SetProperty(
new CngProperty(
"Export Policy",
BitConverter.GetBytes((int)CngExportPolicies.AllowPlaintextExport),
CngPropertyOptions.Persist));
RSAParameters params2 = rsa2.ExportParameters(true);
Console.WriteLine(params2.D.Length);
}
Using the NCRYPT_EXPORT_POLICY_PROPERTY described at https://msdn.microsoft.com/en-us/library/windows/desktop/aa376242(v=vs.85).aspx.

How to verify a signature in .net generated in Android

The problem is the following:
I generate the key in Android (Xamarin.Droid):
public IPublicKey CreateKey(string keyID)
{
/*KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
keyPairGenerator.initialize(
new KeyGenParameterSpec.Builder(
"key1",
KeyProperties.PURPOSE_SIGN)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS)
.build());
KeyPair keyPair = keyPairGenerator.generateKeyPair();
Signature signature = Signature.getInstance("SHA256withRSA/PSS");
signature.initSign(keyPair.getPrivate());
// The key pair can also be obtained from the Android Keystore any time as follows:
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
PrivateKey privateKey = (PrivateKey)keyStore.getKey("key1", null);
PublicKey publicKey = keyStore.getCertificate("key1").getPublicKey();*/
//App.Current.MainPage.DisplayAlert("Info", "Creating a new key pair", "Ok");
// UTILIZANDO RSA
KeyPairGenerator kpg =
KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, KEYSTORE_NAME);
kpg.Initialize(
new KeyGenParameterSpec.Builder(keyID,
KeyStorePurpose.Sign)
.SetSignaturePaddings(KeyProperties.SignaturePaddingRsaPss)
.SetDigests(KeyProperties.DigestSha1)
.Build()
);
KeyPair keyPair = kpg.GenerateKeyPair();
Log.Debug(TAG, "New key created for fingerprint authentication");
return keyPair.Public;
}
Then i generate a signature:
KeyStore.PrivateKeyEntry PKentry =
(KeyStore.PrivateKeyEntry)_keystore.GetEntry(keyID, null);
IPublicKey pk = (IPublicKey)PKentry.Certificate.PublicKey;
//this.pk = pk;
privKey = PKentry.PrivateKey;
//cipher.Init(Cipher.EncryptMode, privKey);
//byte[] output = cipher.DoFinal(Encoding.UTF8.GetBytes(input));
//String s = new string(cipher.DoFinal(input));
// signature
Signature sig = Signature.GetInstance("SHA1withRSA/PSS");
sig.InitSign(privKey);
byte[] inputDataToSign = Encoding.UTF8.GetBytes(input);
sig.Update(inputDataToSign);
byte[] signatureBytes = sig.Sign();
And i send the key and the signature to a ASP.net wep API 2 server.
Client side response generation:
RegistrationResponse registrationResponse = new RegistrationResponse();
string fcparams = Utils.Base64Encode(JsonConvert.SerializeObject(finalChallengeParams));
registrationResponse.fcParams = fcparams;
byte[] signedData = sign(fcparams, registrationRequest.username, facetID);
registrationResponse.signedData = signedData;
registrationResponse.Base64key = convertPublicKeyToString(publicKey);
...
...
private string convertPublicKeyToString(IPublicKey publicKey)
{
string publicKeyString = Base64.EncodeToString(publicKey.GetEncoded(), 0);
return publicKeyString;
}
I send it using Refit Nugget.
And this is the code i use when i receive the HTTPRequest on server side:
[Route("regResponse/")]
[HttpPost]
public IHttpActionResult ProcessClientRegistrationResponse([FromBody] RegistrationResponse registrationResponse)
{
//byte[] publicKeyBytes = Convert.FromBase64String(registrationResponse.Base64key);
byte[] publicKeyBytes = registrationResponse.Base64key;
AsymmetricKeyParameter asymmetricKeyParameter = PublicKeyFactory.CreateKey(publicKeyBytes);
RsaKeyParameters rsaKeyParameters = (RsaKeyParameters)asymmetricKeyParameter;
RSAParameters rsaParameters = new RSAParameters();
rsaParameters.Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned();
rsaParameters.Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned();
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(rsaParameters);
/*****/
string alg = rsa.SignatureAlgorithm;
byte[] signedData = registrationResponse.signedData;
byte[] fcParamsBytes = Encoding.UTF8.GetBytes(registrationResponse.fcParams);
RSACng rsaCng = new RSACng();
rsaCng.ImportParameters(rsaParameters);
SHA1Managed hash = new SHA1Managed();
byte[] hashedData;
hashedData = hash.ComputeHash(signedData);
/*********/
bool rsaCngDataOk1 = rsaCng.VerifyData(fcParamsBytes, signedData, HashAlgorithmName.SHA1, RSASignaturePadding.Pss);
bool rsaCngDataOk2 = rsaCng.VerifyData(fcParamsBytes, signedData, HashAlgorithmName.SHA1, RSASignaturePadding.Pss);
bool rsaCngDataOk3 = rsaCng.VerifyData(hashedData, signedData, HashAlgorithmName.SHA1, RSASignaturePadding.Pss);
bool rsaCngDataOk4 = rsaCng.VerifyData(hashedData, signedData, HashAlgorithmName.SHA1, RSASignaturePadding.Pss);
bool rsaCngHashOk1 = rsaCng.VerifyHash(hashedData, signedData, HashAlgorithmName.SHA1, RSASignaturePadding.Pss);
bool dataOK1 = rsa.VerifyData(fcParamsBytes, new SHA1CryptoServiceProvider(), signedData);
bool dataOk2 = rsa.VerifyData(fcParamsBytes, signedData, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
bool hashOk = rsa.VerifyHash(hashedData, CryptoConfig.MapNameToOID("SHA1"), signedData);
return Ok(true);
}
EVERY bool is wrong. I think the problem is clearly on the public key.
The questions are,
does the method publickey.encode() do what i think? I think it converts my public key to a byte[] representation (source: Android developer Key Info)
do i convert the received byte[] representation of the key to a correct RSA key?
Is there any problem on algorithms? I don't think so but we never know...
I don't find the solution. I searched for ways to import public keys from strings in .net or c# and for ways to export Android Public key to string or byte[] but there's no much help for this concrete questions...
#James K Polk gave me the solution.
Apparently C# doesn't work well with PSS padding. I just had to change it to PKCS1. And i changed to digest algorithm too to SHA512.

Categories