告诉 dotnet 使用哪个私钥来解密传入的 XML 文档?

Telling dotnet which private key to use to decrypt incoming XML documents?

这是一个关于 XML .net 环境和跨平台中的加密密钥管理的问题。

我正在使用 SAML 2.0,其中一台机器——身份提供者——发送另一台机器——服务提供者——一条称为断言的 XML 消息。断言指示服务提供商向其中指定的用户授予访问权限。

所有断言均使用身份提供者持有的私钥进行签名。一些断言也使用身份提供者持有的 public 密钥加密,在接收服务提供商处使用私钥解密。

麻烦的是,这些加密的断言文档中有一些没有携带用于加密它们的密钥对的任何标识。 Microsoft Dot Net EncryptedXML class 无法(我可以看到)告诉它 "use a particular key pair to decrypt this document"。

SAML 的工作方式是,服务提供商通过了解哪个身份提供商发送消息来识别私钥。

非跨平台文档没有问题。使用 dotnet EncryptedXML class 加密的文档本身以 X509 证书结束 - 实际上是 public 一半的密钥对 - 嵌入其中的纯文本以及密文会话密钥。解密方法使用该证书来识别私钥,以便它可以解密会话密钥,然后解密消息。

问题在于在其他平台上加密的文档。他们不携带 X509 证书。 EncryptedXML class 无法确定要使用哪个私钥,因此只能抛出异常。

Microsoft 加密将以下节插入到文档中。

       EncryptedKey                (present in many documents)
         EncryptionMethod          (present in many documents)
           KeyInfo                 (absent in non-ms documents)
             X509Data                
               X509Certificate        

现在我有了这个问题的解决方案,即使用正确的证书将这些项目的最后三项插入到文档中。就是这个。

XmlNode encryptedKey = doc.LookForNode(@"EncryptedKey");
if (null == encryptedKey) 
    throw new CryptographicException("No key information in encrypted document");

XmlNode encryptionMethod = encryptedKey.LookForNode(@"EncryptionMethod");
if (null == encryptionMethod) 
    throw new CryptographicException("No key encryption method in encrypted document");

XmlNode keyinfo = encryptedKey.LookForNode(@"KeyInfo");
if (null == keyinfo) {
  /* no key info, cons up the required stanza of XML based on our public key */
  const string keynamespace = @"http://www.w3.org/2000/09/xmldsig#";
  XmlNode ki = doc.CreateElement(null, @"KeyInfo", keynamespace);
  XmlNode xd = doc.CreateElement(null, @"X509Data", keynamespace);
  XmlNode xc = doc.CreateElement(null, @"X509Certificate", keynamespace);  /* has public key cert in it */
  xc.InnerText = Convert.ToBase64String(key.Export(X509ContentType.Cert));

  xd.AppendChild(xc);
  ki.AppendChild(xd);
  encryptedKey.InsertAfter(ki, encryptionMethod);
}

EncryptedXml exml = new EncryptedXml(doc);
exml.DecryptDocument();

这行得通。 DecryptDocument 当文档被如此扩充时成功。但这似乎是一个大问题。在 dot net 中有更好的方法吗?在这个跨平台文档交换的世界中,是否有某种密钥管理的最佳实践?

您可以像这样从证书中提取私钥(前提是证书有私钥):

rsaProvider = (RSACryptoServiceProvider)cert.PrivateKey;

然后在您的 EncryptedXML 上添加一个密钥映射:

encryptedXML.AddKeyNameMapping("rsaKey", rsaProvider );

我想出了在 C# 中如何解决这个问题。此解决方案将缺少的元素添加到 XML 文档,然后对其进行解密。

