8

考虑以下对 的调用RSA_generate_key()

RSA * rsa = RSA_generate_key(8192, RSA_F4, NULL, NULL);

生成 8,192 位 RSA 密钥可能需要很长时间(从几秒到几分钟不等)。假设包含上述代码行的应用程序为用户提供了一个取消密钥生成的按钮。

如何在生成密钥之前中止计算并使函数返回?我记得第三个参数RSA_generate_key()是用于显示进度的回调函数 - 有什么方法可以让回调返回一个意味着“中止操作并返回”的值?

在另一个线程中运行该函数然后终止该线程不是一种选择。

4

6 回答 6

7

由于RSA_generate_key提供了进度回调,您可以longjmp退出它来终止函数。通过一些额外的代码,您可以创建一个RSA_generate_key接受通用测试函数的包装器,该函数可用于检查超时或窗口系统设置的标志。

#include <openssl/rsa.h>
#include <stdbool.h>
#include <setjmp.h>

struct trampoline_ctx {
  bool (*testfn)(void *);
  void *testfn_arg;
  jmp_buf env;
};

static void trampoline(int ignore1, int ignore2, void *arg)
{
  struct trampoline_ctx *ctx = arg;
  if (!ctx->testfn(ctx->testfn_arg))
    longjmp(ctx->env, 1);
}

// like RSA_generate_key, but accepts a test function. If testfn returns
// false, key generation is terminated and NULL is returned.    
RSA *
my_generate_key(int num, unsigned long e,
                bool (*testfn)(void *), void *testfn_arg)
{
  struct trampoline_ctx ctx;
  ctx.testfn = testfn;
  ctx.testfn_arg = testfn_arg;
  if (setjmp(ctx.env))
    return NULL;
  return RSA_generate_key(num, e, trampoline, &ctx);
}

longjmp正如C89 和 C99 所要求的那样,这种方法具有惊人的可移植性。它的缺点是,如果您要使用 longjmping 的函数动态分配资源,它可能会泄漏资源。然而,在实践中,如果不经常进行或仅在明确的用户请求下进行,泄漏可能足够小以至于不引人注意。肯定是这种情况,在紧密循环中运行代码并观察进程的资源消耗。

这是上述功能的测试程序:

#include <stdio.h>
#include <sys/time.h>

double now()
{
  struct timeval tv;
  gettimeofday(&tv, NULL);
  return tv.tv_sec + (double) tv.tv_usec / 1e6;
}

struct tt_ctx {
  double start;
  double limit;
};

bool test_time_limit(void *arg)
{
  struct tt_ctx *ctx = arg;
  return now() - ctx->start <= ctx->limit;
}

int main(int argc, char **argv)
{
  int limit = atoi(argv[1]);
  struct tt_ctx ctx;
  ctx.start = now();
  ctx.limit = limit / 1000.0;

  RSA *key = my_generate_key(4096, 65537, test_time_limit, &ctx);
  printf("%p\n", key);
  return 0;
}

测试程序假定为 POSIX gettimeofday,但可以轻松转换为系统提供的另一个高分辨率时钟。测试程序如下:

将两段代码附加到文件中并使用-lrsa. 测试程序将在命令行上以毫秒为单位指定的时间限制内生成一个 4096 位的 RSA 密钥。在所有情况下,它都会打印结果RSA *指针以指示my_generate_key其请求是否已完成或已中止。伴随执行的输出time作为健全性检查以验证是否遵守时间限制:

# try with a 10ms limit
$ time ./a.out 10
(nil)                         # too short
./a.out 10  0.02s user 0.00s system 85% cpu 0.023 total

# see if 100ms is enough time
$ time ./a.out 100
(nil)                         # still too short
./a.out 100  0.10s user 0.00s system 97% cpu 0.106 total

# try with 1 whole second:
$ time ./a.out 1000
0x2369010                     # success!
./a.out 1000  0.64s user 0.00s system 99% cpu 0.649 total
于 2013-04-04T21:59:58.383 回答
2

我相信问题在于回调通常不会使您能够发送信息而只能接收信息。正如其他一些海报所提到的,您可能会考虑更改实际密钥生成例程的源代码,并编译您自己的版本。在这种情况下,您可以通过引用将某种值传递回调用例程,然后您可以将其设置在外部,从而触发失败条件。

注意:RSA_generate_key实际上已被弃用,现在只是RSA_generate_key_ex.

根据该文件的1.19.4.2rsa_gen.c版本,除非您在 中,否则FIPS_mode密钥将由静态方法生成rsa_builtin_keygen。在FIPS_mode中,它将由rsa->meth->rsa_keygen进入 的任何内容生成RSA_generate_key

也就是说,有很多地方将回调(cb参数)传递给其他方法,大概是因为这些方法可以反过来调用它来更新状态。

诀窍是更新BN_GENCB以包含某种“取消”标志,或者改变在方法中实际调用回调的方式,以便当回调方法触发时,您可以设置一个标志,调用函数将通过中断生成子例程来兑现。

你将如何去做仍然是你需要弄清楚和解决的问题——但希望这会给你一个开始。

于 2013-04-04T19:34:57.750 回答
2

进度回调不能用于取消函数,它纯粹是为了向用户显示进度。(在这里阅读更多

生成一个要调用的线程,RSA_generate_key()以便它在后台执行。当用户按下取消按钮时,终止线程。

于 2013-03-03T06:39:03.350 回答
1

虽然我从来没有遇到过这种情况,也从来没有做过任何事情。如果可以修改 OpenSSL 的源代码,可以尝试以下方法:

假设变量 V 在两个函数之间共享。因此,它要么是文件中的全局变量,要么是静态变量,并被 RSA_generate_key 使用,并说另一个函数 RSA_set_V。

RSA_generate_key 只会读取变量。RSA_set_V 设置变量。

在 RSA_generate_key 中,耗时循环可能会使用检查此变量的附加条件。它可以终止这个循环或在某个 V 值上退出函数。例如

  while (V!=CERTAIN_VALUE && Rest_of_Condition) {
      //Loop body.
  }

现在,在 RSA_set_V 中,将 V 设置为 CERTAIN_VALUE。当用户单击停止时,调用 RSA_set_V。它应该停止 RSA_generate_key。

但是,它几乎没有问题。它可能会减慢 RSA_generate_key。

于 2013-03-30T03:53:30.433 回答
0

如果您不希望终止线程,您可以派生一个新进程并在用户中止时终止它?

于 2013-04-04T18:24:20.810 回答
0

根据源码,不修改openssl的代码,我们不能中断RSA_generate_key方法让它返回。

我想了两种方法来做到这一点:

  1. 让方法在后台线程中运行,当用户按下取消按钮时,让方法运行到后台结束。然后让线程死掉。并忽略结果。如果用户请求另一代,则启动一个新线程。

  2. 另一种是启动子流程来生成密钥。RSA结构包含很多BIGNUM成员,也是一些ints/ulongs的结构,子进程生成密钥后,将这些相关成员复制回父进程。如果用户按下取消按钮,则终止子进程是安全的。但不确定复制这些整数/长整数是否足以满足您的需求。

于 2013-04-02T15:09:32.770 回答