将一个数分解为两个质因数的乘积

3
Telegram身份验证的要求之一是将给定的数字分解为两个质因数。具体而言,P * Q = N,其中N < 2^63。如何找到较小的质因数,使得P < square_root(N)
我的建议:
1)预先计算从3到2^31.5的质数,然后测试N mod P = 0
2)找到一种测试质数的算法(但我们仍然必须测试N mod P = 0)。
是否有适用于此情况的质数算法?

1
你确定这是一个要求吗?他们描述的整个东西在某种意义上看起来像RSA,但密钥长度很短。在真正的RSA中,pq的长度很重要,没有公开已知的算法可以在合理的时间内分解它。然而,如果你真的想分解小于2^63(即18位数字)的半素数,Pollard Rho在C++上可以在几天内完成。这里有Pollard Rho的列表。特殊数域筛法可以在几秒钟内完成。而且它太小了,甚至不值得使用GNFS。看看msieve。它应该会让你感兴趣。 - WDS
@wds这是一个要求。我甚至链接到了他们API页面的部分。 - Charles Okwuagwu
@wds,我认为你在时间要求方面是不正确的。我们已经有了N。我们所需要做的就是将其分解成2个质数。我已经用我的vb.net代码通过暴力破解完成了这个过程,只用了4分钟。我只是希望能得到一些帮助,将这个时间缩短到几秒钟或更少。 - Charles Okwuagwu
1
非常抱歉,你关于时间要求是正确的。我根据自己使用Pollard Rho算法的经验和估计2^63范围内数字大约有18位数来进行估计。这一点是正确的。我记得我的Pollard Rho算法用几秒钟就可以处理12位数,但我错误地记住了一个关键事实。那几秒钟是在质因数为12位数时,而不是乘积为12位数时。我的程序可以在眨眼之间分解一个18位数的半素数。我会为您发布它。 - WDS
1
@CharlesO,GNU factor代码是Neil和Torbjörn的NT项目。它是自包含的,没有GMP,128位。我的本机代码在https://github.com/danaj/Math-Prime-Util中,可以编译独立的factor.c(请参见末尾),GMP代码在https://github.com/danaj/Math-Prime-Util-GMP上。我的待办项目之一是将所有内容转换为常规C库。 - DanaJ
显示剩余3条评论
2个回答

4

Pollard's Rho算法 [VB.Net]

该算法可快速找到 N < 2^63,满足 P*Q = NP 值。

Dim rnd As New System.Random

Function PollardRho(n As BigInteger) As BigInteger
    If n Mod 2 = 0 Then Return 2

    Dim x As BigInteger = rnd.Next(1, 1000)
    Dim c As BigInteger = rnd.Next(1, 1000)
    Dim g As BigInteger = 1
    Dim y = x

    While g = 1
        x = ((x * x) Mod n + c) Mod n
        y = ((y * y) Mod n + c) Mod n
        y = ((y * y) Mod n + c) Mod n
        g = gcd(BigInteger.Abs(x - y), n)
    End While

    Return g
End Function

Function gcd(a As BigInteger, b As BigInteger) As BigInteger
    Dim r As BigInteger
    While b <> 0
        r = a Mod b
        a = b
        b = r
    End While
    Return a
End Function

Richard Brent算法[VB.Net] 这个更快。

Function Brent(n As BigInteger) As BigInteger
    If n Mod 2 = 0 Then Return 2

    Dim y As BigInteger = rnd.Next(1, 1000)
    Dim c As BigInteger = rnd.Next(1, 1000)
    Dim m As BigInteger = rnd.Next(1, 1000)

    Dim g As BigInteger = 1
    Dim r As BigInteger = 1
    Dim q As BigInteger = 1

    Dim x As BigInteger = 0
    Dim ys As BigInteger = 0

    While g = 1
        x = y
        For i = 1 To r
            y = ((y * y) Mod n + c) Mod n
        Next
        Dim k = New BigInteger(0)
        While (k < r And g = 1)
            ys = y
            For i = 1 To BigInteger.Min(m, r - k)
                y = ((y * y) Mod n + c) Mod n
                q = q * (BigInteger.Abs(x - y)) Mod n
            Next

            g = gcd(q, n)
            k = k + m
        End While
        r = r * 2
    End While

    If g = n Then
        While True
            ys = ((ys * ys) Mod n + c) Mod n
            g = gcd(BigInteger.Abs(x - ys), n)
            If g > 1 Then
                Exit While
            End If
        End While
    End If

    Return g
End Function

2

