Skip to content

Instantly share code, notes, and snippets.

@watahani
Last active October 29, 2024 16:28
Show Gist options
  • Save watahani/42b2514d29c9346676cb70d6dccb1937 to your computer and use it in GitHub Desktop.
Save watahani/42b2514d29c9346676cb70d6dccb1937 to your computer and use it in GitHub Desktop.
Hierarchy Deterministic Key of webauthn credential recovery extension.
import ecdsa
from ecdsa import SECP256k1
from ecdsa.keys import SigningKey, VerifyingKey
from math import log2
import hmac
import hashlib
import secrets
CURVE_ORDER = SECP256k1.order
def hmac512(key, source):
if isinstance(source, str):
source = source.encode()
if isinstance(key, str):
key = key.encode()
return hmac.new(key, source, hashlib.sha512).digest()
def prikey_and_ccode(key, seed):
''' generate ECDSA private key of ecdsa lib and chain code as string'''
hmac = hmac512(key, seed)
prikey = hmac[:32]
prikey = ecdsa.SigningKey.from_string(prikey, curve=ecdsa.SECP256k1)
ccode = hmac[32:]
return prikey, ccode
def add_secret_keys(*args, order):
'''add bytes secrets as int and return new bytes'''
prikey = 0
for key in args:
if prikey == 0:
prikey = int.from_bytes(key, "big")
else:
prikey = (prikey + int.from_bytes(key, "big")) % order
return prikey.to_bytes( int(log2(order)/8), 'big')
def int_from_bytes(key):
return int.from_bytes(key, "big")
def deltakey_and_ccode_from(index, pubkey, ccode):
source = pubkey + index
deltakey, child_ccode = prikey_and_ccode(key=ccode, seed=source)
return deltakey, child_ccode
class HDKey:
''' extended key '''
def __init__(self,index, prikey, ccode, pubkey, is_prikey, depth=0):
self.depth = depth
self.is_prikey = is_prikey
self.index = index[:]
ccode_int = int_from_bytes(ccode)
if not ccode or ccode_int > CURVE_ORDER:
raise Error('ccode must less than {}'.format(CURVE_ORDER))
self.ccode = ccode[:]
if is_prikey:
if not isinstance(prikey, SigningKey):
raise Error('need prikey')
self.prikey = prikey
self.pubkey = prikey.get_verifying_key()
else:
self.pubkey = VerifyingKey.from_string(pubkey.to_string(), curve=pubkey.curve)
def child_key(self, index, include_prikey=False):
'''generate child key'''
if include_prikey:
if not self.is_prikey:
raise Exception('this key doesn\'t include prikey')
return self._child_key_from_prikey(index)
else:
pubkey = self.pubkey
ccode = self.ccode
deltakey, child_ccode = deltakey_and_ccode_from(index, pubkey.to_string(), ccode)
deltakey_point = deltakey.get_verifying_key().pubkey.point
point = pubkey.pubkey.point + deltakey_point
child_key = ecdsa.VerifyingKey.from_public_point(point, curve=SECP256k1)
return HDKey(index=index, prikey=None,ccode=child_ccode, is_prikey=False, pubkey=child_key, depth=self.depth+1)
def to_string(self):
print("is_prikey: ", str(self.is_prikey))
print("depth : ", self.depth )
print("index : ", self.index)
print("prikey : ", self.prikey.to_string().hex() if self.is_prikey else 'None' )
print('pubkey : ', self.pubkey.to_string().hex())
print('ccode : ', self.ccode.hex())
def _child_key_from_prikey(self, index):
''' generate childkey from prikey and chain code'''
prikey = self.prikey
ccode = self.ccode
pubkey = prikey.get_verifying_key().to_string()
delta_key, child_ccode = deltakey_and_ccode_from(index, pubkey, ccode)
child_key_str = add_secret_keys(prikey.to_string(), delta_key.to_string(), order=SECP256k1.order)
child_key = ecdsa.SigningKey.from_string(child_key_str, curve=SECP256k1)
return HDKey(index=index, prikey=child_key, ccode=child_ccode, pubkey=None, is_prikey=True)
def generateRandomKeyId(self):
id = secrets.token_bytes(8)
hmac = hmac512(self.ccode, id)[:8]
return id + hmac
def pubkey_seed(self):
child_keyid = self.generateRandomKeyId()
return self.child_key(child_keyid,include_prikey=False)
def is_child_key_of(self, key):
if self.depth <= 0:
return False
return self.is_child_key_id(key)
def is_child_key_id(self, keyid):
id = keyid[:8]
hmac = keyid[8:]
return hmac == hmac512(self.ccode, id)[:8]
def child_key_from_id(self, keyid):
if self.is_child_key_id(keyid):
return self.child_key(keyid,include_prikey=self.is_prikey)
else:
raise Exception('invalid keyid')
m_key, m_ccode = prikey_and_ccode('webauthn', 'seed')
master_key = HDKey(index=b'0', prikey=m_key, ccode=m_ccode, pubkey=None, is_prikey=True)
print("======== master_key ==========")
master_key.to_string()
pubkey_seed = master_key.pubkey_seed()
print("======== pubkey_seed ==========")
pubkey_seed.to_string()
print("======== pubkey ==========")
pubkey=pubkey_seed.pubkey_seed()
pubkey.to_string()
print("======== private key ==========")
master_key.child_key_from_id(pubkey_seed.index).child_key_from_id(pubkey.index).to_string()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment