目录
例子
生成一个私钥只需要3步
1. 获得指定曲线的group (如比特币的secp256k1)
2. group和key绑定
3. 用key来生成私钥
先上一段代码例子
key1=EC_KEY_new();
if(key1==NULL)
{
printf("EC_KEY_new err!\n");
return -1;
}
key2=EC_KEY_new();
if(key2==NULL)
{
printf("EC_KEY_new err!\n");
return -1;
}
group1 = EC_GROUP_new_by_curve_name(NID_secp256k1);
if(group1==NULL) {
printf("EC_GROUP_new_by_curve_name err!\n");
return -1;
}
group2=EC_GROUP_new_by_curve_name(NID_secp256k1);
if(group2==NULL)
{
printf("EC_GROUP_new_by_curve_name err!\n");
return -1;
}
ret=EC_KEY_set_group(key1, group1);
if(ret!=1)
{
printf("EC_KEY_set_group err.\n");
return -1;
}
ret=EC_KEY_set_group(key2,group2);
if(ret!=1)
{
printf("EC_KEY_set_group err.\n");
return -1;
}
ret=EC_KEY_generate_key(key1);
if(ret!=1)
{
printf("EC_KEY_generate_key err.\n");
return -1;
}
ret=EC_KEY_generate_key(key2);
if(ret!=1)
{
printf("EC_KEY_generate_key err.\n");
return -1;
}
可以看出来,相同方法获得的key,相同的方法获得的group,相同的方法生成私钥,但是最后生成的私钥是不同的。
那是什么造成的不一样呢,是哪一步的输入造成了不同呢。openssl是不是有取当前时间作为种子输入呢? 还是有别的随机数? 还是有读取系统某些信息?
排查
经过仔细排查
1. 获得的key只有一个读写锁的地址不同,其他相同。排除
2. group里面参数众多,参与到计算的比如order, a,b 都相同,这些参数只和具体算法有关、
3. 问题肯定还是在生成函数中
Group里的data主要参数
static const struct {
EC_CURVE_DATA h;
unsigned char data[0 + 32 * 6];
} _EC_SECG_PRIME_256K1 = {
{
NID_X9_62_prime_field, 0, 32, 1
},
{
/* no seed */
/* p */
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFC, 0x2F,
/* a */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* b */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
/* x */
0x79, 0xBE, 0x66, 0x7E, 0xF9, 0xDC, 0xBB, 0xAC, 0x55, 0xA0, 0x62, 0x95,
0xCE, 0x87, 0x0B, 0x07, 0x02, 0x9B, 0xFC, 0xDB, 0x2D, 0xCE, 0x28, 0xD9,
0x59, 0xF2, 0x81, 0x5B, 0x16, 0xF8, 0x17, 0x98,
/* y */
0x48, 0x3a, 0xda, 0x77, 0x26, 0xa3, 0xc4, 0x65, 0x5d, 0xa4, 0xfb, 0xfc,
0x0e, 0x11, 0x08, 0xa8, 0xfd, 0x17, 0xb4, 0x48, 0xa6, 0x85, 0x54, 0x19,
0x9c, 0x47, 0xd0, 0x8f, 0xfb, 0x10, 0xd4, 0xb8,
/* order */
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFE, 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B,
0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x41
}
};
生成私钥的函数实际为
ec_key_simple_generate_key
int ec_key_simple_generate_key(EC_KEY *eckey)
{
int ok = 0;
BN_CTX *ctx = NULL;
BIGNUM *priv_key = NULL;
const BIGNUM *order = NULL;
EC_POINT *pub_key = NULL;
if ((ctx = BN_CTX_new()) == NULL)
goto err;
if (eckey->priv_key == NULL) {
priv_key = BN_new(); //会执行到这里,一个值为0的大数
if (priv_key == NULL)
goto err;
} else
priv_key = eckey->priv_key;
order = EC_GROUP_get0_order(eckey->group); // 获取group的order,固定值大数
if (order == NULL)
goto err;
do
if (!BN_priv_rand_range(priv_key, order)) // 关键就在这里了,生成随机数,值小于order.
goto err;
while (BN_is_zero(priv_key)) ;
if (eckey->pub_key == NULL) {
pub_key = EC_POINT_new(eckey->group);
if (pub_key == NULL)
goto err;
} else
pub_key = eckey->pub_key;
if (!EC_POINT_mul(eckey->group, pub_key, priv_key, NULL, NULL, ctx))
goto err;
eckey->priv_key = priv_key;
eckey->pub_key = pub_key;
ok = 1;
err:
if (eckey->pub_key == NULL)
EC_POINT_free(pub_key);
if (eckey->priv_key != priv_key)
BN_free(priv_key);
BN_CTX_free(ctx);
return ok;
}
到了随机数这里了,那这个随机数的算法就一定每次不冲突吗?
随机算法
这里的随机算法是NIST SP 800-90A DRBG标准算法,计算依赖一个DRBG对象
DRBG里得有一个seed
seed由三部分合成:
Entropy, Nonce, Personalization string.
从代码上来看,Entropy的生成暂时还是看不明白的 =。=
size_t rand_drbg_get_entropy(RAND_DRBG *drbg,
unsigned char **pout,
int entropy, size_t min_len, size_t max_len,
int prediction_resistance)
Nonce的生成由drbg对象本身和一个计数器合成
size_t rand_drbg_get_nonce(RAND_DRBG *drbg,
unsigned char **pout,
int entropy, size_t min_len, size_t max_len)
Personalization string 默认是一个固定字符串,用户也可以自己去设定
/* NIST SP 800-90A DRBG recommends the use of a personalization string. */
static const char ossl_pers_string[] = "OpenSSL NIST SP 800-90A DRBG";
在初始化drbg对象的时候,以上三个输入都已经设定好了,seed就确定了。
那么在确定seed的情况下,后续生成的随机数应该是一个固定的序列(按照通常的random()来理解)
DRBG算法还有一个额外参数addtional data,在每次执行Generate的时候都不一样。
这个addtional data由 进程号,线程号和当前时间构成
int rand_pool_add_additional_data(RAND_POOL *pool)
{
struct {
int fork_id;
CRYPTO_THREAD_ID tid;
uint64_t time;
} data = { 0 };
/*
* Add some noise from the thread id and a high resolution timer.
* The fork_id adds some extra fork-safety.
* The thread id adds a little randomness if the drbg is accessed
* concurrently (which is the case for the <master> drbg).
*/
data.fork_id = openssl_get_fork_id();
data.tid = CRYPTO_THREAD_get_current_id();
data.time = get_timer_bits();
return rand_pool_add(pool, (unsigned char *)&data, sizeof(data), 0);
}
小结
综上,Entropy+Nonce +perstring 构成seed ;
每次生成 再加上 进程号+线程号+当前时间作为参数。
所以不需要再去额外添加时间去确保随机数的唯一性;
如果有需要的话,可以去修改personalization string。
随机数到此为止,不再花时间深入研究了,水太深。
下一步研究一下椭圆曲线的加密解密的使用方法。