MAC、哈希和签名

本文讨论如何在通用 Windows 平台(UWP)应用中使用消息身份验证代码(MAC)、哈希和签名来检测消息篡改。

消息身份验证代码(MAC)

加密有助于防止未经授权的个人读取消息,但不会阻止该个人篡改消息。 更改的消息(即使更改只会导致无稽之谈)可能具有实际成本。 消息身份验证代码(MAC)有助于防止消息篡改。 例如,考虑以下情况:

  • Bob 和 Alice 共享密钥,并同意使用 MAC 函数。
  • Bob 将创建一条消息,并将消息和密钥输入到 MAC 函数中,以检索 MAC 值。
  • Bob 通过网络将 [未加密] 消息和 MAC 值发送到 Alice。
  • Alice 使用密钥和消息作为 MAC 函数的输入。 她将生成的 MAC 值与 Bob 发送的 MAC 值进行比较。 如果消息相同,则传输中未更改消息。

请注意,作为窃听 Bob 和 Alice 对话的第三方,伊芙无法有效地操纵消息。 Eve 无权访问私钥,因此无法创建 MAC 值,这将使篡改的消息对 Alice 看起来合法。

创建消息身份验证代码可确保原始消息未更改,并且通过使用共享密钥,消息哈希由有权访问该私钥的人员签名。

可以使用 MacAlgorithmProvider 枚举可用的 MAC 算法并生成对称密钥。 可以在 CryptographicEngine 类上使用静态方法来执行创建 MAC 值的必需加密。

数字签名是私钥消息身份验证代码(MAC)的公钥等效项。 尽管 MAC 使用私钥使消息接收者能够验证传输过程中消息是否已被更改,而签名则使用私钥和公钥对。

此示例代码演示如何使用 MacAlgorithmProvider 类创建哈希消息身份验证代码(HMAC)。

using Windows.Security.Cryptography;
using Windows.Security.Cryptography.Core;
using Windows.Storage.Streams;

namespace SampleMacAlgorithmProvider
{
    sealed partial class MacAlgProviderApp : Application
    {
        public MacAlgProviderApp()
        {
            // Initialize the application.
            this.InitializeComponent();

            // Initialize the hashing process.
            String strMsg = "This is a message to be authenticated";
            String strAlgName = MacAlgorithmNames.HmacSha384;
            IBuffer buffMsg;
            CryptographicKey hmacKey;
            IBuffer buffHMAC;

            // Create a hashed message authentication code (HMAC)
            this.CreateHMAC(
                strMsg,
                strAlgName,
                out buffMsg,
                out hmacKey,
                out buffHMAC);

            // Verify the HMAC.
            this.VerifyHMAC(
                buffMsg,
                hmacKey,
                buffHMAC);
        }

        void CreateHMAC(
            String strMsg,
            String strAlgName,
            out IBuffer buffMsg,
            out CryptographicKey hmacKey,
            out IBuffer buffHMAC)
        {
            // Create a MacAlgorithmProvider object for the specified algorithm.
            MacAlgorithmProvider objMacProv = MacAlgorithmProvider.OpenAlgorithm(strAlgName);

            // Demonstrate how to retrieve the name of the algorithm used.
            String strNameUsed = objMacProv.AlgorithmName;

            // Create a buffer that contains the message to be signed.
            BinaryStringEncoding encoding = BinaryStringEncoding.Utf8;
            buffMsg = CryptographicBuffer.ConvertStringToBinary(strMsg, encoding);

            // Create a key to be signed with the message.
            IBuffer buffKeyMaterial = CryptographicBuffer.GenerateRandom(objMacProv.MacLength);
            hmacKey = objMacProv.CreateKey(buffKeyMaterial);

            // Sign the key and message together.
            buffHMAC = CryptographicEngine.Sign(hmacKey, buffMsg);

            // Verify that the HMAC length is correct for the selected algorithm
            if (buffHMAC.Length != objMacProv.MacLength)
            {
                throw new Exception("Error computing digest");
            }
         }

        public void VerifyHMAC(
            IBuffer buffMsg,
            CryptographicKey hmacKey,
            IBuffer buffHMAC)
        {
            // The input key must be securely shared between the sender of the HMAC and 
            // the recipient. The recipient uses the CryptographicEngine.VerifySignature() 
            // method as follows to verify that the message has not been altered in transit.
            Boolean IsAuthenticated = CryptographicEngine.VerifySignature(hmacKey, buffMsg, buffHMAC);
            if (!IsAuthenticated)
            {
                throw new Exception("The message cannot be verified.");
            }
        }
    }
}

