【问题标题】:HMACSHA1.ComputeHash() thread-safety questionHMACSHA1.ComputeHash() 线程安全问题
【发布时间】:2010-10-08 20:29:51
【问题描述】:

我在问自己,在 asp.net 页面的代码隐藏中使用包含 HMACSHA1 实例的静态(共享)变量是否会很危险。问题是在处理同一个 asp.net 页面上的多个同时请求时,所有 asp.net worker-process 线程将使用相同的 HMACSHA1-instance。 所有由 ComputeHash() 使用/修改的 (HMACSHA1) 实例和 ComputeHash() 方法变量将由所有线程共享(= 可以修改)?!这个假设正确吗? 结果 ComputeHash 的返回值不能保证是正确的?!?! 因此我不允许在所有 asp.net-threads 上使用静态/共享 HMACSHA1-instance..

我只是想知道您对这个问题的看法。

对此的唯一解决方案是 ComputeHash() 方法中的关键路径等。但那是“我们无法企及的”..

问候, 克里斯

【问题讨论】:

    标签: asp.net hmac sha1


    【解决方案1】:

    散列算法是确定性的,它们必须每次为给定的输入返回相同的散列。

    只要您每次都使用相同的密钥,就无需将它们设为静态。但是,如果您使用无参数构造函数构造 HMACSHA1 实例,那么它会生成一个随机密钥。您应该从 KeyValue 属性中获取随机值并将其与哈希一起存储。

    使用静态实例绝对是危险的。如果 Thread1 设置了要计算的值,然后 Thread2 在 Thread1 调用 ComputerHash() 之前设置了该值,那么 Thread1 将获取 Thread2 值的哈希值。如果任一线程正在设置密钥,也会发生同样的情况。

    【讨论】:

    • 哈希算法确实必须是确定性的.. 但问题是多线程 :) 你是什么意思:“..不需要使它们成为静态的”?密钥始终保持不变,并将定期(每月)更改。我正在创建这样的实例: HMAC hmac = HMAC.Create("HMACSHA1");
    • 我将 HMAC 变量设置为 asp.net-codebehind-class 静态的原因是性能.. 但是,否则,ComputeHash() 应该具有几乎 100% 的内容在关键部分之间以保证线程安全..
    • ..所以我的想法将 hmac 实例保持为静态可能会很好地节省内存,但每个页面请求将比使用非静态 hmac-Instances 花费更长的时间,因为锁定-机制。
    • 好吧,对它进行基准测试,但我的猜测是,在典型的 ASP.NET 页面请求中,多一次 CSP 操作的开销可能并不太重要。
    • 对我来说这听起来像是过早的优化,锁定问题意味着如果您的机器受到严重打击,每个请求 HMAC 的页面都将等待。太可怕了!
    【解决方案2】:

    当我在四核 HT cpu 上运行 8 或 16 个并行任务时,我刚刚从 SHA256Cng.ComputeHash 中得到一个未知的加密异常,这些任务除其他外执行哈希计算。

    在 ComputeHash 周围添加锁定语义解决了这个问题 - 所以看起来至少 SHA256Cng 版本不是线程安全的。

    【讨论】:

      【解决方案3】:

      如果您想要线程安全而不需要锁定,您可以使用 ThreadStatic 属性在每个线程上创建一个唯一的实例,如下所示:

      [ThreadStatic]
      private static HMACSHA1 _hmacSha1;
      
      public static HMACSHA1 HmacSha1
      {
          get 
          {
              if (_hmacSha1 == null)
              {
                  // this will happen once on each thread
                  _hmacSha1 = new HMACSHA1(GetKeyBytes());
              }               
      
              return _hmacSha1;
          }
      }
      

      现在,两个旁注:

      1. 访问线程静态字段比访问普通静态字段花费的时间要长得多。因此,线程静态版本可能对您更好,也可能不会更好。

      2. 如果您对每个页面请求执行一次此操作,那么差异将非常小,以至于您选择哪种方法都无关紧要。如果您在一个非常紧凑的循环中执行此操作,或者您的锁定部分中的代码花费了很长时间,那么选择可能很重要。

      【讨论】:

        【解决方案4】:

        值得知道KeyedHashAlgorithm.ComputeHash() 不是线程安全的,因为它为相同的KeyedHashAlgorithm.Key 提供了不确定的结果。

        在我的例子中,我想缓存 KeyedHashAlgorithm,因为我的 KeyedHashAlgorithm.Key 总是相同的,以从客户端验证 真实性。我意识到ComputeHash() 不一致,可能它会将内部变量缓存到KeyedHashAlgorithm 实例中。我应该缓存每个线程ThreadStaticThreadLocal 的实例。这是测试:

        静态KeyedHashAlgorithm 给出不一致的结果:

        var kha = KeyedHashAlgorithm.Create("HMACSHA256");
        kha.Key = Encoding.UTF8.GetBytes("key");
        Action comp = () =>
        {
            var computed = kha.ComputeHash(Encoding.UTF8.GetBytes("message"));
            Console.WriteLine(Convert.ToBase64String(computed));
        };
        Parallel.Invoke(comp, comp, comp, comp, comp, comp, comp, comp);
        

        与每个线程的KeyedHashAlgorithm 相比:

        ThreadLocal<KeyedHashAlgorithm> tl= new ThreadLocal<KeyedHashAlgorithm>(() =>
        {
            var kha = KeyedHashAlgorithm.Create("HMACSHA256");
            kha.Key = Encoding.UTF8.GetBytes("key");
            return kha;
        });
        Action comp = () =>
        {
            var computed = tl.Value.ComputeHash(Encoding.UTF8.GetBytes("message"));
            Console.WriteLine(Convert.ToBase64String(computed));
        };
        Parallel.Invoke(comp, comp, comp, comp, comp, comp, comp, comp);
        

        此代码可用于测试其他函数的“线程安全”结果。希望这对其他人有帮助。

        【讨论】:

        • 嘿,我正在尝试不一致的结果函数。如果您在操作“comp”内部而不是外部更新 HashAlgorithm,那么它再次保持一致。你知道这是为什么吗?
        • 已经有一段时间了。我记不太清了。您正在试验代码。尝试理解 Parallel.Invoke。如果你在 comp 中启动新的 HashAlgorithm,它就像你每次想要使用它时都创建它一样。打败 OP 缓存实例的目的。根据我上面的代码,我可以为每个线程缓存一个实例以获得一致的结果。
        猜你喜欢
        • 2023-03-12
        • 2013-08-16
        • 2021-12-16
        • 1970-01-01
        • 2011-11-13
        • 1970-01-01
        • 1970-01-01
        • 2018-04-08
        • 2011-03-24
        相关资源
        最近更新 更多