I'm facing a couple of issues with a Java app and another in C#. Here is the thing. There's a server application that receives request through HTTP, process it and sends
back the response. This server is writting in Java with Bouncy Castle and we use PKI to encrypt sensitive data in the request. We have many operations that the server recognizes
and one of the them is used to generate the the public and private keys used to exchange with the clients. Each client has a unique ID, so when this operation named
GetEncryptionKey is executed, it generates the private key and save it locally in the server and generates the public key which is sent back in PEM format, like this:
encryptionKey=-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAon1WDHdarN7yq0UOevzW
5PiFsSC8bEkTUOZ6X3RIth+RCU42pUj/Z8fp9T8rbWp8CqbhlFDxU4c+YucpGljC
7A10nkrPoBT0lpHEuXJiSgx+9qqsyo9q6GddhOpdMa+Z6VCfI+JCM3kdJNMH3r+o
i+WLPHLB8lxnfT2CHyZVQGhkzrH9fk1XhdenXxjtPGpwYBOsUZUwRt8EeW6JUwSI
mKXiXag0IViEcyAa2BvProkxklbQB3BczLHdXjIDwnE6u1aMA7pYPSkBtY6tuQ0F
5sNWXHsaKWON33MnbhlM7sieYDi9L4dWksala/m/mdIeHIXzX4ZCYdOhayWWKZ1N
HwIDAQAB
-----END PUBLIC KEY-----
This is working ok. The code used to generate the key is the following one:
private void getEncryptedKey() throws GWTranException {
PEMWriter pemWriter;
Security.addProvider(new BouncyCastleProvider());
KeyPair keyPair = null;
KeyPairGenerator keyPairGenerator = null;
try {
keyPairGenerator = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
StringWriter writer = new StringWriter();
try {
pemWriter = new PEMWriter(new PrintWriter(writer));
pemWriter.writeObject(keyPair.getPublic());
pemWriter.flush();
String pem = writer.toString();
savePrivateKey(keyPair, pem); // save the private key locally
} catch (IOException e) {
e.printStackTrace();
}
}
Now I have a client written in C# that connects to the server, retrieves the Public Key sent in the response and use it to encrypt a string that will travel encrypted.
This is the .NET code (very simple, as I'm just testing the functionality):
private string EncryptDataWithRSA(string data) {
string cryptedData = string.Empty;
RsaKeyParameters rsaParams;
using (var reader = new StringReader(txtKey.Text)) {
rsaParams = (RsaKeyParameters)new PemReader(reader).ReadObject();
reader.Close();
}
IAsymmetricBlockCipher eng = new RsaEngine();
eng = new OaepEncoding(eng);
eng.Init(true, rsaParams);
var dataBytes = Encoding.UTF8.GetBytes(data);
var cryptedDataBytes = eng.ProcessBlock(dataBytes, 0, dataBytes.Length);
cryptedData = Convert.ToBase64String(cryptedDataBytes);
return cryptedData;
}
Everything looks nice but the problem is that when the server (Java app) tries to decrypt the data I get an exception:
javax.crypto.BadPaddingException: data hash wrong
at org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi.engineDoFinal(Unknown Source)
at javax.crypto.Cipher.doFinal(DashoA13*..)
at com.verifone.gateway.security.RSAEncryptUtil.decrypt(RSAEncryptUtil.java:127)
at com.verifone.gateway.security.RSAEncryptUtil.decrypt(RSAEncryptUtil.java:152)
at com.verifone.gateway.preonline.PreOnlineJob.decryptData(PreOnlineJob.java:1661)
at com.verifone.gateway.preonline.PreOnlineJob.extractCardData(PreOnlineJob.java:989)
at com.verifone.gateway.preonline.PreOnlineJob.run(PreOnlineJob.java:288)
at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source)
at java.util.concurrent.FutureTask.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
This is working fine with another client writting in iOS but I don't have access to the source code. I'm writting a simulator in .NET but I'm not being able to
get the information decrypted correctly. On the server side this is part of the code used to decrypt the data:
public static byte[] decrypt(byte[] text, PrivateKey key) throws Exception
{
byte[] dectyptedText = null;
try {
// decrypt the text using the private key
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
cipher.init(Cipher.DECRYPT_MODE, key);
dectyptedText = cipher.doFinal(text);
}
catch (Exception e) {
//_log.error(e, e);
throw e;
}
return dectyptedText;
}
I'm out of ideas, I've tried everything. Do you see something I don't?
Thanks for your help.
Related
I'm writing a .NET 6 application for Windows that is intended to extract the private key from a PFX file containing an RSA cert/key bundle.
public static Boolean ToCertAndKey(String pfxFilePath, String? unlockPassword, String certFilePath, String keyFilePath, String? keyPassword, out String error) {
try {
error = String.Empty;
using var bundle = new X509Certificate2(pfxFilePath, unlockPassword);
RSA key = bundle.GetRSAPrivateKey();
Byte[] publicKeyBytes = key.ExportSubjectPublicKeyInfo();
Byte[] privateKeyBytes;
//We fail here.
if (String.IsNullOrEmpty(keyPassword)) {
privateKeyBytes = key.ExportPkcs8PrivateKey();
} else {
privateKeyBytes = key.ExportEncryptedPkcs8PrivateKey(keyPassword,
new PbeParameters(
PbeEncryptionAlgorithm.Aes256Cbc,
HashAlgorithmName.SHA256,
iterationCount: 1));
}
String encodedCert = new(PemEncoding.Write("PUBLIC KEY", publicKeyBytes));
File.WriteAllText(certFilePath, encodedCert);
String encodedKey = new(PemEncoding.Write("PRIVATE KEY", privateKeyBytes));
File.WriteAllText(keyFilePath, encodedKey);
return true;
} catch (Exception ex) {
error = $"An exception occurred: '{ex.Message}'\r\n\r\nStack Trace:\r\n{ex.StackTrace}";
return false;
}
}
It fails at both ExportPkcs8PrivateKey (When I don't specify a password to encrypt the key) and ExportEncryptedPkcs8PrivateKey (when I do) with the same exception text:
WindowsCryptographicException: The requested operation is not supported
I came across this answer however, I'm still receiving the same exception at RSA.ExportEncryptedPkcs8PrivateKey.
There doesn't appear to be anything wrong with the PFX files I've been testing with; I'm able to import them into my certstore via the UI or PowerShell with no issues.
Hoping someone else has run into this issue.
You need to mark the keys as exportable.
Change
using var bundle = new X509Certificate2(pfxFilePath, unlockPassword);
to
using var bundle = new X509Certificate2(pfxFilePath, unlockPassword, X509KeyStorageFlags.Exportable);
Environment: VS 2019, Core 3.1, C# 8.0
I'm getting the following error while trying to add a .cer and .key file to my httpClientHandler:
{"ASN1 corrupted data."}
Data: {System.Collections.ListDictionaryInternal}
HResult: -2146233087
HelpLink: null
InnerException: null
Message: "ASN1 corrupted data."
Source: "System.Security.Cryptography.Algorithms"
StackTrace: " at System.Security.Cryptography.Asn1.AsnReader.CheckExpectedTag(Asn1Tag tag, Asn1Tag expectedTag, UniversalTagNumber tagNumber)\r\n at System.Security.Cryptography.Asn1.AsnReader.ReadSequence(Asn1Tag expectedTag)\r\n at System.Security.Cryptography.Asn1.RSAPrivateKeyAsn.Decode(AsnReader reader, Asn1Tag expectedTag, RSAPrivateKeyAsn& decoded)\r\n at System.Security.Cryptography.Asn1.RSAPrivateKeyAsn.Decode(Asn1Tag expectedTag, ReadOnlyMemory`1 encoded, AsnEncodingRules ruleSet)\r\n at System.Security.Cryptography.Asn1.RSAPrivateKeyAsn.Decode(ReadOnlyMemory`1 encoded, AsnEncodingRules ruleSet)\r\n at System.Security.Cryptography.RSAKeyFormatHelper.FromPkcs1PrivateKey(ReadOnlyMemory`1 keyData, AlgorithmIdentifierAsn& algId, RSAParameters& ret)\r\n at System.Security.Cryptography.RSA.ImportRSAPrivateKey(ReadOnlySpan`1 source, Int32& bytesRead)\r\n at BnyMellon.Program.CreateFromCertFile(String cerFile, String keyFile) in C:\\Users\\bbernzweig.AD\\source\\repos\\HttpClientExample\\
BnyMellon\\Program.cs:line 150"
TargetSite: {Void CheckExpectedTag(System.Security.Cryptography.Asn1.Asn1Tag, System.Security.Cryptography.Asn1.Asn1Tag, System.Security.Cryptography.Asn1.UniversalTagNumber)}
Error is raised here on line rsa.ImportRSAPrivateKey(privateKeyBytes, out _);:
private static X509Certificate2 CreateFromCertFile(string cerFile, string keyFile)
{
try
{
var cert = new X509Certificate2 (cerFile);
var privateKeyBytes = LoadPrivateKeyBytes(keyFile);
using var rsa = RSA.Create();
rsa.ImportRSAPrivateKey(privateKeyBytes, out _);
var certWithKey = cert.CopyWithPrivateKey(rsa);
cert.Dispose();
return certWithKey;
}
catch(Exception e)
{
Console.WriteLine(e);
}
return null;
}
Called from:
var clientCertificate = new X509Certificate2();
clientCertificate = CreateFromCertFile(certificateFile, keyFile);
httpClientHandler.ClientCertificates.Add(clientCertificate);
Note: I'm able to make the request using both of these files via curl and Postman without any problem.
I'm trying to attaching both files to the request so not tied to this specific approach. If there is a better way I'm interested in hearing about it.
Super late to this, and faced the same problem ASN1 corrupted data and managed to resolve my problem from both your question and the question answered by #bartonjs
The advice on Create X509Certificate2 from Cert and Key, without making a PFX file question is
using (RSA rsa = RSA.Create())
{
rsa.ImportRSAPrivateKey(binaryEncoding, out _);
// do stuff with the key now
}
The clue for me was binaryEncoding, the answer is commented as part of the same question is...
if you had a PEM you need to "de-PEM" it, by extracting the contents between the BEGIN and END delimiters and running it through Convert.FromBase64String in order to get binaryEncoding
So based on your code... the following imports the PEM file without issue.
private static byte[] LoadPrivateKeyBytes(string keyFile)
{
// remove these lines
// -----BEGIN RSA PRIVATE KEY-----
// -----END RSA PRIVATE KEY-----
var pemFileData = File.ReadAllLines(keyFile).Where(x => !x.StartsWith("-"));
// Join it all together, convert from base64
var binaryEncoding = Convert.FromBase64String(string.Join(null, pemFileData));
// this is the private key byte data
return binaryEncoding;
}
private static X509Certificate2 CreateFromCertFile(string cerFile, string keyFile)
{
try
{
var cert = new X509Certificate2(cerFile);
var privateKeyBytes = LoadPrivateKeyBytes(keyFile);
using var rsa = RSA.Create();
rsa.ImportRSAPrivateKey(privateKeyBytes, out _);
var certWithKey = cert.CopyWithPrivateKey(rsa);
cert.Dispose();
return certWithKey;
}
catch (Exception e)
{
Console.WriteLine(e);
}
#pragma warning disable CS8603 // Possible null reference return.
return null;
#pragma warning restore CS8603 // Possible null reference return.
}
I am working on an ASP.NET MVC 5 web application inside VS 2012 and I am using IIS 8 to deploy the web application.
I have a security token which I am using to call a third party WebAPI. Currently inside my controller class, I define and use the token as follows:
string token = "D12356";
string url = currentURL + "resources?AUTHTOKEN=" + token;
Is there is a way to encrypt this value, so if anyone accesses the code inside VS or anyone reverse engineers the .dll files on IIS they won't see the actual token value, but will instead see the encrypted value?
Is there is a way to encrypt this value, so if anyone accesses the code inside VS or anyone reverse engineers the .dll files on IIS they won't see the actual token value, but will instead see the encrypted value?
Well, yes, you can embed an encrypted value in the code, but the problem is that whoever decompiles the library will also see how you decrypt it.
Since you're talking about ASP.NET, your web.config is just as vulnerable as your source code, so there's no added security there.
The solution is to either store the value somewhere secure outside of your web app (secured database?), or use some external value as part of your decryption process, like a certificate or other private key value.
The following class has the encryption and decryption process, through which one can encrypt or decrypt its data with the provision of some values i.e.
Key = string / byte[] to encrypt or decrypt the input
Input = the user required field on which he wants to apply cryptography
Please write this class as follows:
namespace SomeNameSpace
{
public enum CryptType { ENCRYPT, DECRYPT }
public enum CryptTechnique { AES, RC2, RIJ, DES, TDES }
public class Cryptography
{
public object Crypt(CryptType EncryptOrDecrypt, CryptTechnique CryptographicTechnique, object Input, string Key)
{
try
{
SymmetricAlgorithm SymAlgo; //This class is parent of all classes in CryptTechnique enums
switch (CryptographicTechnique)
{
case CryptTechnique.AES:
SymAlgo = new AesManaged();
break;
case CryptTechnique.RC2:
SymAlgo = new RC2CryptoServiceProvider();
break;
case CryptTechnique.RIJ:
SymAlgo = new RijndaelManaged();
break;
case CryptTechnique.DES:
SymAlgo = new DESCryptoServiceProvider();
break;
case CryptTechnique.TDES:
SymAlgo = new TripleDESCryptoServiceProvider();
break;
default:
return false;
}
SymAlgo.Key = UTF8Encoding.UTF8.GetBytes(Key);
SymAlgo.Padding = PaddingMode.PKCS7;
SymAlgo.Mode = CipherMode.ECB;
ICryptoTransform ICT = null;
byte[] resultArray;
if(EncryptOrDecrypt == CryptType.ENCRYPT)
{
ICT = SymAlgo.CreateEncryptor();
}
else if(EncryptOrDecrypt == CryptType.DECRYPT)
{
ICT = SymAlgo.CreateDecryptor();
}
if (Input is string)
{
byte[] inputArray = UTF8Encoding.UTF8.GetBytes(Input as string);
resultArray = ICT.TransformFinalBlock(inputArray, 0, inputArray.Length);
SymAlgo.Clear();
return Convert.ToBase64String(resultArray, 0, resultArray.Length);
}
else if (Input is byte[])
{
resultArray = ICT.TransformFinalBlock(Input as byte[], 0, (Input as byte[]).Length);
SymAlgo.Clear();
return resultArray;
}
return false;
}catch(Exception ex)
{
return ex.Message;
}
}
}
}
and in some controller where you want to encrypt or decrypt data, write there as
public ActionResult SomeAction()
{
string Key = "1234567890abcdef"; //key must have 16 chars, other wise you may get error "key size in not valid".
Password = "Secret";
Cryptography Crypt = new Cryptography();
EncryptedPassword = (string)Crypt.Crypt(CryptType.ENCRYPT, CryptTechnique.RIJ, Password, Key);
}
Here you will get the encrypted password in EncryptedPassword variable
We wrote a c# server app that muliple clients use a couple of years ago. Some use C++ and others use c# and others use python (They can all connect). A client is trying to use Java and just can't seem to get it to work.
The issue is the PublicKey representation.
In c# I don't send the raw byte [] publicKey, I convert it to a blob using RSACryptoServiceProvider.ExportCspBlob(). So in the server app I simply use rsaCSP.ImportCspBlob(publicKeyBlob);
QUESTION
How can I use Java to create a RSACryptoServiceProvider.ExportCspBlob() byte[] representation of the publicKey
C# SERVER CODE
public static bool VerifySignature(byte[] hash, byte[] signature, byte[] publicKeyBlob)
{
RSACryptoServiceProvider rsaCSP = new RSACryptoServiceProvider();
try
{
rsaCSP.ImportCspBlob(publicKeyBlob);
bool res = rsaCSP.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA1"), signature);
return res;
}
catch
{
return false;
}
finally
{
if (rsaCSP != null)
rsaCSP = null;
}
}
C# Client Code for public key that works
public static byte[] getPublicKeyBlob()
{
RSACryptoServiceProvider rsaCspPublic = (RSACryptoServiceProvider)getCertificate()
.PublicKey.Key;
return rsaCspPublic.ExportCspBlob(false);
}
Java Client Code - In Progress (Currently gets the raw byte [] public key
public static byte[] getPublicKeyBlob(){
try
{
byte[] ba = keystore.getCertificate("le-f0b649ee-4e25-4973-a185-efd5bd587c54")
.getPublicKey().getEncoded();
return ba;
}catch(Exception e){
}
return null;
}
If anyone can assist me it would be greatly appreciated.
It seems this is the way to do it (Only small issue is that Java puts one extra 00 byte which I just strip off in the server code)
public static byte[] getPublicKey(){
try
{
RSAPublicKey key = (RSAPublicKey)keystore.getCertificate("alias").getPublicKey();
return key.getModulus().toByteArray();
}catch(Exception e){
}
return null;
}
I am working on a project where I need to use a "public key" to encrypt a message using RSA algorithm. I was provided with a certificate and my first thought was to use Public Key from that certificate and after investigation I learned I need to use RSACryptoServiceProvider for encryption.
I have checked msdn and only method I thought I should use is RSACryptoServiceProvider.ImportCspBlob(byte[] keyBlob).
When I tried to use public key exported from certificate I was getting an error that the header data for certificate is invalid.
I know I can cast X509certificate2.PublicKey.Key to RSACryptoServiceProvider but from what I understood from my client is that going forward I will be given only a public key and not the certificate. This key will have to be saved in .xml configuration file.
So to summarize: Is there a way to generate an RSACryptoServiceProvider given only a certificate's public key?
You can try to look at this example: RSA public key encryption in C#
var publicKey = "<RSAKeyValue><Modulus>21wEnTU+mcD2w0Lfo1Gv4rtcSWsQJQTNa6gio05AOkV/Er9w3Y13Ddo5wGtjJ19402S71HUeN0vbKILLJdRSES5MHSdJPSVrOqdrll/vLXxDxWs/U0UT1c8u6k/Ogx9hTtZxYwoeYqdhDblof3E75d9n2F0Zvf6iTb4cI7j6fMs=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
var testData = Encoding.UTF8.GetBytes("testing");
using ( var rsa = new RSACryptoServiceProvider(1024))
{
try
{
// client encrypting data with public key issued by server
//
rsa.FromXmlString(publicKey);
var encryptedData = rsa.Encrypt(testData, true);
var base64Encrypted = Convert.ToBase64String(encryptedData);
}
finally
{
rsa.PersistKeyInCsp = false;
}
}
You are OK and following a good typical pattern. The Sender of the data does not need the private key.
The following may confirm some of the code you already have figured out.
The one line where I set the private key for the receiver/decoder I left out.
I took this from a test case I have in my build deploy stuff.
byte[] certBytAr; // This is the certificate as bianry in a .cer file (no private key in it - public only)
X509Certificate2 cert2 = new X509Certificate2(certBytAr);
string strToEncrypt = "Public To Private Test StackOverFlow PsudeoCode. Surfs Up at Secret Beach.";
byte[] bytArToEncrypt = Encoding.UTF8.GetBytes(strToEncrypt);
RSACryptoServiceProvider rsaEncryptor = (RSACryptoServiceProvider)cert2.PublicKey.Key;
byte[] dataNowEncryptedArray = rsaEncryptor.Encrypt(bytArToEncrypt, true);
// done - you now have encrypted bytes
//
// somewhere elxe ...
// this should decrpyt it - simulate the destination which will decrypt the data with the private key
RSACryptoServiceProvider pk = // how this is set is complicated
// set the private key in the x509 oobject we created way above
cert2.PrivateKey = pk;
RSACryptoServiceProvider rsaDecryptor = (RSACryptoServiceProvider)cert2.PrivateKey;
byte[] dataDecrypted = rsaDecryptor.Decrypt(dataNowEncryptedArray, true);
Console.WriteLine(" encrypt 1 Way Intermediate " + BitConverter.ToString(dataDecrypted));
string strDecodedFinal = Encoding.UTF8.GetString(dataDecrypted);
if (strDecodedFinal == strToEncrypt)
{
}
else
{
Console.WriteLine(" FAILURE OF ENCRYPTION ROUND TRIP IN SIMPLE TEST (Direction: Public to Private). No Surfing For You ");
}