/// <summary>
/// decrypt a document with the specified key
/// </summary>
/// <param name="docin">encrypted document</param>
/// <param name="key">the key pair</param>
/// <returns>the decrypted document</returns>
public static XmlDocument DecryptDocument(
    this XmlDocument docin, X509Certificate2 key,
    StringBuilder log = null)
{
    /* first step: make sure encrypted xml contains all these items.
     * EncryptedKey                (present in many documents)
     *   EncryptionMethod          (present in many documents)
     *     KeyInfo                 (absent in some documents)
     *       X509Data                 ditto
     *         X509Certificate        ditto with x.509 cert in Base4
     *  
     * because that's the way dotnet EncryptedXML figures out
     * under the covers which key to use to decode the document,
     * bless its pointed little head.  */

    var encryptedKey = docin.LookForNode(@"EncryptedKey");
    if (null == encryptedKey)
    {
        /* not an encrypted document, give it right back. */
        return docin;
    }
    /* leave the input document unchanged, manipulate a copy */
    var doc = (XmlDocument) docin.Clone();

    encryptedKey = doc.LookForNode(@"EncryptedKey");
    if (null == encryptedKey)
    {
        if (null != log) 
             log.Append("No encryption key in document").AppendLine();
        return docin;
    }

    var encryptionMethod = encryptedKey.LookForNode(@"EncryptionMethod");
    if (null == encryptionMethod)
    {
        if (null != log) 
            log.Append("No key encryption method in document").AppendLine();
        return docin;
    }

    /*create required stanza of XML based on our public key */
    const string keynamespace = SignedXml.XmlDsigNamespaceUrl;
    XmlNode ki = doc.CreateElement(null, @"KeyInfo", keynamespace);
    XmlNode xd = doc.CreateElement(null, @"X509Data", keynamespace);
    XmlNode xc = doc.CreateElement(null, @"X509Certificate", keynamespace);
    xc.InnerText = Convert.ToBase64String(key.Export(X509ContentType.Cert));

    /* insert it into the received document */
    xd.AppendChild(xc);
    ki.AppendChild(xd);
    encryptedKey.InsertAfter(ki, encryptionMethod);
    if (null != log) log.Append("Adding KeyInfo data to encrypted document").AppendLine();

    /* handle the decrypt */
    try
    {
        var exml = new EncryptedXml(doc);
        exml.DecryptDocument();
    }
    catch (CryptographicException cre)
    {
        if (null != log) log.Append(cre.Message).AppendLine();
    }
    return doc;
}

GetDecryptionKey 是 EncryptedXml 中的一个虚拟方法 class... 如果您不想使用默认的 Microsoft 逻辑来确定密钥,您可以创建自己的该方法的覆盖。尽管您随后必须实现使用您的私钥解密生成的对称密钥的逻辑。

回到Microsoft为EncryptedXml提供的参考源,我使用了这样的代码:

class EncryptedXmlWithPreconfiguredAsymmetricKey : EncryptedXml
{
    public readonly X509Certificate2 _encryptionCert;
    public EncryptedXmlWithPreconfiguredAssymetricKey(XmlDocument xmlDoc, X509Certificate2 encryptionCert) : base(xmlDoc)
    {
        _encryptionCert = encryptionCert;
    }

    public override System.Security.Cryptography.SymmetricAlgorithm GetDecryptionKey(EncryptedData encryptedData, string symmetricAlgorithmUri)
    {
        if (encryptedData == null)
            throw new ArgumentNullException("encryptedData");

        if (encryptedData.KeyInfo == null)
            return null;
        IEnumerator keyInfoEnum = encryptedData.KeyInfo.GetEnumerator();
        KeyInfoRetrievalMethod kiRetrievalMethod;
        KeyInfoName kiName;
        KeyInfoEncryptedKey kiEncKey;
        EncryptedKey ek = null;

        while (keyInfoEnum.MoveNext())
        {
            kiName = keyInfoEnum.Current as KeyInfoName;

            kiRetrievalMethod = keyInfoEnum.Current as KeyInfoRetrievalMethod;

            kiEncKey = keyInfoEnum.Current as KeyInfoEncryptedKey;
            if (kiEncKey != null)
            {
                ek = kiEncKey.EncryptedKey;
                break;
            }
        }

        // if we have an EncryptedKey, decrypt to get the symmetric key
        if (ek != null)
        {
            // now process the EncryptedKey, loop recursively
            // If the Uri is not provided by the application, try to get it from the EncryptionMethod 
            if (symmetricAlgorithmUri == null)
            {
                if (encryptedData.EncryptionMethod == null)
                    throw new CryptographicException("Cryptography_Xml_MissingAlgorithm");
                symmetricAlgorithmUri = encryptedData.EncryptionMethod.KeyAlgorithm;
            }
            byte[] key = ek.CipherData.CipherValue;
            if (key == null)
                throw new CryptographicException("Cryptography_Xml_MissingDecryptionKey");

            // Ignore any information about the asymmetric key in the XML, and just use our predefined certificate
            var rsaKey = (RSA)_encryptionCert.PrivateKey;

            byte[] symkey = EncryptedXml.DecryptKey(key, rsaKey, true);

            SymmetricAlgorithm symAlg = (SymmetricAlgorithm)CryptoConfig.CreateFromName(symmetricAlgorithmUri);
            symAlg.Key = symkey;
            return symAlg;
        }
        return null;
    }
}

检查这个图书馆。 https://www.itfoxtec.com/identitysaml2

你使用这样的东西,你的 xml 被解密了。

using (RSA rsa = privateKeyCert.GetRSAPrivateKey())
{
    new Saml2EncryptedXml(xmlDoc, rsa).DecryptDocument();
}