Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decrypt passphrase protected PEM containing private key

I have the following method that creates an Encrypted Private Key using Bouncy Castle for C#:

public string GetPrivateKey(AsymmetricCipherKeyPair keyPair, string password)
{
    var generator = new Pkcs8Generator(keyPair.Private, Pkcs8Generator.PbeSha1_3DES);
    generator.IterationCount = 4;
    generator.Password = password.ToCharArray();
    var pem = generator.Generate();

    TextWriter textWriter = new StringWriter();
    PemWriter pemWriter = new PemWriter(textWriter);
    pemWriter.WriteObject(pem);
    pemWriter.Writer.Flush();
    string privateKey = textWriter.ToString();
    return privateKey;
}

Which looks like this:

-----BEGIN ENCRYPTED PRIVATE KEY-----
...
-----END ENCRYPTED PRIVATE KEY-----

What I don't know is how to consume the password used to encrypt the private key in my Decrypt method. Right now, without knowing how to "decrypt" my private key using he password, I get the following Exception:

Org.BouncyCastle.OpenSsl.PemException : problem creating ENCRYPTED private key: System.NullReferenceException: Object reference not set to an instance of an object. at Org.BouncyCastle.OpenSsl.PemReader.ReadPrivateKey(PemObject pemObject)

Here is the code for the Decrypt method:

public string Decrypt(string base64Input, string privateKey, string password)
{
    var bytesToDecrypt = Convert.FromBase64String(base64Input);

    //get a stream from the string
    AsymmetricCipherKeyPair keyPair;
    var decryptEngine = new Pkcs1Encoding(new RsaEngine());

    using (var txtreader = new StringReader(privateKey))
    {
        var obj = new PemReader(txtreader).ReadObject();
        keyPair = (AsymmetricCipherKeyPair) obj;

        decryptEngine.Init(false, keyPair.Private);
    }

    var decrypted = Encoding.UTF8.GetString(decryptEngine.ProcessBlock(bytesToDecrypt, 0, bytesToDecrypt.Length));
    return decrypted;
}
like image 428
blgrnboy Avatar asked Jun 26 '17 19:06

blgrnboy


1 Answers

It seems to me that you need to decrypt the private key to use it. Currently your password parameter isn't used. Unfortunately it doesn't seem to be all that easy to find out how do this.


Bouncy Castle, as many other Java API's, use a password handler to retrieve the password. The reason to do this is to allow the program to ask the user for the password only when it is required. This allows the program to leave the password in memory for the shortest amount of time.

So to allow for decryption, use the following constructor:

PemReader(TextReader reader, IPasswordFinder pFinder);

with an implementation of IPasswordFinder (Bouncy Castle for C# is mainly a Java port, otherwise a delegate would probably have been used).


For your convenience, the code. This code also reconstructs the entire key pair, not just the private key.

Import statements:

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using System.IO;

the decoder:

private static AsymmetricCipherKeyPair DecodePrivateKey(string encryptedPrivateKey, string password)
{
    TextReader textReader = new StringReader(encryptedPrivateKey);
    PemReader pemReader = new PemReader(textReader, new PasswordFinder(password));
    object privateKeyObject = pemReader.ReadObject();
    RsaPrivateCrtKeyParameters rsaPrivatekey = (RsaPrivateCrtKeyParameters)privateKeyObject;
    RsaKeyParameters rsaPublicKey = new RsaKeyParameters(false, rsaPrivatekey.Modulus, rsaPrivatekey.PublicExponent);
    AsymmetricCipherKeyPair kp = new AsymmetricCipherKeyPair(rsaPublicKey, rsaPrivatekey);
    return kp;
}

required helper class:

private class PasswordFinder : IPasswordFinder
{
    private string password;

    public PasswordFinder(string password)
    {
        this.password = password;
    }


    public char[] GetPassword()
    {
        return password.ToCharArray();
    }
}

Note that normally you should only use char[] instead of string for passwords as char[] can be cleared after use, while string cannot.

Now you have the private key decryption should be easy.

like image 136
Maarten Bodewes Avatar answered Nov 17 '22 00:11

Maarten Bodewes