RSA算法C語言實現(支持任意位密鑰)


代碼路徑:https://github.com/prophetss/rsa

  之前分享過三種常用MD5、SHA2和AES加密算法(點這里)實現源碼,前三者分別屬於哈希加密和對稱加密,而另一種很常用的非對稱加密RSA算法實現這次分享出來。RSA算法的原理和用途大家可以網上自行搜索。雖然其算法原理很簡單,但是由於其密鑰長度很長(一般至少1024位),所以實際在其相互運算以及大質數查找會牽扯很多算法理論,因此我這里代碼實現中數學運算是直接基於GMP(The GNU Multiple Precision Arithmetic Library),我將其linux64位下編譯好的動靜態庫都已上傳,如果環境相同可以不用安裝直接使用。另外針對RSA算法內的大質數p和q以及公鑰e和私鑰d等也是有要求的(參考這里),這塊我是參考JDK內RSA算法的實現。

  下面開始准備工作,首先是大質數的選取,由於大質數判斷是相當困難,所以當前對於大質數的選取都是概率選取,先看下JDK內實現(我查看的代碼版本是jdk-10.0.1)

BigInteger.java內762-769行,其中SMALL_PRIME_THRESHOLD為常量95 ,DEFAULT_PRIME_CERTAINTY為常量100,rnd入參是一個隨機數,所以是按95位為分界對應兩個不同方法,我這里只考慮長密鑰所以直接看大的

    public static BigInteger probablePrime(int bitLength, Random rnd) {
        if (bitLength < 2)
            throw new ArithmeticException("bitLength < 2");

        return (bitLength < SMALL_PRIME_THRESHOLD ?
                smallPrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd) :
                largePrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd));
    }

BigInteger.java內822-841行,certainty就是之前傳入的常量100,而其結果是調用BitSieve類中的retrieve方法獲取,為了簡單些我這就跳過這個函數代碼了,里面大體是循環調用BigInteger類內primeToCertainty方法。

    private static BigInteger largePrime(int bitLength, int certainty, Random rnd) {
        BigInteger p;
        p = new BigInteger(bitLength, rnd).setBit(bitLength-1);
        p.mag[p.mag.length-1] &= 0xfffffffe;

        // Use a sieve length likely to contain the next prime number
        int searchLen = getPrimeSearchLen(bitLength);
        BitSieve searchSieve = new BitSieve(p, searchLen);
        BigInteger candidate = searchSieve.retrieve(p, certainty, rnd);

        while ((candidate == null) || (candidate.bitLength() != bitLength)) {
            p = p.add(BigInteger.valueOf(2*searchLen));
            if (p.bitLength() != bitLength)
                p = new BigInteger(bitLength, rnd).setBit(bitLength-1);
            p.mag[p.mag.length-1] &= 0xfffffffe;
            searchSieve = new BitSieve(p, searchLen);
            candidate = searchSieve.retrieve(p, certainty, rnd);
        }
        return candidate;
    }

BigInteger.java內934-962行,可以看到最后調用MillerRabin和LucasLehmer算法,前者中rounds為迭代輪數,而這個算法檢測一輪為質數實際為不為質數的概率為1/4(參考這里),后者也是一個偽素數檢測算法,感興趣可以參考這里,所以可以看出JDK中RSA算法獲取的是概率質數,其概率跟其位數相關。

    boolean primeToCertainty(int certainty, Random random) {
        int rounds = 0;
        int n = (Math.min(certainty, Integer.MAX_VALUE-1)+1)/2;

        // The relationship between the certainty and the number of rounds
        // we perform is given in the draft standard ANSI X9.80, "PRIME
        // NUMBER GENERATION, PRIMALITY TESTING, AND PRIMALITY CERTIFICATES".
        int sizeInBits = this.bitLength();
        if (sizeInBits < 100) {
            rounds = 50;
            rounds = n < rounds ? n : rounds;
            return passesMillerRabin(rounds, random);
        }

        if (sizeInBits < 256) {
            rounds = 27;
        } else if (sizeInBits < 512) {
            rounds = 15;
        } else if (sizeInBits < 768) {
            rounds = 8;
        } else if (sizeInBits < 1024) {
            rounds = 4;
        } else {
            rounds = 2;
        }
        rounds = n < rounds ? n : rounds;

        return passesMillerRabin(rounds, random) && passesLucasLehmer();
    }

  通過查詢GMP庫中對應函數,其文檔有兩個相關函數int mpz_probab_prime_p (const mpz t n, int reps)和void mpz_nextprime (mpz t rop, const mpz t op)前者是概率判斷n(入參)是否為質數,其概率值為4的reps(入參)次方,后者是直接給出一個大於op(入參)的概率質數rop(出參)。所以前者就是對應JDK中的primeToCertainty方法,后者對應probablePrime方法,為了簡單我這里使用mpz_nextprime 函數直接獲取,這個函數沒有給出是質數的概率,文檔也沒有說明只是說結果是合數的概率很小(原文:“This function uses a probabilistic algorithm to identify primes. For practical purposes it’s adequate, the chance of a composite passing will be extremely small.”)。我查了下源碼,這個概率可能是4的-25次方(最后調用了25輪MillerRabin算法)

  有了獲取大質數的獲取方法剩下的難點就是查詢GMP文檔學習英語了..JDK內的RSA加密解密是基於CRT的,我不太清楚這塊原理所以將這部分去掉了,代碼封裝了字符串加解密(每次加密數據大小不可超過密鑰大小),下面展示下在我機子上的運行結果(2048bit密鑰):

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM