使用MachineKey进行加密是不持久的

4

我在我的Asp.net MVC应用程序中使用MachineKey.Protect()方法来加密通过查询字符串传递的id。

这是我用于加密/解密的代码:

public static string Encrypt(this string expression)
{
    if (string.IsNullOrEmpty(expression))
        return string.Empty;

    byte[] stream = Encoding.Unicode.GetBytes(expression);
    byte[] encodedValue = MachineKey.Protect(stream);            
    return HttpServerUtility.UrlTokenEncode(encodedValue);            
}

public static string Decrypt(this string expression)
{
    if (string.IsNullOrEmpty(expression))
        return string.Empty;

    byte[] stream = HttpServerUtility.UrlTokenDecode(expression);
    byte[] decodedValue = MachineKey.Unprotect(stream);
    return Encoding.Unicode.GetString(decodedValue);
}

这是我 web.config 文件中的 MachineKey 元素:

<system.web>
    .
    .
    .
    <machineKey validationKey="xxx" decryptionKey="xxx" validation="SHA1" decryption="AES" />
</system.web>

问题在于加密ID不是持久的。每次调用该方法,我都会得到一个新的加密表达式。如何使其持久化?


我可以问一下为什么你要加密一个GET URL参数吗? - Tommy
1
由于GET URL参数是机密ID。 - ataravati
你上面的代码“应该”可以工作。我有一些代码和一个WebConfig,看起来几乎与此完全相同,只是我使用的是UTF8并且我进行了一些字符串压缩。你确定在中间没有发生任何字符串变化吗?你的“encodedValue”每次输入字符串时看起来都不同吗?你确定你的machinekey元素在webconfig中的正确位置吗? - JuhaKangas
1
不要完全相信我的话,但我认为这就是使用该加密方式的工作原理。在加密/解密时,会添加一个种子值到加密数据中。你可能可以在某处找到更多相关信息。 - JuhaKangas
@JuhaKangas,你的代码也得到了相同的结果吗? - ataravati
显示剩余4条评论
2个回答

9

简介:

如果您想每次获得相同的结果,就需要使用不同的方法来保护您的数据。 使用MachineKey.Protect会为每次运行使用不同的IV,从而使结果每次都不同。

详细信息:

微软将许多.NET框架的源代码在互联网上免费公开。

从顶部开始:MachineKey

protect方法使用AspNetCryptoServiceProvider

如果您跟随代码进入AspNetCryptoServiceProvider.GetCryptoService并转到NetFXCryptoService,您会发现以下内容:

public byte[] Protect(byte[] clearData) {
        // The entire operation is wrapped in a 'checked' block because any overflows should be treated as failures.
        checked {

            // These SymmetricAlgorithm instances are single-use; we wrap it in a 'using' block.
            using (SymmetricAlgorithm encryptionAlgorithm = _cryptoAlgorithmFactory.GetEncryptionAlgorithm()) {
                // Initialize the algorithm with the specified key and an appropriate IV
                encryptionAlgorithm.Key = _encryptionKey.GetKeyMaterial();

                if (_predictableIV) {
                    // The caller wanted the output to be predictable (e.g. for caching), so we'll create an
                    // appropriate IV directly from the input buffer. The IV length is equal to the block size.
                    encryptionAlgorithm.IV = CryptoUtil.CreatePredictableIV(clearData, encryptionAlgorithm.BlockSize);
                }
                else {
                    // If the caller didn't ask for a predictable IV, just let the algorithm itself choose one.
                    encryptionAlgorithm.GenerateIV();
                }
                byte[] iv = encryptionAlgorithm.IV;

                using (MemoryStream memStream = new MemoryStream()) {
                    memStream.Write(iv, 0, iv.Length);

                    // At this point:
                    // memStream := IV

                    // Write the encrypted payload to the memory stream.
                    using (ICryptoTransform encryptor = encryptionAlgorithm.CreateEncryptor()) {
                        using (CryptoStream cryptoStream = new CryptoStream(memStream, encryptor, CryptoStreamMode.Write)) {
                            cryptoStream.Write(clearData, 0, clearData.Length);
                            cryptoStream.FlushFinalBlock();

                            // At this point:
                            // memStream := IV || Enc(Kenc, IV, clearData)

                            // These KeyedHashAlgorithm instances are single-use; we wrap it in a 'using' block.
                            using (KeyedHashAlgorithm signingAlgorithm = _cryptoAlgorithmFactory.GetValidationAlgorithm()) {
                                // Initialize the algorithm with the specified key
                                signingAlgorithm.Key = _validationKey.GetKeyMaterial();

                                // Compute the signature
                                byte[] signature = signingAlgorithm.ComputeHash(memStream.GetBuffer(), 0, (int)memStream.Length);

                                // At this point:
                                // memStream := IV || Enc(Kenc, IV, clearData)
                                // signature := Sign(Kval, IV || Enc(Kenc, IV, clearData))

                                // Append the signature to the encrypted payload
                                memStream.Write(signature, 0, signature.Length);

                                // At this point:
                                // memStream := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData))

                                // Algorithm complete
                                byte[] protectedData = memStream.ToArray();
                                return protectedData;
                            }
                        }
                    }
                }
            }
        }
    }

