【问题标题】:Encrypt and Decrypt using PFX on multiple servers在多台服务器上使用 PFX 加密和解密
【发布时间】:2019-07-29 09:19:09
【问题描述】:

我目前正在尝试构建一个系统来加密客户数据并将其添加到另一台服务器上的远程 MSMQ 队列中。然后,每 X 分钟运行一次的作业会获取数据,该作业将尝试解密数据并对其进行处理。

要求我们必须使用 .PFX 证书进行加密/解密(我知道这不是最有效的做事方式,但有要求,我无法更改此设置)。

我目前正在使用使用 Open-SSL 的自签名证书:

openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out certificate.pem

openssl pkcs12 -inkey key.pem -in certificate.pem -export -out certificate.p12

我已成功加密数据,但每次尝试解密时,都会出现一般的“参数不正确”异常。

为了加载证书,我们将 .PFX 文件保存在机器本地,并使用 X509Certificate2 类导入它并使用它来加密和解密。这是我正在使用的辅助类的简化版

public static string EncryptData(string data)
{
    var certificate = GetCertificate();

    using (var rsa = certificate.PublicKey.Key as RSACryptoServiceProvider)
    {
        var dataBytes = Convert.FromBase64String(data);
        var encryptedBytes = rsa.Encrypt(dataBytes, false);

        return Convert.ToBase64String(encryptedBytes);
    }
}

public static string DecryptData(string data)
{
    var certificate = GetCertificate();

    using (var rsa = certificate.PrivateKey as RSACryptoServiceProvider)
    {
        var dataBytes = Convert.FromBase64String(data);
        var decryptedBytes = rsa.Decrypt(dataBytes, false);

        return Convert.ToBase64String(decryptedBytes);
    }
}

private static X509Certificate2 GetCertificate()
{   
    var certificate = new X509Certificate2();
    certificate.Import("certificatePath", "certificatePassword", X509KeyStorageFlags.PersistKeySet);
    return certificate;
}

错误总是发生在“rsa.Decrypt()”调用上。

我尝试了以下方法:

  • 加密后立即在我的“EncryptData”方法中调用“rsa.Decrypt()”。这没有问题,并且“rsa.Decrypt”给了我与原始数据字节相同的字节。

  • 在“EncryptData”调用之后直接调用“DecryptData”方法。发生同样的问题,我得到“参数不正确”的异常

这就是为什么我怀疑创建了一个“新”X509Certificate2 的事实,即私钥不再相同并且不能再解密数据。

请注意,我不是安全专家,也没有使用过 X509 证书或任何加密技术,所以我有点超出我的深度,可能正在做一些非常愚蠢的事情,所以如果我是,请告诉我。

更新 1 (08/03/2019)

已根据@bartonjs 给出的建议点 1-3 和 5 更新了代码代码

public static string EncryptData(string data)
{
    var certificate = GetCertificate();

    using (var rsa = certificate.GetRSAPublicKey())
    {
        var dataBytes = Convert.FromBase64String(data);
        var encryptedBytes = rsa.Encrypt(dataBytes, RSAEncryptionPadding.OaepSHA1);

        return Convert.ToBase64String(encryptedBytes);
    }
}

public static string DecryptData(string data)
{
    var certificate = GetCertificate();

    using (var rsa = certificate.GetRSAPrivateKey())
    {
        var dataBytes = Convert.FromBase64String(data);
        var decryptedBytes = rsa.Decrypt(dataBytes, RSAEncryptionPadding.OaepSHA1);

        return Convert.ToBase64String(decryptedBytes);
    }
}

private static X509Certificate2 GetCertificate()
{   
    var certificate = new X509Certificate2("certificatePath", "certificatePassword", X509KeyStorageFlags.PersistKeySet);
    return certificate;
}

添加错误信息:

Message: The parameter is incorrect.

