I have the below Java code:
String getSignature(String jsonBody) throws IOException {
try (FileInputStream ksFile = new FileInputStream(keyStorePath)) {
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(ksFile, keyPassword.toCharArray());
// there may be more than one key in the keystore...here we are picking the 'dev' key
var pk = ks.getKey("dev", keyPassword.toCharArray());
Signature ps = Signature.getInstance("SHA256withRSA");
ps.initSign((PrivateKey) pk);
ps.update(jsonBody.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(ps.sign());
} catch (KeyStoreException | SignatureException | NoSuchAlgorithmException |
CertificateException | InvalidKeyException | UnrecoverableKeyException e) {
Log.error("signature generation failed", e);
throw new RuntimeException("signature failure");
}
}
How do I convert it to C#? I have attempted the following and it is not working:
string filePath = #"C:\projects\private.ppk";
using (var fileStreem = System.IO.File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
var certificate = new X509Certificate2Collection();
certificate.Import(filePath, (string)null, X509KeyStorageFlags.Exportable);
var pk = certificate[0].PrivateKey;
using (var rsa = RSA.Create())
{
rsa.FromXmlString(pk.ToXmlString(true));
var ps = new RSACryptoServiceProvider();
ps.ImportParameters(rsa.ExportParameters(true));
var hash = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(jsonBody));
return Convert.ToBase64String(ps.SignHash(hash, CryptoConfig.MapNameToOID("SHA256")));
}
}
I am getting an error when I try this. I was given the code in Java, which is working, but I need to put it to C#. It is generating an error. The Java I got is here git link.
Related
I have to interface with a system written in java that encrypts data using the following java method:
public final void rsaEncrypt(String data, String filePath) {
try {
Base64.Encoder encoder = Base64.getEncoder();
PublicKey pubKey = readKeyFromFile("/" + Constants.PUBLIC_KEY_FILE_NAME, filePath);
Cipher cipher = Cipher.getInstance(Constants.RSA_INSTANCE);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] cipherData = cipher.doFinal(data.getBytes("UTF-8"));
writeToFile(Constants.ENCRYPTED_STRING_FILE_NAME, filePath, encoder.encodeToString(cipherData));
} catch (BadPaddingException | InvalidKeyException | NoSuchPaddingException | NoSuchAlgorithmException
| IllegalBlockSizeException | UnsupportedEncodingException e) {
if (LOG.isErrorEnabled())
LOG.error("Error encrypting String.", e);
throw new EncryptionException("Error encrypting data", e);
}
}
My code is written in c++ using openssl:
std::string prv =
"-----BEGIN RSA PRIVATE KEY-----\n"
// cut key data
"-----END RSA PRIVATE KEY-----\n";
BIO *bio = BIO_new_mem_buf((void*)prv.c_str(), -1);
RSA* rsaPrivKey = PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, NULL);
if (!rsaPrivKey)
printf("ERROR: Could not load PRIVATE KEY! PEM_read_bio_RSAPrivateKey FAILED: %s\n", ERR_error_string(ERR_get_error(), NULL));
BIO_free(bio);
// where enc[] holds the Base64 decoded data, but len becomes -1 and no data is decoded.
int len = RSA_private_decrypt(64,(unsigned char *)&enc, (unsigned char *)&dec, private_key_, RSA_PKCS1_PADDING);
I can decode data I encrypt myself with the public key, but don't seem to match the java options for this.
Any suggestions?
These is no answer here as like my comment said, it turns out the Java code was incorrect in creating their keys, which were not matching the .pem files I was given.
I am trying to store multiple private keys in a JKS file using the Java KeyStore library. I created a method which writes and reads to the JKS file but the private key isn't being saved in the file.
When I store something into the KeyStore I can get all the aliases in the keystore and the new key is there. Once the method is closed and the same key is attempted to be pulled it does not find the key.
Main.java
public static void main(String[] args) throws Exception {
//Create keys
main m = new main();
m.getOrSetPrivateKey("123", "123", privateKey, false);
PrivateKey p = m.getOrSetPrivateKey("123", "123", null, true);
if (p.equals(c.getPriv_key()))
System.err.println("Equal");
else
System.err.println("Not equal !!!!!!!!");
}
private synchronized PrivateKey getOrSetPrivateKey(String alias, String id, PrivateKey c, boolean read ) throws InterruptedException, IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException, InvalidKeySpecException, NotSupportedException, UnrecoverableKeyException {
PrivateKey key = null;
InputStream inpusStream = new FileInputStream(getFile2(Constants.JKS_PRIVATE_FILE_NAME));
KeyStore keyStore = null;
try {
keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(inpusStream, Constants.JKS_PRIVATE_FILE_PASSWORD);
} finally {
if (inpusStream != null)
inpusStream.close();
}
Enumeration<String> s = keyStore.aliases();
while (s.hasMoreElements())
System.err.println("[ " + s.nextElement() + " ]");
//Generate password for this private key
char [] pass = getKeyPassword(c, alias, id);
if (read == true) { //If reading/getting private key from file store
boolean isKeyEntry = keyStore.isKeyEntry(alias);//Check if there is a key with the alias deviceSerialnumber
if (!isKeyEntry) {//No key with this alias exists
throw new KeyStoreException("No key with alias " + alias + " exists!");
}
key = (PrivateKey) keyStore.getKey(alias, pass);
} else { //Writing/ saving key to the file store
keyStore.setKeyEntry(alias, c , pass, new Certificate[] { createCertificate() });
FileOutputStream out = new FileOutputStream(new File(Constants.JKS_PRIVATE_FILE_NAME), true);
try {
keyStore.store(out, pass);
System.out.println("Alias exists = " + keyStore.containsAlias(alias));
} finally {
if (out != null)
out.close();
}
}
s = keyStore.aliases();
while (s.hasMoreElements())
System.err.println("( " + s.nextElement() + " )");
return key;
}
Output:
[ mykey ]
( 123 )
( mykey )
Alias exists = true
[ mykey ]
Exception in thread "main" java.security.KeyStoreException: No key with alias 123 exists!
Why is the key not being saved to the JKS file file?
You are appending to the existing keystore instead of replacing it because you are passing "true" to the FileOutputStream constructor.
FileOutputStream out = new FileOutputStream(new File(Constants.JKS_PRIVATE_FILE_NAME), true);
Replace the line above with the following:
FileOutputStream out = new FileOutputStream(new File(Constants.JKS_PRIVATE_FILE_NAME));
The problem was in the FileOutputStream was pointing to the wrong file.
FileOutputStream out = new FileOutputStream(new File(Constants.JKS_PRIVATE_FILE_NAME), true);
Should be using the getFile2 method like this:
FileOutputStream out = new FileOutputStream(getFile2(Constants.JKS_PRIVATE_FILE_NAME));
As Palamino pointed out, don't need to include true in the FileOutputStream constructor.
Also the key store should have been using the JKS file password, not the password that is generated by getKeyPassword().
Changed this:
keyStore.store(out, pass);
To use the JKS file password, like this:
keyStore.store(out, Constants.JKS_PRIVATE_FILE_PASSWORD);
The following method is deprecated
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(this)
.setAlias(alias)
.setSubject(new X500Principal("CN=Sample Name, O=Android Authority"))
.setSerialNumber(BigInteger.ONE)
.setStartDate(start.getTime())
.setEndDate(end.getTime())
.build();
generator.initialize(spec);
The replacement I came upon looks like this
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
generator.initialize(new KeyGenParameterSpec.Builder
(alias, KeyProperties.PURPOSE_SIGN)
.setDigests(KeyProperties.DIGEST_SHA256)
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.build());
Although I am able to use this to generate a keypair entry and encrypt the value, I am unable to decrypt it
public void encryptString(String alias) {
try {
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);
RSAPublicKey publicKey = (RSAPublicKey) privateKeyEntry.getCertificate().getPublicKey();
String initialText = startText.getText().toString();
if(initialText.isEmpty()) {
Toast.makeText(this, "Enter text in the 'Initial Text' widget", Toast.LENGTH_LONG).show();
return;
}
//Security.getProviders();
Cipher inCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidKeyStoreBCWorkaround");
inCipher.init(Cipher.ENCRYPT_MODE, publicKey);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
CipherOutputStream cipherOutputStream = new CipherOutputStream(
outputStream, inCipher);
cipherOutputStream.write(initialText.getBytes("UTF-8"));
cipherOutputStream.close();
byte [] vals = outputStream.toByteArray();
encryptedText.setText(Base64.encodeToString(vals, Base64.DEFAULT));
} catch (Exception e) {
Toast.makeText(this, "Exception " + e.getMessage() + " occured", Toast.LENGTH_LONG).show();
Log.e(TAG, Log.getStackTraceString(e));
}
}
public void decryptString(String alias) {
try {
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);
Cipher output = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidKeyStoreBCWorkaround");
output.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey());
String cipherText = encryptedText.getText().toString();
CipherInputStream cipherInputStream = new CipherInputStream(
new ByteArrayInputStream(Base64.decode(cipherText, Base64.DEFAULT)), output);
ArrayList<Byte> values = new ArrayList<>();
int nextByte;
while ((nextByte = cipherInputStream.read()) != -1) {
values.add((byte)nextByte);
}
byte[] bytes = new byte[values.size()];
for(int i = 0; i < bytes.length; i++) {
bytes[i] = values.get(i).byteValue();
}
String finalText = new String(bytes, 0, bytes.length, "UTF-8");
decryptedText.setText(finalText);
} catch (Exception e) {
Toast.makeText(this, "Exception " + e.getMessage() + " occured", Toast.LENGTH_LONG).show();
Log.e(TAG, Log.getStackTraceString(e));
}
in the decrypt method, the following command fails:
Cipher output = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidKeyStoreBCWorkaround");
output.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey());
with
java.security.InvalidKeyException: Keystore operation failed
I think it has to do with the KeyGenParamaterSpec.Builder has incorrect conditions, similarly that the encrypt Cipher types are incorrect strings, same thing in the decrypt function.
But this can all be traced back to the use of the new KeygenParameterSpec.Builder, as using the older deprecated method allows me to encrypt and decrypt.
How to fix?
As Alex mentioned one missing piece is KeyProperties.PURPOSE_DECRYPT other one is setSignaturePaddings instead for that you have to use setEncryptionPaddings method. Here is the sample snippet.
new KeyGenParameterSpec.Builder(ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
// other options
.build()
Refer documentation for more information.
It's hard to be 100% sure given that you didn't provide a full stack trace of the exception.
Your code generates the private key such that it is only authorized to be used for signing, not decrypting. Encryption works fine because it does not use the private key -- it uses the public key and Android Keystore public keys can be used without any restrictions. Decryption fails because it needs to use the private key, but your code did not authorize the use of the private key for decryption.
It looks like the immediate fix is to authorize the private key to be used for decryption. Thia is achieved by listing KeyProperties.PURPOSE_DECRYPT when invoking the KeyGenParameterSpec.Builder constructor. If the key shouldn't be used for signing, remove KeyProperties.PURPOSE_SIGN from there as well as remove setSignaturePaddings.
You'll also need to authorize the private key use with PKCS1Padding: invoke setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
I have a function that successfully reads a openssl formatted private key:
static AsymmetricKeyParameter readPrivateKey(string privateKeyFileName)
{
AsymmetricCipherKeyPair keyPair;
using (var reader = File.OpenText(privateKeyFileName))
keyPair = (AsymmetricCipherKeyPair)new PemReader(reader).ReadObject();
return keyPair.Private;
}
and returns an AsymmetricKeyParameter which is then used to decrypt an encrypted text.
Below is the decrypt code:
public static byte[] Decrypt3(byte[] data, string pemFilename)
{
string result = "";
try {
AsymmetricKeyParameter key = readPrivateKey(pemFilename);
RsaEngine e = new RsaEngine();
e.Init(false, key);
//byte[] cipheredBytes = GetBytes(encryptedMsg);
//Debug.Log (encryptedMsg);
byte[] cipheredBytes = e.ProcessBlock(data, 0, data.Length);
//result = Encoding.UTF8.GetString(cipheredBytes);
//return result;
return cipheredBytes;
} catch (Exception e) {
Debug.Log ("Exception in Decrypt3: " + e.Message);
return GetBytes(e.Message);
}
}
These work in C# using bouncy castle library and I get the correct decrypted text. However, when I added this to Java, the PEMParser.readObject() returns an object of type PEMKeyPair instead of AsymmetricCipherKeyPair and java throws an exception trying to cast it. I checked in C# and it is actually returning AsymmetricCipherKeyPair.
I don't know why Java behaves differently but I hope someone here can help how to cast this object or read the privatekey file and decrypt successfully. I used the same public and privatekey files in both C# and Java code so I don't think the error is from them.
Here for reference the Java version of how I'm reading the privatekey:
public static String readPrivateKey3(String pemFilename) throws FileNotFoundException, IOException
{
AsymmetricCipherKeyPair keyParam = null;
AsymmetricKeyParameter keyPair = null;
PEMKeyPair kp = null;
//PrivateKeyInfo pi = null;
try {
//var fileStream = System.IO.File.OpenText(pemFilename);
String absolutePath = "";
absolutePath = Encryption.class.getProtectionDomain().getCodeSource().getLocation().getPath();
absolutePath = absolutePath.substring(0, (absolutePath.lastIndexOf("/")+1));
String filePath = "";
filePath = absolutePath + pemFilename;
File f = new File(filePath);
//return filePath;
FileReader fileReader = new FileReader(f);
PEMParser r = new PEMParser(fileReader);
keyParam = (AsymmetricCipherKeyPair) r.readObject();
return keyParam.toString();
}
catch (Exception e) {
return "hello: " + e.getMessage() + e.getLocalizedMessage() + e.toString();
//return e.toString();
//return pi;
}
}
The Java code has been updated to a new API, which is yet to be ported across to C#. You could try the equivalent (but now deprecated) Java PEMReader class. It will return a JCE KeyPair though (part of the reason for the change was because the original version worked only with JCE types, not BC lightweight classes).
If using PEMParser, and you get back a PEMKeyPair, you can use org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter.getKeyPair to get a JCE KeyPair from it. Ideally there would be a BCPEMKeyConverter, but it doesn't appear to have been written yet. In any case, it should be easy to make an AsymmetricCipherKeyPair:
PEMKeyPair kp = ...;
AsymmetricKeyParameter privKey = PrivateKeyFactory.createKey(kp.getPrivateKeyInfo());
AsymmetricKeyParameter pubKey = PublicKeyFactory.createKey(kp.getPublicKeyInfo());
new AsymmetricCipherKeyPair(pubKey, privKey);
Those factory classes are in the org.bouncycastle.crypto.util package.
I'm writing a program that uses RSA for various tasks.
I know how to generate and write the key pair to file, but I cannot load the encrypted (AES-256-CFB) key pair to a KeyPair object.
So the question is: how do I load/decrypt an encrypted PEM key pair as a java.security.KeyPair object using the BouncyCastle library?
Thanks.
Generation/export code:
public void generateKeyPair(int keysize, File publicKeyFile, File privateKeyFile, String passphrase) throws FileNotFoundException, IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
SecureRandom random = new SecureRandom();
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "BC");
generator.initialize(keysize, random);
KeyPair pair = generator.generateKeyPair();
Key pubKey = pair.getPublic();
PEMWriter pubWriter = new PEMWriter(new FileWriter(publicKeyFile));
pubWriter.writeObject(pubKey);
pubWriter.close();
PEMWriter privWriter = new PEMWriter(new FileWriter(privateKeyFile));
if (passphrase == null) {
privWriter.writeObject(pair);
} else {
PEMEncryptor penc = (new JcePEMEncryptorBuilder("AES-256-CFB"))
.build(passphrase.toCharArray());
privWriter.writeObject(pair, penc);
}
privWriter.close();
}
I am assuming that you have set BouncyCastle as the security provider, for example with:
Security.addProvider(new BouncyCastleProvider());
The code you provided creates two key files, one for the private key and one for the public key. However, the public key is implicitly contained in the private key, so we only have to read the private key file to reconstruct the key pair.
The main steps then are:
Creating a PEMParser to read from the key file.
Create a JcePEMDecryptorProvider with the passphrase required to decrypt the key.
Create a JcaPEMKeyConverter to convert the decrypted key to a KeyPair.
KeyPair loadEncryptedKeyPair(File privateKeyFile, String passphrase)
throws FileNotFoundException, IOException {
FileReader reader = new FileReader(privateKeyFile);
PEMParser parser = new PEMParser(reader);
Object o = parser.readObject();
if (o == null) {
throw new IllegalArgumentException(
"Failed to read PEM object from file!");
}
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
if (o instanceof PEMKeyPair) {
PEMKeyPair keyPair = (PEMKeyPair)o;
return converter.getKeyPair(keyPair);
}
if (o instanceof PEMEncryptedKeyPair) {
PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair)o;
PEMDecryptorProvider decryptor =
new JcePEMDecryptorProviderBuilder().build(passphrase.toCharArray());
return converter.getKeyPair(encryptedKeyPair.decryptKeyPair(decryptor));
}
throw new IllegalArgumentException("Invalid object type: " + o.getClass());
}
Example usage:
File privKeyFile = new File("priv.pem");
String passphrase = "abc";
try {
KeyPair keyPair = loadEncryptedKeyPair(privKeyFile, passphrase);
} catch (IOException ex) {
System.err.println(ex);
}
Reference: BouncyCastle unit test for key parsing (link).