Last active
October 29, 2024 16:28
-
-
Save watahani/42b2514d29c9346676cb70d6dccb1937 to your computer and use it in GitHub Desktop.
Hierarchy Deterministic Key of webauthn credential recovery extension.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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