糟糕!我刚刚把这个程序放进去,然后才意识到你的问题标记了C#。这是C ++,我几年前写的Pollard Rho版本,并在此处发布以帮助其他人理解它。与试除法相比,它在分解半素数时快得多。正如我所说,我很遗憾它不是C#,但你应该能够理解概念,甚至可以轻松移植它。作为奖励,.NET库具有用于处理任意大整数的命名空间,我的C ++实现需要我查找第三方库来使用它们。无论如何,即使在C#中,以下程序也将在不到1秒钟的时间内将一个2^63阶半素数分解成2个质数。有比这更快的算法,但它们要复杂得多。

#include <string>
#include <stdio.h>
#include <iostream>
#include "BigIntegerLibrary.hh"

typedef BigInteger BI;
typedef BigUnsigned BU;

using std::string;
using std::cin;
using std::cout;

BU pollard(BU &numberToFactor);
BU gcda(BU differenceBetweenCongruentFunctions, BU numberToFactor);
BU f(BU &x, BU &numberToFactor, int &increment);
void initializeArrays();
BU getNumberToFactor ();
void factorComposites();
bool testForComposite (BU &num);

BU primeFactors[1000];
BU compositeFactors[1000];
BU tempFactors [1000];
int primeIndex;
int compositeIndex;
int tempIndex;
int numberOfCompositeFactors;
bool allJTestsShowComposite;

int main ()
{
    while(1)
    {
        primeIndex=0;
        compositeIndex=0;
        tempIndex=0;
        initializeArrays();
        compositeFactors[0] = getNumberToFactor();
        cout<<"\n\n";
        if (compositeFactors[0] == 0) return 0;
        numberOfCompositeFactors = 1;
        factorComposites();
    }
}

void initializeArrays()
{
    for (int i = 0; i<1000;i++)
    {
        primeFactors[i] = 0;
        compositeFactors[i]=0;
        tempFactors[i]=0;
    }
}

BU getNumberToFactor ()
{
    std::string s;
    std::cout<<"Enter the number for which you want a prime factor, or 0 to     quit: ";
    std::cin>>s;
    return stringToBigUnsigned(s);
}

void factorComposites()
{
    while (numberOfCompositeFactors!=0)
    {
        compositeIndex = 0;
        tempIndex = 0;

        // This while loop finds non-zero values in compositeFactors.
        // If they are composite, it factors them and puts one factor in     tempFactors,
        // then divides the element in compositeFactors by the same amount.
        // If the element is prime, it moves it into tempFactors (zeros the     element in compositeFactors)
        while (compositeIndex < 1000)
        {
            if(compositeFactors[compositeIndex] == 0)
            {
                compositeIndex++;
                continue;
            }
            if(testForComposite(compositeFactors[compositeIndex]) == false)
            {
                tempFactors[tempIndex] = compositeFactors[compositeIndex];
                compositeFactors[compositeIndex] = 0;
                tempIndex++;
                compositeIndex++;
            }
            else
            {
                tempFactors[tempIndex] = pollard     (compositeFactors[compositeIndex]);
                compositeFactors[compositeIndex] /= tempFactors[tempIndex];
                tempIndex++;
                compositeIndex++;
            }
        }
        compositeIndex = 0;

        // This while loop moves all remaining non-zero values from     compositeFactors into tempFactors
        // When it is done, compositeFactors should be all 0 value elements
        while (compositeIndex < 1000)
        {
            if (compositeFactors[compositeIndex] != 0)
            {
                tempFactors[tempIndex] = compositeFactors[compositeIndex];
                compositeFactors[compositeIndex] = 0;
                tempIndex++;
                compositeIndex++;
            }
            else compositeIndex++;
        }
        compositeIndex = 0;
        tempIndex = 0;
    // This while loop checks all non-zero elements in tempIndex.
    // Those that are prime are shown on screen and moved to primeFactors
    // Those that are composite are moved to compositeFactors
    // When this is done, all elements in tempFactors should be 0
    while (tempIndex<1000)
    {
        if(tempFactors[tempIndex] == 0)
        {
            tempIndex++;
            continue;
        }
        if(testForComposite(tempFactors[tempIndex]) == false)
        {
            primeFactors[primeIndex] = tempFactors[tempIndex];
            cout<<primeFactors[primeIndex]<<"\n";
            tempFactors[tempIndex]=0;
            primeIndex++;
            tempIndex++;
        }
        else
        {
            compositeFactors[compositeIndex] = tempFactors[tempIndex];
            tempFactors[tempIndex]=0;
            compositeIndex++;
            tempIndex++;
        }
    }
    compositeIndex=0;
    numberOfCompositeFactors=0;

    // This while loop just checks to be sure there are still one or more composite factors.
    // As long as there are, the outer while loop will repeat
    while(compositeIndex<1000)
    {
        if(compositeFactors[compositeIndex]!=0) numberOfCompositeFactors++;
        compositeIndex ++;
    }
}
return;
}

// The following method uses the Miller-Rabin primality test to prove with 100% confidence a given number is composite,
// or to establish with a high level of confidence -- but not 100% -- that it is prime

bool testForComposite (BU &num)
{
    BU confidenceFactor = 101;
    if (confidenceFactor >= num) confidenceFactor = num-1;
    BU a,d,s, nMinusOne;
    nMinusOne=num-1;
    d=nMinusOne;
    s=0;
    while(modexp(d,1,2)==0)
    {
        d /= 2;
        s++;
    }
    allJTestsShowComposite = true; // assume composite here until we can prove otherwise
    for (BI i = 2 ; i<=confidenceFactor;i++)
    {
        if (modexp(i,d,num) == 1) 
            continue;  // if this modulus is 1, then we cannot prove that num is composite with this value of i, so continue
        if (modexp(i,d,num) == nMinusOne)
        {
            allJTestsShowComposite = false;
            continue;
        }
        BU exponent(1);     
        for (BU j(0); j.toInt()<=s.toInt()-1;j++)
        {
            exponent *= 2;
            if (modexp(i,exponent*d,num) == nMinusOne)
            {
                // if the modulus is not right for even a single j, then break and increment i.
                allJTestsShowComposite = false;
                continue;
            }
        }
        if (allJTestsShowComposite == true) return true; // proven composite with 100% certainty, no need to continue testing
    }
    return false;
    /* not proven composite in any test, so assume prime with a possibility of error = 
    (1/4)^(number of different values of i tested).  This will be equal to the value of the
confidenceFactor variable, and the "witnesses" to the primality of the number being tested will be all integers from
2 through the value of confidenceFactor.

Note that this makes this primality test cryptographically less secure than it could be.  It is theoretically possible,
if difficult, for a malicious party to pass a known composite number for which all of the lowest n integers fail to
detect that it is composite.  A safer way is to generate random integers in the outer "for" loop and use those in place of
the variable i.  Better still if those random numbers are checked to ensure no duplicates are generated.
*/
}

BU pollard(BU &n)
{
    if (n == 4) return 2;
    BU x = 2;
    BU y = 2;
    BU d = 1;
    int increment = 1;

    while(d==1||d==n||d==0)
    {
        x = f(x,n, increment);
        y = f(y,n, increment);
        y = f(y,n, increment);
        if (y>x)
        {
            d = gcda(y-x, n);
        }
        else
        {
            d = gcda(x-y, n);
        }
        if (d==0) 
        {
            x = 2;
            y = 2;
            d = 1;
            increment++; // This changes the pseudorandom function we use to increment x and y
        }
    }
    return d;
}


BU gcda(BU a, BU b)
{
    if (a==b||a==0)
        return 0;   // If x==y or if the absolute value of (x-y) == the number to be factored, then we have failed to find
                    // a factor.  I think this is not proof of primality, so the process could be repeated with a new function.
                    // For example, by replacing x*x+1 with x*x+2, and so on.  If many such functions fail, primality is likely.

    BU currentGCD = 1;
    while (currentGCD!=0) // This while loop is based on Euclid's algorithm
    {
        currentGCD = b % a;
        b=a;
        a=currentGCD;
    }
    return b;
}

BU f(BU &x, BU &n, int &increment)
{
    return (x * x + increment) % n;
}

1
顺便说一下,我不知道这是否是SO上被接受的做法,但如果您想在尝试移植和编译之前先尝试它,这里是我的Dropbox上可执行文件的链接。 如果您复制了它,当然要进行病毒扫描。 您不会发现其中有任何病毒,因为其中没有病毒。 但是这仍然是一个好习惯。 https://www.dropbox.com/s/qvinsmnjy1ft82g/Pollard%20Rho%20Complete.exe?dl=0 - WDS
请问您能否指导我使用名称更快的算法,最好有 .net 实现。谢谢。 - Charles Okwuagwu
很遗憾,我从未见过比这个Pollard Rho更快的.net解决方案。虽然更快是可能的,但我从未见过有人这么写。但是为了让你知道可能性,可以看看msieve。请到这里:http://sourceforge.net/projects/msieve/files/msieve/Msieve%20v1.52/下载并解压文件msieve152_gpu.zip。然后在cmd窗口中浏览到该文件夹,输入“msieve152_gpu -q 1724114033281923457”,然后按回车键。它将在一秒钟内因式分解该数字。我认为还有比这个更快的程序,但这是我见过最快的程序,非常不错。 - WDS
谢谢您指明方向。.Net 的 BigInteger 简化了实施过程。但是我想知道在 N <2^63 的情况下,是否也可以使用 UInt64 来代替 BigInteger - Charles Okwuagwu
当没有计算超出该变量范围时,UInt64 可以很好地工作。另外,UInt64 比 BigInteger 更易于使用,并且速度更快。 - WDS
显示剩余2条评论

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