该类以默认选项初始化,因此_predictableIV为false。
因此,每次使用新的IV,这意味着即使是相同的输入,结果也会不同。
IV包含在结果中,因此Unprotect方法可以反转加密。

1
谢谢!因此,当使用MachineKey.Protect()时,似乎没有办法使加密表达式始终保持相同。微软最好为该方法添加一个布尔参数以实现该目的。 - ataravati

0

试着给 Machine.Key.Protect 方法添加一个 purpose 参数!就像这样。

public static class Key
{       
    public static string EncryptWithAPurpose(this string expression, string[] purpose)
    {
        if (string.IsNullOrEmpty(expression))
            return string.Empty;

        byte[] stream = Encoding.Unicode.GetBytes(expression);
        byte[] encodedValue = MachineKey.Protect(stream, purpose);
        return HttpServerUtility.UrlTokenEncode(encodedValue);
    }

    public static string DecryptWithAPurpose(this string expression, string[] purpose)
    {
        if (string.IsNullOrEmpty(expression))
            return string.Empty;

        byte[] stream = HttpServerUtility.UrlTokenDecode(expression);

        byte[] decodedValue = MachineKey.Unprotect(stream,purpose);
        return Encoding.Unicode.GetString(decodedValue);
    }

}

测试用:

  1. 添加链接 -> @Html.ActionLink("获取新密钥", "GetKey", new { id = Guid.NewGuid()})
  2. 添加控制器方法
  3. 添加视图模型
  4. 添加视图。

{

public ActionResult GetKey(Guid id)  
{
            var vm = new vmGetKey();

            vm.Guid = id;

            //create a purpose
            var purpose = new string[] { "Test", "WithAPurpose" };

            //encrypt  key1
            vm.key1 = Key.EncryptWithAPurpose(id.ToString(),  purpose);

            //encrypt Key2
            vm.key2 = Key.EncryptWithAPurpose(id.ToString(), purpose);

            //decrypt key1
            vm.key1_DecryptResult = Key.DecryptWithAPurpose(vm.key1, purpose);

            //decrypt key2
            vm.key2_DecryptResult = Key.DecryptWithAPurpose(vm.key2, purpose);

            return View(vm);
}


public class vmGetKey
{
    public Guid Guid { get; set; }
    public string key1 { get; set; }
    public string key2 { get; set; }
    public string key1_DecryptResult { get; set; }
    public string key2_DecryptResult { get; set; }        
}        




    }

enter image description here


我之前曾经有目的地尝试过它,但是没有成功。明天我会再试一次并告诉你结果。谢谢! - ataravati
我又试了一遍。和我预料的一样,它没有起作用。purpose参数只是在加密中增加了一个安全级别,对我在问题中解释的行为没有任何影响。 - ataravati

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接