ccryptographyrsacommand-line-toolmodular-arithmetic

Why do I have inconsistent evaluation of cipher and corresponding plain message using RSA algorithm?


Rationale

To simulate the RSA algorithm in C using a robust command-line application written in C.

Here is my problem:

Taking values of e=7, p=3 and q=11 works fine when encrypting and decrypting. However, when I veer slightly off to use other values for e, p and q there is a high degree of inconsistency for the generated cipher and when I decode it. There was an instance where the cipher was a negative value. :-(

I made an implementation in C:


#include <stdio.h>
#include <unistd.h>
#include <math.h>
#include <stdlib.h>


struct RSACfg {
  long d;
  long e;
  long p;
  long q;
};

long gcd(long a, long b) {
  if (!b)
    return labs(a);
  return gcd(b, a % b);
}
int valid_rsa(const struct RSACfg *c) {
  const long *e = &c->e, *p = &c->p, *q = &c->q, n = *p * (*q),
             m = (*p - 1) * (*q - 1);
  int pass_1 = gcd(*p, *q) == 1, pass_2 = gcd(m, *e) == 1,
      pass_3 = gcd(n, *e) == 1, pass_4 = *e > 0 && *e < (*p * (*q)), pass_5 = 1;

  if (c->d)
    pass_5 = gcd(c->d, m) == 1;

  return pass_1 & pass_2 & pass_3 & pass_4 & pass_5;
}



int make_d(long *d, const struct RSACfg *cfg) {
  if (!valid_rsa(cfg))
    return -1;
  const long p = cfg->p, q = cfg->q, e = cfg->e, n = p * q,
             t = (p - 1) * (q - 1);
#ifdef DEBUG
    printf("info: solving '%ld x d = 1 (mod %ld)' using `make_d(long*, const struct RSACfg*)`\n", e, t);
#endif
  for (long i = 1; i < t; i++){
  
    if ((i * e) % t == 1 && gcd(i, t) == 1) {
      *d = i;
      return 0;
    }
 }
#ifdef DEBUG
  fprintf(stderr,
          "error: %ld could not be used to calculate 'd' with totient %ld\n", e,
          t);
#endif
  return 1; // not invertible
}

int mod_inv(long *inv, const long a, const long n) {
  long t = 0, newt = 1;
  long r = n, newr = a;

  while (newr) {
    long quotient = r / newr, tmp = t;
    t = newt;
    newt = tmp - quotient * newt;
    tmp = r;
    r = newr;
    newr = tmp - quotient * newr;
  }

  if (r > 1)
    return 1;

  if (t < 0)
    t += n;

  *inv = t;
  return 0;
}

int encrypt(long *M, const long m, const struct RSACfg *r) {
  if (!valid_rsa(r))
    return 1;
  *M = ((long)pow(m, r->e)) % (r->p * r->q);
  return 0;
}

int decrypt(long *m, const long M, const struct RSACfg *r) {
  long d = r->d;
  const long *p = &r->p, *q = &r->q, *e = &r->e;
  if (!valid_rsa(r)){
#ifdef DEBUG
      fprintf(stderr, "error: RSACfg is not valid (%s:%d)\n",__FILE__, __LINE__);
#endif
    return 1;
   }
  if (!d) {
    if (mod_inv(&d, *e, (*p - 1) * (*q - 1))) {
#ifdef DEBUG
      fprintf(stderr, "error: 'd' is not invertible '*m' not assigned\n");
#endif
      return 2;
    }
  }
  *m = ((long)pow(M, d)) % (*p * (*q));

  return 0;
}

And here's the driver code


int do_encrypt(long *, const long, const struct RSACfg *);

int do_decrypt(long *, const long, struct RSACfg *);

int main(int argc, char **argv) {
  struct RSACfg cfg = {.e = 7, .p = 3, .q = 11, .d = 0};
  long M, m, e, d, p, q;
  int opt;
  opterr = e = p = q = d = 0;

  while ((opt = getopt(argc, argv, ":hDEe:p:q:m:M:")) != -1) {
    switch (opt) {
    case 'h':
      printf("Usage: %s [-h] [-DE] [-d] [-e x -p y -q z] arg\n", argv[0]);
      return 0;
    case 'D':
      task = Decrypt;
      break;
    case 'E':
      task = Encrypt;
      break;
    case 'e':
      e = atoi(optarg);
      break;
    case 'd':
      d = atoi(optarg);
      break;
    case 'p':
      p = atoi(optarg);
      break;
    case 'q':
      q = atoi(optarg);
      break;
    case ':':
      fprintf(stderr, "'-%c' option missing a value\n", optopt);
      return -1;
    case '?':
      fprintf(stderr, "Unrecognized option '-%c'\n", optopt);
      return 1;
    }
  }

  if (!task) {
    fprintf(stderr, "Specify a task with -E or -D option\n");
    return 3;
  }

  if ((d && task == Decrypt) || (e || p || q)) {
    if (!(p && q && e)) {
      fprintf(stderr, "error: Make sure you set a complete update on p, q and "
                      "e when you set any of the options (e,p,q)\n");
      return 4;
    }
    cfg.e = e;
    cfg.p = p;
    cfg.q = q;
  }
  if (optind < argc) {
    if (task == Decrypt) {
      M = atoi(argv[optind]);
      if (d)
        cfg.d = d;
      do_decrypt(&m, M, &cfg);
      printf("Decryption\nM: %ld\nResult: %ld\n", M, m);
    } else if (task == Encrypt) {
      m = atoi(argv[optind]);
      do_encrypt(&M, m, &cfg);
      printf("Encryption\nm: %ld\nResult: %ld\n", m, M);
    }
  } else {
    fprintf(stderr, "error: An argument was expected\n");
    return 5;
  }
}

int do_encrypt(long *M, const long m, const struct RSACfg *c) {
  printf("Encrypting with pub key(%ld,%ld)\n", c->p * c->q, c->e);
  return encrypt(M, m, c);
}

int do_decrypt(long *m, const long M, struct RSACfg *c) {
  if (!c->d)
    make_d(&c->d, c);

  printf("Decrypting with priv key(%ld, %ld, %ld)\n", c->d, c->p, c->q);
  return decrypt(m, M, c);
}

I have been pondering on this for a week but each time I look up for research and improve the implementation, it always fails.

I recently looked at www.cs.drexel.edu and made an adjustment to struct RSACfg so that long d can be coded at runtime.

Update (Test Sample)

Compilation:

~$ gcc mod.c -lm

Running: Case encrypting plain 28

~$ ./a.out -E 28

is the same as (default)

~$ ./a.out -e7 -p3 -q11 -E 28

and produces the output:

Encrypting with pub key(33,7)
Encryption
m: 28
Result(M): 19

When we replace -E (encryption) with -D (decryption) and the argument 28 with 19, we get

info: solving '7 x d = 1 (mod 20)' using `make_d(long*, const struct RSACfg*)`
Decrypting with priv key (3, 3, 11)
Decryption
M: 19
Result(m): 28

Note that setting -d X requires that -p, -q and -e are passed on (which are should all be set when any of them is set)

Case in point (other values for -p, -q, -e) producing inconsistency

~$ ./a.out -e 13 -p17 -q100003 -E 4
Encrypting with pub key(1700051,13)
Encryption
m: 4
Result(M): 806875

~$ ./a.out -e 13 -p17 -q100003 -D 806875
Decrypting with priv key (615397, 17, 100003)
Decryption
M: 806875
Result(m): -1156009  # This should be 4 and not -1156009


Solution

  • Overflow

    ((long)pow(m, r->e)) % (r->p * r->q) risks integer overflow with r->p * r->q, (long)pow(m, r->e) risks precision lost and converting a double that is out-of-range for a long.

    (long)pow(M, d)) % (*p * (*q) is likewise at risk.

    Various places where code uses * also risk overflow.

    To solve:

    For *, use a 2x as wide math. e.g. r->p * r->q --> 1LL * r->p * r->q.
    If long is 32-bits, long long, which is at least 64-bit, will suffice.

    ab mod c is trickier and may need advanced code like this.