0

In the RSA asymmetric cryptography algorithm every user has a public key (n,e) and a private key (d) to send and receive encrypted messages from other users.

To encrypt a message it changes the characters to their ascii codes:

HELLO -> 72-69-76-76-79

and to send the message encrypted with RSA (c), has to calculate

c = m^e % n

for each character m in the message using the public keys n and e.

to decrypt a message that the user receives has to calculate:

m = c^d % n (is the same to say m is congruent to c^d mod n)

to each number c usign the private key d.


A little example:

user Beto got the public keys:

n = 64523 
e = 127

and his private key:

d = 15583

if some user wants to send a message to Beto:

ABAC -> 65-66-65-67

to encrypt the message the user had to calculate

65^127 % 64523 = 27725
66^127 % 64523 = 6407
65^127 % 64523 = 27725
67^127 % 64523 = 2523

and the encrypted code was 27725-6407-27725-2523

Beto to decipher the message had to calculate:

27725^15583 % 64523 = 65
6407^15583 % 64523 = 66
27725^15583 % 64523 = 65
2523^15583 % 64523 = 67

and he got the decrypted message 65-66-65-67 => ABAC.


Now the question:

I have this code to solve the last part, but I cant use it with big numbers (like the ones in the example):

function getCongruence(c, d, n) {
  return Math.pow(c,d) % n;
}

console.log(getCongruence(5,3,7)); // = 6 cuz 5^3=125 and 125 % 7 => 125 - 7*17 = 125 -119
console.log(getCongruence(19,11,17)); // = 8 cuz 19^11=116490258898219 % 17 = 8
console.log(getCongruence(27725,15583,64523)); // should be 65 but it shows NaN
.as-console-wrapper { max-height: 100% !important; top: 0; }

How can I get the result if use big numbers?

Can I use another algorithm to find the answer?

there is a library I can use to do it?

