考虑以下RSA_generate_key()
的调用:
RSA * rsa = RSA_generate_key(8192, RSA_F4, NULL, NULL);
生成8,192位RSA密钥可能需要很长时间(从几秒到几分钟)。假设包含上述代码行的应用程序为用户提供了一个取消密钥生成的按钮。
如何在生成密钥之前中止计算并使函数返回?我记得RSA_generate_key()
的第三个参数是一个用于显示进度的回调函数 - 有没有什么方法可以回调一个值,意味着“中止操作并返回”?
在另一个线程中运行该函数然后终止该线程不是一个选项。
答案 0 :(得分: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的强制要求。它的缺点是,如果您需要动态分配它们的函数,它可能会泄漏资源。然而,在实践中,如果不经常进行或仅在明确的用户请求下进行泄漏,则泄漏可能足够小以至于不可察觉。为了肯定这种情况,请在紧密循环中运行代码并观察流程的资源消耗。
以下是上述功能的测试程序:
#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
答案 1 :(得分:2)
进度回调不能用于取消功能,它纯粹是为了向用户显示进度。 (Read more here)
生成一个线程来调用RSA_generate_key()
,以便它在后台执行。当用户按下取消按钮时,终止线程。
答案 2 :(得分:2)
我认为问题在于,回调通常不会让您能够在中发送信息,只能接收返回信息。正如其他一些海报所提到的,您可以考虑更改实际密钥生成例程的源代码,并编译您自己的版本。在这种情况下,您可以将某个值通过引用传递回您的调用例程,然后您可以将其置于外部,从而触发失败条件。
注意:RSA_generate_key
实际上已弃用,现在只是RSA_generate_key_ex
的包装。
根据rsa_gen.c
文件的版本1.19.4.2,除非您在FIPS_mode
,否则密钥将由静态方法rsa_builtin_keygen
生成。在FIPS_mode
中,它会在输入rsa->meth->rsa_keygen
时由RSA_generate_key
生成。
也就是说,有很多地方将回调(cb
参数)传递给其他方法,可能是因为这些方法可以反过来调用它来更新状态。
诀窍是更新BN_GENCB
以包含某种“取消”标志,或者改变在方法中实际调用回调的方式,这样当回调方法触发时,你可以设置一个标志,调用函数将通过中断生成子例程来表示。
你是如何去做的仍然是你需要弄清楚并完成的事情 - 但希望这会给你一点开始。
答案 3 :(得分:1)
虽然我从未面对这种情况,也从未做过任何事情。如果您可以修改OpenSSL的源代码,可以尝试以下命令:
我们假设变量V在两个函数之间共享。因此,它是文件中的全局变量或静态变量,并由RSA_generate_key使用,并说另一个函数RSA_set_V。
RSA_generate_key只会读取变量。 RSA_set_V设置变量。
在RSA_generate_key中,耗时的循环可能会使用检查此变量的附加条件。它可以终止此循环或在某个V值上退出该函数。 e.g。
while (V!=CERTAIN_VALUE && Rest_of_Condition) {
//Loop body.
}
现在,在RSA_set_V中,将V设置为CERTAIN_VALUE。 当用户单击“停止”时,请调用RSA_set_V。它应该停止RSA_generate_key。
然而,它几乎没有问题。它可能会减慢RSA_generate_key。
答案 4 :(得分:0)
根据source code,如果不修改openssl的代码,我们就不能中断RSA_generate_key方法让它返回。
我认为有两种方法可以做到这一点:
让方法在后台线程中运行,当用户按下取消按钮时,让方法在后台运行。然后让线程死掉。并忽略结果。 如果用户请求另一代,则启动一个新线程。
另一个是启动子流程来生成密钥。 RSA结构包含许多BIGNUM成员,它也是一些int / ulongs的结构, 在子流程生成密钥后,将这些相关成员复制回父流程。 如果用户按下取消按钮,则杀死子过程是安全的。 但是不确定复制这些整数/长期是否足以让你的服务。
答案 5 :(得分:0)
如果您不希望终止该线程,您可以改为分叉一个新进程并在用户中止时终止该进程?