/* $Id: t_bdRSA.c $ */ /* Test BigDigits "bd" functions using a new RSA key and random data */ /***** BEGIN LICENSE BLOCK ***** * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright (c) 2001-15 David Ireland, D.I. Management Services Pty Limited * . All rights reserved. * ***** END LICENSE BLOCK *****/ /* * Last updated: * $Date: 2015-10-22 10:23:00 $ * $Revision: 2.5.0 $ * $Author: dai $ */ #if _MSC_VER >= 1100 /* Detect memory leaks in MSVC++ */ #define _CRTDBG_MAP_ALLOC #include #include #else #include #endif #include #include #include #include #include "bigd.h" static int my_rand(unsigned char *bytes, size_t nbytes, const unsigned char *seed, size_t seedlen) /* Our own (very insecure) random generator func using good old rand() but in the required format for BD_RANDFUNC -- replace this in practice with your own cryptographically-secure function -- or use bdRandomOctets() in bigdRand.h */ { unsigned int myseed; size_t i; int offset; /* Use time for 32-bit seed - then blend in user-supplied seed, if any */ myseed = (unsigned)time(NULL) ^ (unsigned)clock(); if (seed) { for (offset = 0, i = 0; i < seedlen; i++, offset = (offset + 1) % sizeof(unsigned)) myseed ^= ((unsigned int)seed[i] << (offset * 8)); } srand(myseed); while (nbytes--) { *bytes++ = rand() & 0xFF; } return 0; } #define give_a_sign(c) putchar((c)) static bdigit_t SMALL_PRIMES[] = { 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, }; #define N_SMALL_PRIMES (sizeof(SMALL_PRIMES)/sizeof(bdigit_t)) int generateRSAPrime(BIGD p, size_t nbits, bdigit_t e, size_t ntests, const unsigned char *seed, size_t seedlen, BD_RANDFUNC randFunc) /* Create a prime p such that gcd(p-1, e) = 1. Returns # prime tests carried out or -1 if failed. Sets the TWO highest bits to ensure that the product pq will always have its high bit set. e MUST be a prime > 2. This function assumes that e is prime so we can do the less expensive test p mod e != 1 instead of gcd(p-1, e) == 1. Uses improvement in trial division from Menezes 4.51. */ { BIGD u; size_t i, j, iloop, maxloops, maxodd; int done, overflow, failedtrial; int count = 0; bdigit_t r[N_SMALL_PRIMES]; /* Create a temp */ u = bdNew(); maxodd = nbits * 100; maxloops = 5; done = 0; for (iloop = 0; !done && iloop < maxloops; iloop++) { /* Set candidate n0 as random odd number */ bdRandomSeeded(p, nbits, seed, seedlen, randFunc); /* Set two highest and low bits */ bdSetBit(p, nbits - 1, 1); bdSetBit(p, nbits - 2, 1); bdSetBit(p, 0, 1); /* To improve trial division, compute table R[q] = n0 mod q for each odd prime q <= B */ for (i = 0; i < N_SMALL_PRIMES; i++) { r[i] = bdShortMod(u, p, SMALL_PRIMES[i]); } done = overflow = 0; /* Try every odd number n0, n0+2, n0+4,... until we succeed */ for (j = 0; j < maxodd; j++, overflow = bdShortAdd(p, p, 2)) { /* Check for overflow */ if (overflow) break; give_a_sign('.'); count++; /* Each time 2 is added to the current candidate update table R[q] = (R[q] + 2) mod q */ if (j > 0) { for (i = 0; i < N_SMALL_PRIMES; i++) { r[i] = (r[i] + 2) % SMALL_PRIMES[i]; } } /* Candidate passes the trial division stage if and only if NONE of the R[q] values equal zero */ for (failedtrial = 0, i = 0; i < N_SMALL_PRIMES; i++) { if (r[i] == 0) { failedtrial = 1; break; } } if (failedtrial) continue; /* If p mod e = 1 then gcd(p, e) > 1, so try again */ bdShortMod(u, p, e); if (bdShortCmp(u, 1) == 0) continue; /* Do expensive primality test */ give_a_sign('*'); if (bdRabinMiller(p, ntests)) { /* Success! - we have a prime */ done = 1; break; } } } /* Clear up */ bdFree(&u); printf("\n"); return (done ? count : -1); } int generateRSAKey(BIGD n, BIGD e, BIGD d, BIGD p, BIGD q, BIGD dP, BIGD dQ, BIGD qInv, size_t nbits, bdigit_t ee, size_t ntests, unsigned char *seed, size_t seedlen, BD_RANDFUNC randFunc) { BIGD g, p1, q1, phi; size_t np, nq; unsigned char *myseed = NULL; clock_t start, finish; double duration, tmake; long ptests; int res; /* Initialise */ g = bdNew(); p1 = bdNew(); q1 = bdNew(); phi = bdNew(); printf("Generating a %d-bit RSA key...\n", nbits); /* We add an extra byte to the user-supplied seed */ myseed = malloc(seedlen + 1); if (!myseed) return -1; memcpy(myseed, seed, seedlen); /* Do (p, q) in two halves, approx equal */ nq = nbits / 2 ; np = nbits - nq; /* Make sure seeds are slightly different for p and q */ myseed[seedlen] = 0x01; start = clock(); res = generateRSAPrime(p, np, ee, ntests, myseed, seedlen+1, randFunc); finish = clock(); bdPrintHex("p=", p, "\n"); assert(res > 0); duration = (double)(finish - start) / CLOCKS_PER_SEC; printf("generateRSAPrime took %.3f secs and %d prime candidates (%.4f s/test)\n", duration, res, duration / res); ptests = res; tmake = duration; printf("p is %d bits\n", bdBitLength(p)); myseed[seedlen] = 0xff; start = clock(); res = generateRSAPrime(q, nq, ee, ntests, myseed, seedlen+1, randFunc); finish = clock(); bdPrintHex("q=", q, "\n"); assert(res > 0); duration = (double)(finish - start) / CLOCKS_PER_SEC; printf("generateRSAPrime took %.3f secs and %d prime candidates (%.4f s/test)\n", duration, res, duration / res); ptests += res; tmake += duration; printf("q is %d bits\n", bdBitLength(q)); /* Check that p != q (if so, RNG is faulty!) */ assert(!bdIsEqual(p, q)); bdSetShort(e, ee); bdPrintHex("e=", e, "\n"); /* If q > p swap p and q so p > q */ if (bdCompare(p, q) < 1) { bdSetEqual(g, p); bdSetEqual(p, q); bdSetEqual(q, g); } /* Calc p-1 and q-1 */ bdSetEqual(p1, p); bdDecrement(p1); bdPrintHex("p-1=\n", p1, "\n"); bdSetEqual(q1, q); bdDecrement(q1); bdPrintHex("q-1=\n", q1, "\n"); /* Check gcd(p-1, e) = 1 */ bdGcd(g, p1, e); bdPrintHex("gcd(p-1,e)=", g, "\n"); assert(bdShortCmp(g, 1) == 0); bdGcd(g, q1, e); bdPrintHex("gcd(q-1,e)=", g, "\n"); assert(bdShortCmp(g, 1) == 0); /* Compute n = pq */ bdMultiply(n, p, q); bdPrintHex("n=\n", n, "\n"); /* Compute d = e^-1 mod (p-1)(q-1) */ bdMultiply(phi, p1, q1); bdPrintHex("phi=\n", phi, "\n"); res = bdModInv(d, e, phi); assert(res == 0); bdPrintHex("d=\n", d, "\n"); /* Check ed = 1 mod phi */ bdModMult(g, e, d, phi); bdPrintHex("ed mod phi=", g, "\n"); assert(bdShortCmp(g, 1) == 0); /* Calculate CRT key values */ printf("CRT values:\n"); bdModInv(dP, e, p1); bdModInv(dQ, e, q1); bdModInv(qInv, q, p); bdPrintHex("dP=", dP, "\n"); bdPrintHex("dQ=", dQ, "\n"); bdPrintHex("qInv=", qInv, "\n"); printf("\nTime to create key = %.3f secs with %ld prime candidates (%.4f s/test)\n\n", tmake, ptests, tmake / ptests); printf("n is %d bits\n", bdBitLength(n)); /* Clean up */ if (myseed) free(myseed); bdFree(&g); bdFree(&p1); bdFree(&q1); bdFree(&phi); return 0; } static int debug = 0; int main(void) { size_t nbits = 1025; /* (use an odd modulus size to see if it breaks anything!) */ unsigned ee = 0x3; size_t ntests = 50; unsigned char *seed = NULL; size_t seedlen = 0; BIGD n, e, d, p, q, dP, dQ, qInv; BIGD m, c, s, hq, h, m1, m2; int res; clock_t start, finish; double tinv, tcrt; /* MSVC memory leak checking stuff */ #if _MSC_VER >= 1100 _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE ); _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDOUT ); _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE ); _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDOUT ); _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE ); _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDOUT ); #endif printf("Test BIGDIGITS with a new %d-bit RSA key and random data.\n", nbits); /* Initialise */ p = bdNew(); q = bdNew(); n = bdNew(); e = bdNew(); d = bdNew(); dP= bdNew(); dQ= bdNew(); qInv= bdNew(); m = bdNew(); c = bdNew(); s = bdNew(); m1 = bdNew(); m2 = bdNew(); h = bdNew(); hq = bdNew(); /* Create RSA key pair (n, e),(d, p, q, dP, dQ, qInv) */ /* NB we use simple my_rand() here -- you should use a proper cryptographically-secure RNG */ res = generateRSAKey(n, e, d, p, q, dP, dQ, qInv, nbits, ee, ntests, seed, seedlen, my_rand); if (res != 0) { printf("Failed to generate RSA key!\n"); goto clean_up; } /* Set a random message m < n */ bdRandomSeeded(m, bdBitLength(n)-1, NULL, 0, my_rand); bdPrintHex("m=\n", m, "\n"); /* Encrypt c = m^e mod n */ bdModExp(c, m, e, n); bdPrintHex("c=\n", c, "\n"); /* Check decrypt m1 = c^d mod n */ start = clock(); bdModExp(m1, c, d, n); finish = clock(); tinv = (double)(finish - start) / CLOCKS_PER_SEC; bdPrintHex("m'=\n", m1, "\n"); res = bdCompare(m1, m); printf("Decryption %s\n", (res == 0 ? "OK" : "FAILED!")); assert(res == 0); printf("Decrypt by inversion took %.3f secs\n", tinv); /* Sign s = m^d mod n */ bdModExp(s, m, d, n); bdPrintHex("s=\n", s, "\n"); /* Check verify m1 = s^e mod n */ bdModExp(m1, s, e, n); bdPrintHex("m'=\n", m1, "\n"); res = bdCompare(m1, m); printf("Verification %s\n", (res == 0 ? "OK" : "FAILED!")); assert(res == 0); /* Decrypt using CRT method - Ref: PKCS #1 */ bdPrintHex("m=", m, "\n"); bdPrintHex("c=", c, "\n"); bdPrintHex("p=", p, "\n"); bdPrintHex("q=", q, "\n"); start = clock(); /* Let m_1 = c^dP mod p. */ bdModExp(m1, c, dP, p); if(debug)bdPrintHex("m_1=c^dP mod p=", m1, "\n"); /* Let m_2 = c^dQ mod q. */ bdModExp(m2, c, dQ, q); if(debug)bdPrintHex("m_2=c^dQ mod q=", m2, "\n"); if (bdCompare(m1, m2) < 0) bdAdd(m1, m1, p); bdSubtract(m1, m1, m2); if(debug)bdPrintHex("m_1 - m_2=", m1, "\n"); /* Let h = qInv ( m_1 - m_2 ) mod p. */ bdModMult(h, qInv, m1, p); if(debug)bdPrintHex("h=qInv(m1-m2) mod p=", h, "\n"); bdMultiply(hq, h, q); if(debug)bdPrintHex("hq=", hq, "\n"); /* Let m = m_2 + hq. */ bdAdd(m1, m2, hq); finish = clock(); tcrt = (double)(finish - start) / CLOCKS_PER_SEC; if(debug)bdPrintHex("m'=m_2 + hq=", m1, "\n"); bdPrintHex("(CRT)m'=\n", m1, "\n"); res = bdCompare(m1, m); printf("CRT Decryption %s\n", (res == 0 ? "OK" : "FAILED!")); assert(res == 0); printf("Decrypt by CRT took %.3f secs\n", tcrt); printf("c.f. Decrypt by inversion %.3f secs (factor = %.1f)\n", tinv, (tcrt ? tinv / tcrt : 0)); printf("n is %d bits\n", bdBitLength(n)); /* Clean up */ clean_up: bdFree(&n); bdFree(&e); bdFree(&d); bdFree(&p); bdFree(&q); bdFree(&dP); bdFree(&dQ); bdFree(&qInv); bdFree(&m); bdFree(&c); bdFree(&s); bdFree(&m1); bdFree(&m2); bdFree(&h); bdFree(&hq); printf("OK, successfully completed tests.\n"); return 0; }