the-breaker
  • 195
  • 1
  • 13
  • javascript does have a `bigInt` object for dealing with numbers larger than 2^53-1. – PeterN Sep 28 '19 at 14:00
  • @PeterN I know it, that's the reason of my question. – the-breaker Sep 28 '19 at 14:03
  • Even if that function worked it still wouldn't work (lol), I mean, you cannot separate the exponentiation and the modular reduction, the intermediate result would become too large, at least for reasonable message and key size. – harold Sep 28 '19 at 14:14
  • Encrypting individual ASCII characters is pretty useless by the way, that just turns it into a substitution cypher that you could break with frequency analysis. – harold Sep 28 '19 at 14:17
  • @harold first I think so, that's the reason I'm searching another way to get it and second take it easy, it's just an example to teach algorithms. – the-breaker Sep 28 '19 at 14:21
  • [This one](https://stackoverflow.com/q/30694842/555045) looks close to what you can use – harold Sep 28 '19 at 14:38

2 Answers2

2

Edit

As per @harold's suggestion, iterative exponetiation by squaring is the fastest way to approach this, I'll leave my original naive recursive method below for comparison.

Edit 2

Added function to handle the inverse of a BigInt for very small numbers. Used @harold's suggestion to move the modulo reduction inside the function for a performance boost.

Iterative exponentiation by squaring:

const handleBigInverse = x => {
  const stringX = x.toString();

  if (stringX.length > 21) {
    const approximate = Number(stringX.slice(0, 21));
    const e = stringX.length - 21;

    const inverse = 1 / approximate;
    const inverseString = inverse.toString();

    const splitString = inverseString.split("e");
    splitString[1] = (Number(splitString[1]) - e).toString();

    return splitString.join("e");
  } else {
    const inverse = 1 / Number(x);
    return inverse.toString();
  }
};

const iterativeExpBySqWithMod = (x, n, mod) => {
  let bigX = BigInt(x);
  let bigN = BigInt(n);

  if (n < 0) {
    if (!mod || Math.abs(mod) >= 1) {
      return handleBigInverse(iterativeExpBySqWithMod(x, -n));
    } else {
      return (
        handleBigInverse(iterativeExpBySqWithMod(x, -n)) % mod
      ).toString();
    }
  }
  if (mod) {
    const bigMod = BigInt(mod);
    let result = BigInt(1);

    while (bigN > 0) {
      if (bigN % BigInt(2) == 1) {
        result = (result * bigX) % bigMod;
      }
      bigX = (bigX * bigX) % bigMod;
      bigN /= BigInt(2);
    }
    return result;
  } else {
    let result = BigInt(1);
    while (bigN > 0) {
      if (bigN % BigInt(2) == 1) {
        result *= bigX;
      }
      bigX *= bigX;
      bigN /= BigInt(2);
    }
    return result;
  }
};

// Big numbers output a bigInt
console.log(iterativeExpBySqWithMod(27725, 15583, 64523)); //65n

// Small numbers output a string
console.log(iterativeExpBySqWithMod(72552, -50102)); //"5.550317946486025e-243529"


Naive recursive:

Adjust the maxStack param depending on the environment it will be running in:

function getCongruence(c, d, n, maxStack) {
  return getPow(BigInt(c), BigInt(d), BigInt(maxStack)) % BigInt(n);
}

const recursivePow = (value, exponent, total) => {
  if (exponent > 1) {
    exponent--;
    return recursivePow(value, exponent, total) * value;
  } else {
    if (total) {
      return total * value;
    } else {
      return value;
    }
  }
};

const getPow = (value, exponent, maxStack) => {
  let total = BigInt(0);

  while (exponent > maxStack) {
    total = recursivePow(value, maxStack, total);
    exponent -= maxStack;
  }
  return recursivePow(value, exponent, total);
};

console.log(getCongruence(27725, 15583, 64523, 3000)); //65n
PeterN
  • 1,817
  • 2
  • 9
  • 12
  • thanks for the answer, I tried to run it, but it doesn't work – the-breaker Sep 28 '19 at 16:03
  • It works here running on node, what environment are you running in node or browser? – PeterN Sep 28 '19 at 16:14
  • I just pasted it into the dev console in chrome and it worked there, what's the error message? – PeterN Sep 28 '19 at 16:17
  • 1
    This seems more complicated than the usual iterative exponentiation by squaring – harold Sep 28 '19 at 16:27
  • oh, just get it, I was trying in a snippet, it runs if you add `''+` to the result. – the-breaker Sep 28 '19 at 16:29
  • 1
    @the-breaker ok cool, I did a few quick tests and, it handled fairly large numbers, but perhaps it could be combined with harold's suggestion of exponentiation by squaring to increase performance. – PeterN Sep 28 '19 at 16:49
  • @harold, after checking out your suggestion, yes iterative exponentiation by squaring is the way to go, a difference of O(n) vs O(lg n) without the call stack issue, a nice one for the toolbox, thanks! – PeterN Sep 28 '19 at 18:14
  • 1
    I recommend applying the modulo reduction during the exponentiation also, to prevent the numbers getting very large/slow – harold Sep 28 '19 at 18:46
0

I don't know much about RSA yet, I'm trying to learn it though.

But at least I know how to fix your code:

function getCongruence(c, d, n) {
  return c**d % n;
}

console.log(getCongruence(5,3,7)); // = 6 cuz 5^3=125 and 125 % 7 => 125 - 7*17 = 125 -119
console.log(getCongruence(19,11,17)); // = 8 cuz 19^11=116490258898219 % 17 = 8
console.log(getCongruence(27725n,15583n,64523n)); // now returns 65n (n as in BigInt)

But harold said this (is that true?):

Even if that function worked it still wouldn't work (lol), I mean, you cannot separate the exponentiation and the modular reduction, the intermediate result would become too large, at least for reasonable message and key size. – harold

Regarding the rest of the code I can't get your calculation to work in JS, I don't know why (it works in a calculator though)?

console.log(65^127 % 64523) // 62 instead of 27725
console.log(27725^15583 % 64523) // 20626 instead of 65

// Trying the same with BigInt's:
console.log(65n^127n % 64523n) // 62 instead of 27725
console.log(27725n^15583n % 64523n) // 20626 instead of 65

// This works though:
console.log(65n**127n % 64523n) // 27725
console.log(27725n**15583n % 64523n) // 65

Ah, I get it now. c**d generates a super huge number in memory if used with real world cryptographic keys, hence it will not work. But I guess this will:

function getCongruence(c, d, n) {
  return powermod(c,d,n)
}
// from: https://stackoverflow.com/questions/30694842/exponentiation-by-squaring
function powermod(base, exponent, modulus) {
  if (base < 0n || exponent < 0n || modulus < 1n)
      return -1n
  let result = 1n
  while (exponent > 0n) {
    if ((exponent % 2n) == 1n) {
      result = (result * base) % modulus
    }
    log(base * base)
    base = (base * base) % modulus
    exponent = exponent / 2n // floor
  }
  return result
}

Or maybe use the one by PeterN (above here), like this:

function getCongruence(c, d, n) {
  return iterativeExpBySqWithMod(c,d,n)
}