【问题标题】:Using machine keys for IDataProtector - ASP.NET CORE使用 IDataProtector 的机器密钥 - ASP.NET CORE
【发布时间】:2017-10-03 13:50:47
【问题描述】:

有没有一种方法可以指定单独的加密和验证密钥。目前,只有一个主密钥可以同时进行验证和加密。但是,我们在网络场中有多个应用程序,其中只有一个在 ASP.NET CORE 上运行,并且托管在 IIS 上。应用程序的其余部分(在 ASP.NET *非核心上运行)使用相同的机器密钥。当然,机器密钥具有解密和验证密钥,所有其他应用程序都使用相同的机器密钥在它们之间同步数据。我还想让 CORE 应用程序与相同的键同步。目前,核心应用程序有这个。 IDataProtector 使用主设备来验证和加密/解密。

  <?xml version="1.0" encoding="utf-8"?>
    <key id="6015093e-8571-4244-8824-17157f248d13" version="1">
      <creationDate>2017-10-03T12:13:26.6902857Z</creationDate>
      <activationDate>2017-10-03T13:13:26.6897307+01:00</activationDate>
      <expirationDate>2017-11-03T13:13:26.6898152+01:00</expirationDate>
      <descriptor>
        <descriptor>
          <encryption algorithm="AES_256_CBC" />
          <validation algorithm="HMACSHA256" />
          <masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
            <value>**This is the key**</value>
          </masterKey>
        </descriptor>
      </descriptor>
    </key>

我想要这样的东西

    <descriptor>
      <encryption algorithm="AES_256_CBC" />
      <validation algorithm="HMACSHA256" />
      <encryptionKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
        <!-- Warning: the key below is in an unencrypted form. -->
        <value>encrypt key</value>
      </encryptionKey>
      <decryptionKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
        <!-- Warning: the key below is in an unencrypted form. -->
        <value>validation key</value>
      </decryptionKey>
    </descriptor>
  </descriptor>

指定单独的验证和加密密钥。这样的事情可能吗?

