【问题标题】:Decrypt passphrase protected PEM containing private key解密包含私钥的密码保护 PEM
【发布时间】:2023-11-10 18:09:02
【问题描述】:

我有以下使用 C# 的 Bouncy Castle 创建加密私钥的方法:

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;
}

看起来像这样:

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

我不知道如何在我的 Decrypt 方法中使用用于加密私钥的密码。现在,在不知道如何使用password“解密”我的私钥的情况下,我得到以下异常:

Org.BouncyCastle.OpenSsl.PemException:创建加密的问题 私钥:System.NullReferenceException:未设置对象引用 到一个对象的实例。在 Org.BouncyCastle.OpenSsl.PemReader.ReadPrivateKey(PemObject pemObject)

下面是 Decrypt 方法的代码:

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;
}

【问题讨论】:

    标签: c# encryption bouncycastle private-key pem


    【解决方案1】:

    在我看来,您需要解密私钥才能使用它。当前未使用您的密码参数。不幸的是,要弄清楚如何做到这一点似乎并不那么容易。


    Bouncy Castle 与许多其他 Java API 一样,使用密码处理程序来检索密码。这样做的原因是允许程序仅在需要时才向用户询问密码。这允许程序将密码留在内存中的时间最短。

    所以要允许解密,请使用以下构造函数:

    PemReader(TextReader reader, IPasswordFinder pFinder);
    

    使用IPasswordFinder 的实现(C# 的 Bouncy Castle 主要是 Java 端口,否则可能会使用委托)。


    为方便起见,代码。此代码还重构了整个密钥对,而不仅仅是私钥。

    导入语句:

    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;
    

    解码器:

    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;
    }
    

    所需的帮助类:

    private class PasswordFinder : IPasswordFinder
    {
        private string password;
    
        public PasswordFinder(string password)
        {
            this.password = password;
        }
    
    
        public char[] GetPassword()
        {
            return password.ToCharArray();
        }
    }
    

    请注意,密码通常只能使用char[] 而不是string,因为char[] 可以在使用后清除,而string 则不能。

    现在你有了私钥解密应该很容易了。

    【讨论】:

    • 更多测试(例如,在将对象转换为 RsaPrivateCrtKeyParameters 之前,当然也不会受到伤害。
    • 希望我能投票 10 次!支持 OP 和 AA。谢谢!