哈希

加密哈希函数采用任意长的数据块并返回固定大小的位字符串。 对数据进行签名时,通常使用哈希函数。 由于大多数公钥签名操作的计算密集型,因此对消息哈希进行签名(加密)通常比对原始消息进行签名效率更高。 以下过程表示一个常见(尽管简化)方案:

  • Alice 具有公钥/私钥对,并希望向 Bob 发送签名消息。
  • Alice 创建消息并使用哈希函数计算消息的哈希。
  • Alice 使用她的私钥对哈希进行签名,并通过网络将 [未加密] 消息和签名发送到 Bob。
  • Bob 使用相同的哈希函数计算收到的消息的哈希。 然后,他使用 Alice 的公钥解密签名,并将其与计算哈希进行比较。 如果消息相同,则消息在传输过程中未更改,来自 Alice。

请注意,Alice 发送了未加密的消息。 只有哈希被加密了。 此过程确保原始消息未被更改,同时通过使用 Alice 的公钥来验证消息哈希由能够访问 Alice 私钥的人签署,这个人很可能是 Alice。

可以使用 HashAlgorithmProvider 类枚举可用的哈希算法并创建 CryptographicHash 值。

数字签名是私钥消息身份验证代码(MAC)的公钥等效项。 虽然 MAC 使用私钥使消息接收者能够验证传输过程中消息是否已更改,但签名使用私钥/公钥对。

CryptographicHash 对象可用于重复哈希不同的数据,而无需为每个用途重新创建对象。 Append 方法向要哈希的缓冲区添加新数据。 GetValueAndReset 方法对数据进行哈希处理,并重置对象以供其他使用。 以下示例对此进行演示。

public void SampleReusableHash()
{
    // Create a string that contains the name of the hashing algorithm to use.
    String strAlgName = HashAlgorithmNames.Sha512;

    // Create a HashAlgorithmProvider object.
    HashAlgorithmProvider objAlgProv = HashAlgorithmProvider.OpenAlgorithm(strAlgName);

    // Create a CryptographicHash object. This object can be reused to continually
    // hash new messages.
    CryptographicHash objHash = objAlgProv.CreateHash();

    // Hash message 1.
    String strMsg1 = "This is message 1.";
    IBuffer buffMsg1 = CryptographicBuffer.ConvertStringToBinary(strMsg1, BinaryStringEncoding.Utf16BE);
    objHash.Append(buffMsg1);
    IBuffer buffHash1 = objHash.GetValueAndReset();

    // Hash message 2.
    String strMsg2 = "This is message 2.";
    IBuffer buffMsg2 = CryptographicBuffer.ConvertStringToBinary(strMsg2, BinaryStringEncoding.Utf16BE);
    objHash.Append(buffMsg2);
    IBuffer buffHash2 = objHash.GetValueAndReset();

    // Hash message 3.
    String strMsg3 = "This is message 3.";
    IBuffer buffMsg3 = CryptographicBuffer.ConvertStringToBinary(strMsg3, BinaryStringEncoding.Utf16BE);
    objHash.Append(buffMsg3);
    IBuffer buffHash3 = objHash.GetValueAndReset();

    // Convert the hashes to string values (for display);
    String strHash1 = CryptographicBuffer.EncodeToBase64String(buffHash1);
    String strHash2 = CryptographicBuffer.EncodeToBase64String(buffHash2);
    String strHash3 = CryptographicBuffer.EncodeToBase64String(buffHash3);
}

数字签名

数字签名是私钥消息身份验证代码(MAC)的公钥等效项。 虽然 MAC 使用私钥使消息接收者能够验证传输过程中消息是否已更改,但签名使用私钥/公钥对。

但是,由于大多数公钥签名操作的计算密集型,因此对消息哈希进行签名(加密)通常比对原始消息进行签名效率更高。 发送方创建一个消息哈希,对其进行签名,并发送签名和(未加密)消息。 收件人计算邮件上的哈希,解密签名,并将解密的签名与哈希值进行比较。 如果他们匹配,收件人可以相当确定邮件确实来自发件人,并在传输期间没有更改。

签名仅确保原始消息未更改,并且通过使用发件人的公钥,消息哈希由有权访问私钥的人员签名。

可以使用 AsymmetricKeyAlgorithmProvider 对象来枚举可用的签名算法并生成或导入密钥对。 可以在 CryptographicHash 类上使用静态方法对消息进行签名或验证签名。