Stack Trace:
 at System.Security.Cryptography.NCryptNative.DecryptData[T](SafeNCryptKeyHandle key, Byte[] data, T& paddingInfo, AsymmetricPaddingMode paddingMode, NCryptDecryptor`1 decryptor)
 at System.Security.Cryptography.NCryptNative.DecryptDataOaep(SafeNCryptKeyHandle key, Byte[] data, String hashAlgorithm)
 at System.Security.Cryptography.RSACng.Decrypt(Byte[] data, RSAEncryptionPadding padding)

【问题讨论】:

    标签: c# encryption x509certificate2 pfx


    【解决方案1】:

    很抱歉,这里几乎所有内容都是错误的。

    获取证书

    private static X509Certificate2 GetCertificate()
    {   
        var certificate = new X509Certificate2();
        certificate.Import("certificatePath", "certificatePassword", X509KeyStorageFlags.PersistKeySet);
        return certificate;
    }
    

    您正在使用 PersistKeySet 进行导入,因此您的硬盘驱动器正在慢慢填满。见What is the rationale for all the different X509KeyStorageFlags?

    此外,您正在使用 certificate.Import,这在 .NET Core 中不可用(因为突变 X509Certificate2 对象是意外的)。只需使用构造函数。所以整个方法应该是

    private static X509Certificate2 GetCertificate()
    {
        // Assuming you do nothing else with the certificate than what's shown here,
        // EphemeralKeySet will work for you (except on macOS).
        return new X509Certificate2(path, password, X509KeyStorageFlags.EphemeralKeySet);
    }
    

    加密数据

    public static string EncryptData(string data)
    {
        var certificate = GetCertificate();
    
        using (var rsa = certificate.PublicKey.Key as RSACryptoServiceProvider)
        {
            var dataBytes = Convert.FromBase64String(data);
            var encryptedBytes = rsa.Encrypt(dataBytes, false);
    
            return Convert.ToBase64String(encryptedBytes);
        }
    }
    

    这里有几个问题。

    1) PrivateKey 是一个共享属性,因此如果它被多次读取,您就会将该对象从另一个调用者下释放出来。

    2) 如果您碰巧获得了非 RSA 证书,则不会丢弃它

    3) 您正在使用 PrivateKey,它不支持支持现代选项的更好的 RSA 或 DSA 类。

    4) 您是证书的唯一处理者,但没有处置它。也许您的所有权语义可以更清楚。

    从安全角度来看,5) 您使用的是 PKCS#1 填充而不是 OAEP

    从数据的角度来看,同样是 6)为什么要加密的是 base64 数据而不是原始数据?

    我不会解决#s 4-6。

    public static string EncryptData(string data)
    {
        var certificate = GetCertificate();
    
        using (RSA rsa = certificate.GetRSAPublicKey())
        {
            var dataBytes = Convert.FromBase64String(data);
            var encryptedBytes = rsa.Encrypt(dataBytes, RSAEncryptionPadding.Pkcs1);
    
            return Convert.ToBase64String(encryptedBytes);
        }
    }
    

    在这种情况下,将私钥放在 using 语句中是正确的,GetRSAPrivateKey() 方法总是返回一个新对象。

    解密数据

    解密应该与加密类似。

    如果在所有这些之后,您仍然遇到异常,请包括确切的消息和堆栈跟踪(至少从调用 RSA.Decrypt 到抛出它的位置的部分)

    【讨论】:

    • 感谢您的回复@bartonjs 我已经查看了您的回复,并根据您的建议对代码进行了一些更新,并更新了原始问题。有一点我无法更新,就是将“X509KeyStorageFlags”设置为“EphemeralKeySet”,不幸的是,这对我们来说是不可能的,因为我们正在使用.Net 4.6.1(由于依赖于旧的遗留代码和旧服务器)并且它似乎仅在 4.7.2 以后可用。还有一点我要补充的是,我们没有使用 .Net Core,但已经对您推荐的导入进行了更改
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-20
    • 1970-01-01
    • 2018-05-21
    • 1970-01-01
    • 1970-01-01
    • 2013-01-15
    相关资源
    最近更新 更多