【问题讨论】:

    标签: security encryption asp.net-core


    【解决方案1】:

    我只需要 MachineKey.UnProtect 功能。我无法使用 ASP.NET CORE 的 API 获得任何东西,所以我别无选择,只能从 .net 框架中拼接源代码。以下代码最终帮助我解除保护。

    public static class MachineKey
        {
            private static readonly UTF8Encoding SecureUTF8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
            public static byte[] Unprotect(byte[] protectedData, string validationKey, string encKey, params string[] specificPurposes)
            {
                // The entire operation is wrapped in a 'checked' block because any overflows should be treated as failures.
                checked
                {
                    using (SymmetricAlgorithm decryptionAlgorithm = new AesCryptoServiceProvider())
                    {
    
                        decryptionAlgorithm.Key = SP800_108.DeriveKey(HexToBinary(encKey), "User.MachineKey.Protect", specificPurposes);
    
                        // These KeyedHashAlgorithm instances are single-use; we wrap it in a 'using' block.
                        using (KeyedHashAlgorithm validationAlgorithm = new HMACSHA256())
                        {
                            validationAlgorithm.Key = SP800_108.DeriveKey(HexToBinary(validationKey), "User.MachineKey.Protect", specificPurposes);
    
                            int ivByteCount = decryptionAlgorithm.BlockSize / 8; 
                            int signatureByteCount = validationAlgorithm.HashSize / 8;
                            int encryptedPayloadByteCount = protectedData.Length - ivByteCount - signatureByteCount;
                            if (encryptedPayloadByteCount <= 0)
                            {
                                return null;
                            }
    
                            byte[] computedSignature = validationAlgorithm.ComputeHash(protectedData, 0, ivByteCount + encryptedPayloadByteCount);
    
                            if (!BuffersAreEqual(
                                buffer1: protectedData, buffer1Offset: ivByteCount + encryptedPayloadByteCount, buffer1Count: signatureByteCount,
                                buffer2: computedSignature, buffer2Offset: 0, buffer2Count: computedSignature.Length))
                            {
    
                                return null;
                            }
    
                            byte[] iv = new byte[ivByteCount];
                            Buffer.BlockCopy(protectedData, 0, iv, 0, iv.Length);
                            decryptionAlgorithm.IV = iv;
    
                            using (MemoryStream memStream = new MemoryStream())
                            {
                                using (ICryptoTransform decryptor = decryptionAlgorithm.CreateDecryptor())
                                {
                                    using (CryptoStream cryptoStream = new CryptoStream(memStream, decryptor, CryptoStreamMode.Write))
                                    {
                                        cryptoStream.Write(protectedData, ivByteCount, encryptedPayloadByteCount);
                                        cryptoStream.FlushFinalBlock();
    
                                        byte[] clearData = memStream.ToArray();
    
                                        return clearData;
                                    }
                                }
                            }
                        }
                    }
                }
            }
    
            private static bool BuffersAreEqual(byte[] buffer1, int buffer1Offset, int buffer1Count, byte[] buffer2, int buffer2Offset, int buffer2Count)
            {
                bool success = (buffer1Count == buffer2Count); // can't possibly be successful if the buffers are of different lengths
                for (int i = 0; i < buffer1Count; i++)
                {
                    success &= (buffer1[buffer1Offset + i] == buffer2[buffer2Offset + (i % buffer2Count)]);
                }
                return success;
            }
    
            private static class SP800_108
            {
    
                public static byte[] DeriveKey(byte[] keyDerivationKey, string primaryPurpose, params string[] specificPurposes)
                {
                    using (HMACSHA512 hmac = new HMACSHA512(keyDerivationKey))
                    {
    
                        GetKeyDerivationParameters(out byte[] label, out byte[] context, primaryPurpose, specificPurposes);
    
                        byte[] derivedKey = DeriveKeyImpl(hmac, label, context, keyDerivationKey.Length * 8);
    
                        return derivedKey;
                    }
                }
    
                private static byte[] DeriveKeyImpl(HMAC hmac, byte[] label, byte[] context, int keyLengthInBits)
                {
                    checked
                    {
                        int labelLength = (label != null) ? label.Length : 0;
                        int contextLength = (context != null) ? context.Length : 0;
                        byte[] buffer = new byte[4 /* [i]_2 */ + labelLength /* label */ + 1 /* 0x00 */ + contextLength /* context */ + 4 /* [L]_2 */];
    
                        if (labelLength != 0)
                        {
                            Buffer.BlockCopy(label, 0, buffer, 4, labelLength); // the 4 accounts for the [i]_2 length
                        }
                        if (contextLength != 0)
                        {
                            Buffer.BlockCopy(context, 0, buffer, 5 + labelLength, contextLength); // the '5 +' accounts for the [i]_2 length, the label, and the 0x00 byte
                        }
                        WriteUInt32ToByteArrayBigEndian((uint)keyLengthInBits, buffer, 5 + labelLength + contextLength); // the '5 +' accounts for the [i]_2 length, the label, the 0x00 byte, and the context
    
                        int numBytesWritten = 0;
                        int numBytesRemaining = keyLengthInBits / 8;
                        byte[] output = new byte[numBytesRemaining];
    
                        for (uint i = 1; numBytesRemaining > 0; i++)
                        {
                            WriteUInt32ToByteArrayBigEndian(i, buffer, 0); // set the first 32 bits of the buffer to be the current iteration value
                            byte[] K_i = hmac.ComputeHash(buffer);
    
                            // copy the leftmost bits of K_i into the output buffer
                            int numBytesToCopy = Math.Min(numBytesRemaining, K_i.Length);
                            Buffer.BlockCopy(K_i, 0, output, numBytesWritten, numBytesToCopy);
                            numBytesWritten += numBytesToCopy;
                            numBytesRemaining -= numBytesToCopy;
                        }
    
                        // finished
                        return output;
                    }
                }
    
                private static void WriteUInt32ToByteArrayBigEndian(uint value, byte[] buffer, int offset)
                {
                    buffer[offset + 0] = (byte)(value >> 24);
                    buffer[offset + 1] = (byte)(value >> 16);
                    buffer[offset + 2] = (byte)(value >> 8);
                    buffer[offset + 3] = (byte)(value);
                }
    
            }
    
            private static void GetKeyDerivationParameters(out byte[] label, out byte[] context, string primaryPurpose, params string[] specificPurposes)
            {
                label = SecureUTF8Encoding.GetBytes(primaryPurpose);
    
                    using (MemoryStream stream = new MemoryStream())
                    using (BinaryWriter writer = new BinaryWriter(stream, SecureUTF8Encoding))
                    {
                        foreach (string specificPurpose in specificPurposes)
                        {
                            writer.Write(specificPurpose);
                        }
                        context = stream.ToArray();
                    }
            }
    
            private static byte[] HexToBinary(string data)
            {
                if (data == null || data.Length % 2 != 0)
                {
                    // input string length is not evenly divisible by 2
                    return null;
                }
    
                byte[] binary = new byte[data.Length / 2];
    
                for (int i = 0; i < binary.Length; i++)
                {
                    int highNibble = HexToInt(data[2 * i]);
                    int lowNibble = HexToInt(data[2 * i + 1]);
    
                    if (highNibble == -1 || lowNibble == -1)
                    {
                        return null; // bad hex data
                    }
                    binary[i] = (byte)((highNibble << 4) | lowNibble);
                }
    
                int HexToInt(char h)
                {
                    return (h >= '0' && h <= '9') ? h - '0' :
                    (h >= 'a' && h <= 'f') ? h - 'a' + 10 :
                    (h >= 'A' && h <= 'F') ? h - 'A' + 10 :
                    -1;
                }
                return binary;
            }
    
        } 
    

    [示例]

    var message = "My secret message";
    
    var encodedMessage = Encoding.ASCII.GetBytes(message);
    
    var protectedMessage = MachineKey.Protect(encodedMessage, "My Purpose");
    
    var protectedMessageAsBase64 = Convert.ToBase64String(protectedMessage);
    
    // Now make sure you reverse the process 
    
    var convertFromBase64 = Convert.FromBase64String(protectedMessageAsBase64);
    
    var unProtectedMessage = MachineKey.Unprotect(convertFromBase64, "Your validation key", "Your encryption key", "My Purpose");
    
    var decodedMessage = Encoding.ASCII.GetString(unProtectedMessage);
    

    这只是一个简单的例子。首先,确保您拥有来自 IIS 的正确验证和加密密钥。这似乎是一个显而易见的观点,但它让我发疯,因为我使用了错误的键。接下来,确保您知道加密消息的目的。在我的示例中,目的是“我的目的”。如果消息是在没有目的的情况下加密的,那么当您取消保护某些内容时,只需保留目的参数即可。最后,您必须知道您的加密消息是如何呈现给您的。是base64编码吗,比如,你需要知道这个,这样你才能做相反的事情。

    【讨论】:

    • 您能分享一下您是如何使用它的吗?我正在尝试解密 AuthenticationTicket 并使用您在上面提供的内容。但是,我的 BuffersAreEqual 最终是错误的,我怀疑这是因为我没有指定正确的“specificPurposes”来保护原始文件。
    • 我发布了一个示例,我们在生产中为我们的一个内部工具工作,所以我可以告诉你这 100% 有效。如果您遇到问题,请告诉我。
    • 我搞定了,谢谢 Umar。我的问题是我以前使用的是 HMACSHA1(在我的 web.config/machineKey 中配置)。一旦我使用它,它就开始按预期工作。我已经制作了一个 NuGet 包,以防其他人想使用它我如何使用它:github.com/dmarlow/BearerTokenBridge
    • 太棒了,你把它做成了一个包:)我会更新我的答案,提到你如何使用不同的加密和验证算法。
    • 我也得到了它的工作。非常感谢 Umar,dmarlow。我使用了这个 nuget 包 AspNetTicketBridge(来源 github.com/dmarlow/AspNetTicketBridge)。拥有与 IIS 相同的密钥非常重要(请参阅从何处获取 Azure AppService rimdev.io/… 的信息)。另一个重要的事情是默认验证算法现在是 HMACSHA256 而不是 HMAC1)
    猜你喜欢
    • 1970-01-01
    • 2015-05-11
    • 1970-01-01
    • 1970-01-01
    • 2020-12-25
    • 2011-12-28
    • 1970-01-01
    • 2017-11-20
    • 2021-06-01
    相关资源
    最近更新 更多