eccblind.py
1 """ 2 ECC blind signature functionality based on 3 "An Efficient Blind Signature Scheme 4 Based on the Elliptic CurveDiscrete Logarithm Problem" by Morteza Nikooghadama 5 <mnikooghadam@sbu.ac.ir> and Ali Zakerolhosseini <a-zaker@sbu.ac.ir>, 6 http://www.isecure-journal.com/article_39171_47f9ec605dd3918c2793565ec21fcd7a.pdf 7 """ 8 9 # variable names are based on the math in the paper, so they don't conform 10 # to PEP8 11 12 import time 13 from hashlib import sha256 14 from struct import pack, unpack 15 16 from .openssl import OpenSSL 17 18 # first byte in serialisation can contain data 19 Y_BIT = 0x01 20 COMPRESSED_BIT = 0x02 21 22 # formats 23 BIGNUM = '!32s' 24 EC = '!B32s' 25 PUBKEY = '!BB33s' 26 27 28 class Expiration(object): 29 """Expiration of pubkey""" 30 @staticmethod 31 def deserialize(val): 32 """Create an object out of int""" 33 year = ((val & 0xF0) >> 4) + 2020 34 month = val & 0x0F 35 assert month < 12 36 return Expiration(year, month) 37 38 def __init__(self, year, month): 39 assert isinstance(year, int) 40 assert year > 2019 and year < 2036 41 assert isinstance(month, int) 42 assert month < 12 43 self.year = year 44 self.month = month 45 self.exp = year + month / 12.0 46 47 def serialize(self): 48 """Make int out of object""" 49 return ((self.year - 2020) << 4) + self.month 50 51 def verify(self): 52 """Check if the pubkey has expired""" 53 now = time.gmtime() 54 return self.exp >= now.tm_year + (now.tm_mon - 1) / 12.0 55 56 57 class Value(object): 58 """Value of a pubkey""" 59 @staticmethod 60 def deserialize(val): 61 """Make object out of int""" 62 return Value(val) 63 64 def __init__(self, value=0xFF): 65 assert isinstance(value, int) 66 self.value = value 67 68 def serialize(self): 69 """Make int out of object""" 70 return self.value & 0xFF 71 72 def verify(self, value): 73 """Verify against supplied value""" 74 return value <= self.value 75 76 77 class ECCBlind(object): # pylint: disable=too-many-instance-attributes 78 """ 79 Class for ECC blind signature functionality 80 """ 81 82 # init 83 k = None 84 R = None 85 F = None 86 d = None 87 Q = None 88 a = None 89 b = None 90 c = None 91 binv = None 92 r = None 93 m = None 94 m_ = None 95 s_ = None 96 signature = None 97 exp = None 98 val = None 99 100 def ec_get_random(self): 101 """ 102 Random integer within the EC order 103 """ 104 randomnum = OpenSSL.BN_new() 105 OpenSSL.BN_rand(randomnum, OpenSSL.BN_num_bits(self.n), 0, 0) 106 return randomnum 107 108 def ec_invert(self, a): 109 """ 110 ECC inversion 111 """ 112 inverse = OpenSSL.BN_mod_inverse(None, a, self.n, self.ctx) 113 return inverse 114 115 def ec_gen_keypair(self): 116 """ 117 Generate an ECC keypair 118 We're using compressed keys 119 """ 120 d = self.ec_get_random() 121 Q = OpenSSL.EC_POINT_new(self.group) 122 OpenSSL.EC_POINT_mul(self.group, Q, d, None, None, None) 123 return (d, Q) 124 125 def ec_Ftor(self, F): 126 """ 127 x0 coordinate of F 128 """ 129 # F = (x0, y0) 130 x0 = OpenSSL.BN_new() 131 y0 = OpenSSL.BN_new() 132 OpenSSL.EC_POINT_get_affine_coordinates(self.group, F, x0, y0, self.ctx) 133 OpenSSL.BN_free(y0) 134 return x0 135 136 def _ec_point_serialize(self, point): 137 """Make an EC point into a string""" 138 try: 139 x = OpenSSL.BN_new() 140 y = OpenSSL.BN_new() 141 OpenSSL.EC_POINT_get_affine_coordinates( 142 self.group, point, x, y, None) 143 y_byte = (OpenSSL.BN_is_odd(y) & Y_BIT) | COMPRESSED_BIT 144 l_ = OpenSSL.BN_num_bytes(self.n) 145 try: 146 bx = OpenSSL.malloc(0, l_) 147 OpenSSL.BN_bn2binpad(x, bx, l_) 148 out = bx.raw 149 except AttributeError: 150 # padding manually 151 bx = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(x)) 152 OpenSSL.BN_bn2bin(x, bx) 153 out = bx.raw.rjust(l_, b'\x00') 154 return pack(EC, y_byte, out) 155 156 finally: 157 OpenSSL.BN_clear_free(x) 158 OpenSSL.BN_clear_free(y) 159 160 def _ec_point_deserialize(self, data): 161 """Make a string into an EC point""" 162 y_bit, x_raw = unpack(EC, data) 163 x = OpenSSL.BN_bin2bn(x_raw, OpenSSL.BN_num_bytes(self.n), None) 164 y_bit &= Y_BIT 165 retval = OpenSSL.EC_POINT_new(self.group) 166 OpenSSL.EC_POINT_set_compressed_coordinates(self.group, 167 retval, 168 x, 169 y_bit, 170 self.ctx) 171 return retval 172 173 def _bn_serialize(self, bn): 174 """Make a string out of BigNum""" 175 l_ = OpenSSL.BN_num_bytes(self.n) 176 try: 177 o = OpenSSL.malloc(0, l_) 178 OpenSSL.BN_bn2binpad(bn, o, l_) 179 return o.raw 180 except AttributeError: 181 o = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(bn)) 182 OpenSSL.BN_bn2bin(bn, o) 183 return o.raw.rjust(l_, b'\x00') 184 185 def _bn_deserialize(self, data): 186 """Make a BigNum out of string""" 187 x = OpenSSL.BN_bin2bn(data, OpenSSL.BN_num_bytes(self.n), None) 188 return x 189 190 def _init_privkey(self, privkey): 191 """Initialise private key out of string/bytes""" 192 self.d = self._bn_deserialize(privkey) 193 194 def privkey(self): 195 """Make a private key into a string""" 196 return pack(BIGNUM, self.d) 197 198 def _init_pubkey(self, pubkey): 199 """Initialise pubkey out of string/bytes""" 200 unpacked = unpack(PUBKEY, pubkey) 201 self.expiration = Expiration.deserialize(unpacked[0]) 202 self.value = Value.deserialize(unpacked[1]) 203 self.Q = self._ec_point_deserialize(unpacked[2]) 204 205 def pubkey(self): 206 """Make a pubkey into a string""" 207 return pack(PUBKEY, self.expiration.serialize(), 208 self.value.serialize(), 209 self._ec_point_serialize(self.Q)) 210 211 def __init__(self, curve="secp256k1", pubkey=None, privkey=None, 212 year=2025, month=11, value=0xFF): 213 self.ctx = OpenSSL.BN_CTX_new() 214 215 # ECC group 216 self.group = OpenSSL.EC_GROUP_new_by_curve_name( 217 OpenSSL.get_curve(curve)) 218 219 # Order n 220 self.n = OpenSSL.BN_new() 221 OpenSSL.EC_GROUP_get_order(self.group, self.n, self.ctx) 222 223 # Generator G 224 self.G = OpenSSL.EC_GROUP_get0_generator(self.group) 225 226 # Identity O (infinity) 227 self.iO = OpenSSL.EC_POINT_new(self.group) 228 OpenSSL.EC_POINT_set_to_infinity(self.group, self.iO) 229 230 if privkey: 231 assert pubkey 232 # load both pubkey and privkey from bytes 233 self._init_privkey(privkey) 234 self._init_pubkey(pubkey) 235 elif pubkey: 236 # load pubkey from bytes 237 self._init_pubkey(pubkey) 238 else: 239 # new keypair 240 self.d, self.Q = self.ec_gen_keypair() 241 if not year or not month: 242 now = time.gmtime() 243 if now.tm_mon == 12: 244 self.expiration = Expiration(now.tm_year + 1, 1) 245 else: 246 self.expiration = Expiration(now.tm_year, now.tm_mon + 1) 247 else: 248 self.expiration = Expiration(year, month) 249 self.value = Value(value) 250 251 def __del__(self): 252 OpenSSL.BN_free(self.n) 253 OpenSSL.BN_CTX_free(self.ctx) 254 255 def signer_init(self): 256 """ 257 Init signer 258 """ 259 # Signer: Random integer k 260 self.k = self.ec_get_random() 261 262 # R = kG 263 self.R = OpenSSL.EC_POINT_new(self.group) 264 OpenSSL.EC_POINT_mul(self.group, self.R, self.k, None, None, None) 265 266 return self._ec_point_serialize(self.R) 267 268 def create_signing_request(self, R, msg): 269 """ 270 Requester creates a new signing request 271 """ 272 self.R = self._ec_point_deserialize(R) 273 msghash = sha256(msg).digest() 274 275 # Requester: 3 random blinding factors 276 self.F = OpenSSL.EC_POINT_new(self.group) 277 OpenSSL.EC_POINT_set_to_infinity(self.group, self.F) 278 temp = OpenSSL.EC_POINT_new(self.group) 279 abinv = OpenSSL.BN_new() 280 281 # F != O 282 while OpenSSL.EC_POINT_cmp(self.group, self.F, self.iO, self.ctx) == 0: 283 self.a = self.ec_get_random() 284 self.b = self.ec_get_random() 285 self.c = self.ec_get_random() 286 287 # F = b^-1 * R... 288 self.binv = self.ec_invert(self.b) 289 OpenSSL.EC_POINT_mul(self.group, temp, None, self.R, self.binv, 290 None) 291 OpenSSL.EC_POINT_copy(self.F, temp) 292 293 # ... + a*b^-1 * Q... 294 OpenSSL.BN_mul(abinv, self.a, self.binv, self.ctx) 295 OpenSSL.EC_POINT_mul(self.group, temp, None, self.Q, abinv, None) 296 OpenSSL.EC_POINT_add(self.group, self.F, self.F, temp, None) 297 298 # ... + c*G 299 OpenSSL.EC_POINT_mul(self.group, temp, None, self.G, self.c, None) 300 OpenSSL.EC_POINT_add(self.group, self.F, self.F, temp, None) 301 302 # F = (x0, y0) 303 self.r = self.ec_Ftor(self.F) 304 305 # Requester: Blinding (m' = br(m) + a) 306 self.m = OpenSSL.BN_new() 307 OpenSSL.BN_bin2bn(msghash, len(msghash), self.m) 308 309 self.m_ = OpenSSL.BN_new() 310 OpenSSL.BN_mod_mul(self.m_, self.b, self.r, self.n, self.ctx) 311 OpenSSL.BN_mod_mul(self.m_, self.m_, self.m, self.n, self.ctx) 312 OpenSSL.BN_mod_add(self.m_, self.m_, self.a, self.n, self.ctx) 313 return self._bn_serialize(self.m_) 314 315 def blind_sign(self, m_): 316 """ 317 Signer blind-signs the request 318 """ 319 self.m_ = self._bn_deserialize(m_) 320 self.s_ = OpenSSL.BN_new() 321 OpenSSL.BN_mod_mul(self.s_, self.d, self.m_, self.n, self.ctx) 322 OpenSSL.BN_mod_add(self.s_, self.s_, self.k, self.n, self.ctx) 323 OpenSSL.BN_free(self.k) 324 return self._bn_serialize(self.s_) 325 326 def unblind(self, s_): 327 """ 328 Requester unblinds the signature 329 """ 330 self.s_ = self._bn_deserialize(s_) 331 s = OpenSSL.BN_new() 332 OpenSSL.BN_mod_mul(s, self.binv, self.s_, self.n, self.ctx) 333 OpenSSL.BN_mod_add(s, s, self.c, self.n, self.ctx) 334 OpenSSL.BN_free(self.a) 335 OpenSSL.BN_free(self.b) 336 OpenSSL.BN_free(self.c) 337 self.signature = (s, self.F) 338 return self._bn_serialize(s) + self._ec_point_serialize(self.F) 339 340 def verify(self, msg, signature, value=1): 341 """ 342 Verify signature with certifier's pubkey 343 """ 344 345 # convert msg to BIGNUM 346 self.m = OpenSSL.BN_new() 347 msghash = sha256(msg).digest() 348 OpenSSL.BN_bin2bn(msghash, len(msghash), self.m) 349 350 # init 351 s, self.F = (self._bn_deserialize(signature[0:32]), 352 self._ec_point_deserialize(signature[32:])) 353 if self.r is None: 354 self.r = self.ec_Ftor(self.F) 355 356 lhs = OpenSSL.EC_POINT_new(self.group) 357 rhs = OpenSSL.EC_POINT_new(self.group) 358 359 OpenSSL.EC_POINT_mul(self.group, lhs, s, None, None, None) 360 361 OpenSSL.EC_POINT_mul(self.group, rhs, None, self.Q, self.m, None) 362 OpenSSL.EC_POINT_mul(self.group, rhs, None, rhs, self.r, None) 363 OpenSSL.EC_POINT_add(self.group, rhs, rhs, self.F, self.ctx) 364 365 retval = OpenSSL.EC_POINT_cmp(self.group, lhs, rhs, self.ctx) 366 if retval == -1: 367 raise RuntimeError("EC_POINT_cmp returned an error") 368 elif not self.value.verify(value): 369 return False 370 elif not self.expiration.verify(): 371 return False 372 elif retval != 0: 373 return False 374 return True