【问题标题】:Calculating pow(a,b) mod n计算 pow(a,b) mod n
【发布时间】:2012-01-19 17:42:16
【问题描述】:

我想计算 abmod n 以用于 RSA 解密。我的代码(如下)返回不正确的答案。它有什么问题?

unsigned long int decrypt2(int a,int b,int n)
{
    unsigned long int res = 1;

    for (int i = 0; i < (b / 2); i++)
    {
        res *= ((a * a) % n);
        res %= n;
    }

    if (b % n == 1)
        res *=a;

    res %=n;
    return res;
}

【问题讨论】:

  • -1:您是否尝试过在调试器中单步执行您的代码?你能举个例子说明哪里出错了吗?
  • 您的操作员分组是什么?
  • 为什么你不做 pow(a,b) % n?
  • 看来你需要% 2 insted of % n in last if
  • 例如,int 溢出,但 64 位类型就足够了。但是,如果您要认真使用 RSA,则需要大整数,gmp 将是一个选项(并且具有模块化功能)。

标签: c++ c algorithm


【解决方案1】:

你可以试试这个 C++ 代码。我已经将它与 32 位和 64 位整数一起使用。我确定我是从 SO 那里得到的。

template <typename T>
T modpow(T base, T exp, T modulus) {
  base %= modulus;
  T result = 1;
  while (exp > 0) {
    if (exp & 1) result = (result * base) % modulus;
    base = (base * base) % modulus;
    exp >>= 1;
  }
  return result;
}

您可以在 p.1 的文献中找到该算法和相关讨论。 244 的

施奈尔,布鲁斯 (1996)。应用密码学:C 中的协议、算法和源代码,第二版(第二版)。威利。国际标准书号 978-0-471-11709-4。


请注意,在此简化版本中,乘法 result * basebase * base 会溢出。如果模数大于T 宽度的一半(即大于最大T 值的平方根),则应改用合适的模乘算法 - 请参阅Ways to do modulo multiplication with primitive types 的答案.

【讨论】:

  • +1(我本来打算早点发表评论)这对question很有帮助,如果能找到原始来源会很棒。
  • 但是如果modulus &gt; sqrt(std::numeric_limits&lt;T&gt;::max())呢?那样的话(result * base)会溢出,结果会不正确。
  • @RuudvA:为避免这种情况,您可以将这些乘法替换为您喜欢的模乘函数。例如:Ways to do modulo multiplication with primitive types
  • T result = 1; --> T result = modulus &gt; 1;result = 1%modulusmodpow(x, 0, 1) 一起正常工作。
  • 这段代码在计算result * basebase * base时如何避免溢出T的范围?
【解决方案2】:

为了计算pow(a,b) % n用于RSA解密,我遇到的最好的算法是Primality Testing 1),如下:

 int modulo(int a, int b, int n){
    long long x=1, y=a; 
    while (b > 0) {
        if (b%2 == 1) {
            x = (x*y) % n; // multiplying with base
        }
        y = (y*y) % n; // squaring the base
        b /= 2;
    }
    return x % n;
}

有关详细信息,请参阅下面的参考。


1)Primality Testing : Non-deterministic Algorithms – topcoder

【讨论】:

  • 您似乎假设long long 的范围比int 的范围更广。在 C 或 C++ 中都没有这样的保证,所以这段代码仍然会溢出。
【解决方案3】:

通常是这样的:

while (b)
{
    if (b % 2) { res = (res * a) % n; }

    a = (a * a) % n;
    b /= 2;
}

return res;

