本文讨论如何在通用 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 类上使用静态方法对消息进行签名或验证签名。