基于BLS的盲签名与基于RSA的盲签名的实现与比较
1. 利用PBC库实现基于BLS的盲签名
BLS(Boneh-Lynn-Shacham) 签名算法是一种可以实现签名聚合和密钥聚合的算法(即可以将多个密钥聚合成一把密钥,将多个签名聚合成一个签名)。可以通过这篇(理解BLS签名算法)CSDN来理解BLS签名。
PBC库(The Pairing-Based Cryptography Library)可以实现基于双线性对的密码编程,关于PBC库的细节可以参考我的PBC Library专栏。
盲签名算法如下图所示,其中
H
ˉ
(
)
\bar H()
Hˉ()表示BLS哈希,
r
i
r_i
ri是从Zp群随机选择的元素。
在单个程序(不区分客户端和服务器端,不进行消息传递)中实现盲签名代码:
代码中多了一步操作,将文件块按照1KB大小进行了分块。
#include <iostream>
#include <fstream>
// for pbc library
#include "/usr/local/include/pbc/pbc.h"
// for encrypt
#include <openssl/md5.h>
#include <openssl/sha.h>
// for gmp library
#include <gmp.h>
using namespace std;
// 全局参数
char source[] = "sourceFiles/";
#define PARAM "a.param"
pairing_t pairing;
element_t public_key; // 密钥服务器的公钥
element_t secret_key; // 密钥服务器的私玥
element_t g; // 系统参数g,生成元
int initPairing(){
// 读param参数文件
string s = PARAM;
ifstream in(s.c_str());
istreambuf_iterator<char> beg(in), end;
string strdata(beg, end);
in.close();
// cout <<strdata<<endl;
if(pairing_init_set_str(pairing, strdata.c_str())){ // 初始化pairing
cout<<"failed"<<endl;
}
element_init_G2(g, pairing);
element_init_G2(public_key, pairing);
element_init_Zr(secret_key, pairing);
// 生成系统参数g
element_random(g);
// element_printf("system parameter g = %B\n", g);
// 生成私玥
element_random(secret_key);
// element_printf("private key = %B\n", secret_key);
// compute corresponding public key
element_pow_zn(public_key, g, secret_key);
// element_printf("public key = %B\n", public_key);
return 0;
}
/*
* 将文件块的hash值映射到群上
*/
int BLSHash(element_t h, char *behindHash, int hashLen){
element_init_G1(h, pairing);
element_from_hash(h, behindHash, hashLen);
return 0;
}
int sigAndCheck256(char *databuf, long size){
int hash_length = SHA256_DIGEST_LENGTH;
unsigned char digest[hash_length];
SHA256((unsigned char*)databuf, size, (unsigned char*)&digest);
element_t h;
BLSHash(h, (char*)digest, hash_length);
element_t r;
element_init_Zr(r, pairing);
element_random(r);
// h*g^r盲化
element_t h_blind;
element_init_G1(h_blind, pairing);
element_t blindFactor;
element_init_G2(blindFactor, pairing);
element_pow_zn(blindFactor, g, r);
element_mul(h_blind, h, blindFactor);
element_t h_blind_sig;
element_init_G1(h_blind_sig, pairing);
element_pow_zn(h_blind_sig, h_blind, secret_key);
// 去盲化
element_t h_sig; // 去盲化后
element_init_G1(h_sig, pairing);
element_t neg_r;
element_t KSpk_negr;
element_init_Zr(neg_r, pairing);
element_init_G2(KSpk_negr, pairing);
element_neg(neg_r, r);
element_pow_zn(KSpk_negr, public_key, neg_r); // y^-r
element_mul(h_sig, h_blind_sig, KSpk_negr); // 去盲化过程
// 验证
element_t temp1, temp2;
element_init_GT(temp1, pairing);
element_init_GT(temp2, pairing);
element_pairing(temp1, h_sig, g);
element_pairing(temp2, h, public_key);
if(!element_cmp(temp1, temp2)){
printf("success\n");
return 1;
}else{
printf("failed\n");
return 0;
}
}
int readFile(string filename, long size){
string fullPath = source +filename;
fstream in;
in.open(fullPath.c_str(), ios::in|ios::binary);
if(!in){
cout<<"error!"<<endl;
}
int temp = in.tellg();
in.seekg(0, ios_base::end);
long filelen = in.tellg();
in.seekg(temp);
int n;
int lastLen;
if((filelen % size) == 0){ // 整数倍时
n = filelen / size;
lastLen = size;
}else{
n = filelen / size;
lastLen = filelen % size + size;
}
char *databuf = new char[size];
for(int i=0;i<n-1;i++){ // 前n-1个块
in.read(databuf,size*sizeof(char));
sigAndCheck256(databuf, size);
}
char *databufLast = new char[lastLen];
in.read(databufLast,lastLen*sizeof(char));
sigAndCheck256(databufLast, lastLen);
return 0;
}
int main() {
initPairing();
readFile("4k.txt", 1024);
return 0;
}
2. 利用Openssl库实现基于RSA的盲签名
基于RSA的盲签名思想与上面的思想类似,假设Alice想让Bob给她的文件m进行盲签名。
(1)Bob作为服务器有自己的公私钥,公钥为
(
n
,
e
)
(n, e)
(n,e),私钥为
(
n
,
d
)
(n, d)
(n,d)。
(2)Alice首先使用Hash函数对自己的消息m进行哈希得
h
(
m
)
h(m)
h(m)。
(3)盲化处理。Alice选择一个随机数
k
k
k,生成盲化后的消息
m
′
=
h
(
m
)
k
e
m
o
d
n
m' = h(m) k^e mod\ n
m′=h(m)kemod n,然后将
m
′
m'
m′发送给Bob。
(4)Bob收到后直接在收到的消息上进行签名,
S
′
=
S
i
g
n
(
m
′
)
S' = Sign(m')
S′=Sign(m′),然后将签名
S
′
S'
S′返回给Alice。
(5)去盲化。将(3)中的式子带入(4)中得,
S
′
=
h
d
(
m
)
k
m
o
d
n
S'=h^d(m)\ k \ mod\ n
S′=hd(m) k mod n,只需要再把k去掉即可,即
S
=
S
′
k
−
1
S=S' \ k^{-1}
S=S′ k−1,其中
k
−
1
k^{-1}
k−1表示k在模n意义下的逆。
(6)签名验证。验证
S
e
S^e
Se是否与
h
(
m
)
h(m)
h(m)相等。
#include <iostream>
#include <string.h>
#include <fstream>
#include <openssl/bn.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <openssl/sha.h>
using namespace std;
// 全局变量
char source[] = "sourceFiles/";
RSA *r;
BIGNUM *bne;
unsigned long e = RSA_F4; // RSA公钥指数
int bits = 2048; // RSA密钥长度
int KeyGen(){
int ret;
bne = BN_new();
ret = BN_set_word(bne, e);
r = RSA_new();
ret = RSA_generate_key_ex(r, bits, bne, NULL); // 生成RSA密钥
if (ret != 1) {
printf("RSA_generate_key_ex err!\n");
return -1;
} else {
// printf("生成了RSA密钥。\n");
}
return 0;
}
int blindSig(char *databuf, long size){
// 对消息message进行sha256 hash,得到hm
unsigned char hm[SHA256_DIGEST_LENGTH + 1];
SHA256((unsigned char*)databuf, size, (unsigned char*) hm);
// for (int i = 0; i < 32; i++) {
// printf("%02X", hm[i]);
// }
// printf("\n");
BIGNUM *nn; // 模数
nn = r->n;
// 生成随机数k
BIGNUM *k; // 随机数k
k = BN_new();
BN_rand_range(k, nn); // 选取小于模数的随机数k
BN_CTX*ctx;
ctx = BN_CTX_new();
// 盲化因子blindF = k ^ bne (mod nn)
// 相当于使用公钥加密k
BIGNUM *blind;
blind = BN_new();
BN_mod_exp(blind, k, bne, nn, ctx);
// 将hm转为BN bn_hm
BIGNUM *bn_hm;
bn_hm = BN_new();
BN_bin2bn(hm, SHA256_DIGEST_LENGTH, bn_hm);
// BN_print_fp(stdout, bn_hm);
// printf("\n");
// 对bn_hm进行盲化 m_blind = bn_hm * blind (mod nn)
BIGNUM *m_blind;
m_blind = BN_new();
BN_mod_mul(m_blind, bn_hm, blind, nn, ctx);
// 服务器使用私钥对m_blind进行签名为signed_blind_bn
BIGNUM *signed_blind_bn;
signed_blind_bn = BN_new();
BN_mod_exp(signed_blind_bn, m_blind, r->d, nn, ctx);
// 盲签名后结果发回客户端
// 求得k模nn下的逆
BIGNUM *k_inverse;
k_inverse = BN_new();
BN_mod_inverse(k_inverse, k, nn, ctx);
// 去盲化得signed_bn
BIGNUM *signed_bn;
signed_bn = BN_new();
BN_mod_mul(signed_bn, signed_blind_bn, k_inverse, nn, ctx);
// 公钥验证签名
BIGNUM *hm2_bn;
hm2_bn = BN_new();
BN_mod_exp(hm2_bn, signed_bn, bne, nn, ctx);
// BN_print_fp(stdout, hm2_bn);
// printf("\n");
if(!BN_cmp(bn_hm, hm2_bn)){
printf("success!\n");
}else{
printf("Failed!\n");
}
return 0;
}
int readFile(string filename, long size){
string fullPath = source +filename;
fstream in;
in.open(fullPath.c_str(), ios::in|ios::binary);
if(!in){
cout<<"error!"<<endl;
}
int temp = in.tellg();
in.seekg(0, ios_base::end);
long filelen = in.tellg();
in.seekg(temp);
int n;
int lastLen;
if((filelen % size) == 0){ // 整数倍时
n = filelen / size;
lastLen = size;
}else{
n = filelen / size;
lastLen = filelen % size + size;
}
char *databuf = new char[size];
for(int i=0;i<n-1;i++){ // 前n-1个块
in.read(databuf,size*sizeof(char));
blindSig(databuf, size);
}
char *databufLast = new char[lastLen];
in.read(databufLast,lastLen*sizeof(char));
blindSig(databufLast, lastLen);
return 0;
}
int main() {
KeyGen();
readFile("4k.txt", 1024);
return 0;
}
3.比较
首先比较时间,虽然说基于椭圆曲线的公钥密码体制的速度要比RSA安全的多且同安全程度下速度更快,160位的ECC就可以达到1024位RSA的安全,就像NITS给出的比较结果。
RSA key size (bits) | ECC key size (bits) |
---|---|
1024 | 160 |
2048 | 224 |
3072 | 256 |
7680 | 384 |
但是双线性对的运算我也不是很懂,不多作说明,只能进行简单的时间测试。
分别测试了基于1024位、2048位RSA和BLS的盲签名算法,随着文件大小的增大时间花费(文件大小主要影响的是签名次数)。
从图中可以看出,基于BLS的盲签名算法效率远不如RSA(据PBC Library给出的安全性,A曲线大体与1024位RSA差不多)(注意需要时重新查证)。但是BLS签名的优点远不是在这里,其可以进行签名聚合、密钥聚合等,多用户时会节省很多时间和空间。
注:
数据都是自己通过实验自己测出来的,具体理论目前只是稍微了解,参考时请注意求证,如有大神发现问题请及时提出,不想误导他人。