【讨论】:

    【解决方案4】:

    我看到的唯一实际逻辑错误是这一行:

    if (b % n == 1)
    

    应该是这样的:

    if (b % 2 == 1)
    

    但是您的整体设计存在问题:您的函数执行 O(b) 乘法和取模运算,但您使用 b / 2a * a 意味着您的目标是执行 O(log b) 运算(通常是如何进行模幂运算)。

    【讨论】:

    • 我认为的另一个逻辑错误是:如果a == 0 结果是1 而不是0a 应该分配给一个临时的 64 位变量,该变量又用于乘法 - 以避免溢出。
    • @ChristianAmmer:回复:如果a == 0。实际上不会,因为只要b &gt; 0res 至少会乘以aa * a 一次。 (如果b == 0,那么1 是一个完全有效的返回值,即使a == 0。)回复:将a 分配给一个临时的64 位变量:我想是这样。其他评论者已经指出,32 位整数不足以将其用于实际的 RSA 加密,因此我的回答假设此功能仅用于尝试使用少量数字并了解其工作原理。也许我应该明确表示?
    【解决方案5】:

    进行原始功率运算非常昂贵,因此您可以应用以下逻辑来简化解密。

    来自here

    现在说我们要加密消息 m = 7,
    c = m^e mod n = 7^3 mod 33 = 343 mod 33 = 13。
    因此密文 c = 13。

    为了检查解密,我们计算
    m' = c^d mod n = 13^7 mod 33 = 7。
    注意 我们不必计算 13 的 7 次方的全部值 这里。我们可以利用
    a = bc mod n = (b mod n).(c mod n) mod n
    所以我们可以将一个可能很大的数字分解成它的 组件并结合更简单、更小计算的结果 计算最终值。

    计算 m' 的一种方法如下:-
    请注意,任何数字都可以 表示为 2 的幂的和。所以首先计算
    13^2 的值, 13^4, 13^8, ... 通过反复对连续值取模 33. 13^2 = 169 ≡ 4, 13^4 = 4.4 = 16, 13^8 = 16.16 = 256 ≡ 25。
    那么,由于 7 = 4 + 2 + 1,我们有 m' = 13^7 = 13^(4 +2+1) = 13^4.13^2.13^1
    ≡ 16 x 4 x 13 = 832 ≡ 7 模 33

    【讨论】:

    • 嗯,这似乎是他试图实现的算法。问题是他做错了什么。
    【解决方案6】:

    您是要计算(a^b)%n,还是a^(b%n)

    如果您想要第一个,那么您的代码仅在 b 是偶数时才有效,因为 b/2。 “if b%n==1”是不正确的,因为您在这里不关心b%n,而是关心b%2

    如果你想要第二个,那么循环是错误的,因为你循环了 b/2 次而不是 (b%n)/2 次。

    无论哪种方式,您的功能都不必要地复杂。为什么要循环直到 b/2 并尝试每次乘以 2 个 a?为什么不只是循环直到 b 并且每次乘以一个。这将消除许多不必要的复杂性,从而消除潜在的错误。您是否认为通过将循环次数减半来使程序更快?坦率地说,这是一种糟糕的编程习惯:微优化。它并没有太大帮助:您仍然乘以相同的次数,您所做的只是减少测试循环的次数。如果 b 通常很小(如一位或两位数),则不值得麻烦。如果 b 很大——如果它可以达到数百万——那么这还不够,您需要进行更彻底的优化。

    另外,为什么%n 每次都通过循环?为什么不最后只做一次?

    【讨论】:

    • 最后做模数 --> res 会变得不必要的大和溢出(无论如何它在他的例子中做了什么)。循环直到 b/2 --> 只需要一半的迭代,因为编译器会优化和预先计算 (a*a) % n
    【解决方案7】:

    计算 pow(a,b) mod n

    1. OP 代码的一个关键问题是a * a。当a 足够大时,这是int 溢出(未定义行为)。 res 的类型与a * a 的乘法无关。

      解决方案是确保:

      • 乘法是用 2 倍宽的数学运算或
      • 模数 n, n*n &lt;= type_MAX + 1
    2. 没有理由返回比 模数 的类型更宽的类型,因为结果始终由该类型表示。

      // unsigned long int decrypt2(int a,int b,int n)
      int decrypt2(int a,int b,int n)
      
    3. 使用 unsigned 数学当然更适合 OP 的 RSA 目标。


    另见Modular exponentiation without range restriction

    // (a^b)%n
    // n != 0
    
    // Test if unsigned long long at least 2x values bits as unsigned
    #if ULLONG_MAX/UINT_MAX  - 1 > UINT_MAX
    unsigned decrypt2(unsigned a, unsigned b, unsigned n) {
      unsigned long long result = 1u % n;  // Insure result < n, even when n==1
      while (b > 0) {
        if (b & 1) result = (result * a) % n;
        a = (1ULL * a * a) %n;
        b >>= 1;
      }
      return (unsigned) result;
    }
    
    #else
    unsigned decrypt2(unsigned a, unsigned b, unsigned n) {
      // Detect if  UINT_MAX + 1 < n*n
      if (UINT_MAX/n < n-1) {
        return TBD_code_with_wider_math(a,b,n);
      }
      a %= n;
      unsigned result = 1u % n;
      while (b > 0) {
        if (b & 1) result = (result * a) % n;
        a = (a * a) % n;
        b >>= 1;
      }
      return result;
    }
    
    #endif
    

    【讨论】:

    • 这是目前唯一能确定问题原因并提出适当解决方案的答案。其他答案都可能溢出。
    【解决方案8】:

    int 对于 RSA 来说通常是不够的(除非您正在处理小型简化示例)

    您需要一种数据类型,可以存储最多 2256(用于 256 位 RSA 密钥)或 2512(用于 512 位密钥等)的整数等512 p>

    【讨论】:

      【解决方案9】:

      这是另一种方式。请记住,当我们在 mod m 下找到 a 中的 modulo multiplicative inverse 时。 那么

      am 必须是 coprime

      我们可以使用gcd extended 来计算modulo multiplicative inverse

      用于计算 abmod mab 可以有超过 105 数字然后计算结果很棘手。

      下面的代码将完成计算部分:

      #include <iostream>
      #include <string>
      using namespace std;
      /*
      *   May this code live long.
      */
      long pow(string,string,long long);
      long pow(long long ,long long ,long long);
      int main() {
          string _num,_pow;
          long long _mod;
          cin>>_num>>_pow>>_mod;
          //cout<<_num<<" "<<_pow<<" "<<_mod<<endl;
          cout<<pow(_num,_pow,_mod)<<endl;
         return 0;
      }
      long pow(string n,string p,long long mod){
          long long num=0,_pow=0;
          for(char c: n){
              num=(num*10+c-48)%mod;
          }
          for(char c: p){
              _pow=(_pow*10+c-48)%(mod-1);
          }
          return pow(num,_pow,mod);
      }
      long pow(long long a,long long p,long long mod){
          long res=1;
          if(a==0)return 0;
          while(p>0){
              if((p&1)==0){
                  p/=2;
                  a=(a*a)%mod;
              }
              else{
                  p--;
                  res=(res*a)%mod;
              }
          }
          return res;
      }
       
      

      此代码有效,因为 abmod m 可以写成 (a mod m )b mod m-1mod m.

      希望对您有所帮助{ :)

      【讨论】:

        【解决方案10】:

        使用快速求幂可能.....给出与上面的模板相同的 o(log n)

            int power(int base, int exp,int mod)
        {
            if(exp == 0)
             return 1;
        
            int p=power(base, exp/2,mod);
            p=(p*p)% mod;
            return (exp%2 == 0)?p:(base * p)%mod;
        }
        

        【讨论】:

        • 拐角失败:power(x, 0, 1) 因为它返回 1 而不是 0。exp &lt; 0 时会出现更多问题。
        【解决方案11】:

        这(加密)更像是一个算法设计问题,而不是一个编程问题。重要的缺失部分是对现代代数的熟悉。我建议你在群论和数论中寻找一个巨大的优化。 如果n是素数,pow(a,n-1)%n==1(假设是无限位整数)。所以,基本上你需要计算pow(a,b%(n-1))%n;根据群论,你可以找到e,这样每隔一个数就等于en 的幂。因此范围[1..n-1] 可以表示为e 的幂的排列。给定算法为n 找到ea 的对数基数e,可以显着简化计算。密码学需要数学背景的基调;我宁愿在没有足够背景的情况下离开那个地方。

        【讨论】:

          【解决方案12】:

          对于我的代码 a^k mod n in php:

          function pmod(a, k, n)
          {
              if (n==1) return 0;
              power = 1;
              for(i=1; i<=k; $i++)
              {
                  power = (power*a) % n;
              }
              return power;
          }
          

          【讨论】:

            【解决方案13】:
            #include <cmath>
            ...
            static_cast<int>(std::pow(a,b))%n
            

            但我最好的选择是,在创建完全相同的函数时,我遇到了同样的问题。

            【讨论】:

              【解决方案14】:

              我正在使用这个功能:

              int CalculateMod(int base, int exp ,int mod){
                  int result;
                  result = (int) pow(base,exp);
                  result = result % mod;
                  return result;
              }
              

              我解析变量结果是因为 pow 会返回一个双精度值,而对于使用 mod,您需要两个 int 类型的变量,无论如何,在 RSA 解密中,您应该只使用整数。

              【讨论】:

              • 如果 pow(base, exp) 的结果大于 MAXINT,那么在转换回 int 时会出现异常。即使您没有遇到异常,您也会使用错误编号的 mod。