mirror of
https://github.com/tcsenpai/hmywallet.git
synced 2025-06-03 01:20:04 +00:00
first upload
This commit is contained in:
commit
2c3d0ecae5
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
data/credential.pkl
|
||||
__pycache__
|
28
README.md
Normal file
28
README.md
Normal file
@ -0,0 +1,28 @@
|
||||
# HMyWallet
|
||||
|
||||
## Description
|
||||
This is a small proof of concept yet functional and working utility to safely manage a 24 words mnemonic seed using a FIDO2 authenticator and a password.
|
||||
|
||||
You can use it to reliably manage your mnemonic seed and to recover it later without having to remember it or to write it down.
|
||||
|
||||
## Usage
|
||||
The script will create a set of credentials from your FIDO2 authenticator during the first run. This set of credentials only works with your FIDO2 authenticator and must be in the data/credential.pkl file. If you lose your FIDO2 authenticator or your credential file, you will not be able to recover your mnemonic seed.
|
||||
|
||||
The script will then ask you to enter your password to authenticate your FIDO2 authenticator.
|
||||
As the password is not stored anywhere, it cannot be compromised by anyone with access to your computer.
|
||||
For the same reason, you will lose access to your mnemonic seed if you lose your password.
|
||||
|
||||
Using the provided password, the script will then generate a 24 words mnemonic seed and print it to the console.
|
||||
|
||||
You can then use this mnemonic seed to access or recover your wallet anywhere.
|
||||
|
||||
# How it works
|
||||
The password is hashed using SHA256, and is used as a seed to generate a hmac_secret from the provided credential.pkl file.
|
||||
This hmac_secret is then used to generate a 24 words mnemonic seed using a wordlist.txt file.
|
||||
This way, both the password and the hmac_secret are only kept in memory until the mnemonic seed is generated, and are not stored anywhere.
|
||||
|
||||
# Credits
|
||||
This script is based on the fido2 library by Yubico.
|
||||
|
||||
## Disclaimer
|
||||
This script is provided as is and should be used with caution. It is not tested for usage in managing mnemonic seeds in a production environment.
|
2048
data/wordlist.txt
Normal file
2048
data/wordlist.txt
Normal file
File diff suppressed because it is too large
Load Diff
42
libs/cred_manager.py
Normal file
42
libs/cred_manager.py
Normal file
@ -0,0 +1,42 @@
|
||||
|
||||
import os
|
||||
import pickle
|
||||
import sys
|
||||
|
||||
def create_credential(client, rp, user, challenge):
|
||||
# Create a random challenge
|
||||
challenge = os.urandom(16)
|
||||
# Create a credential with a HmacSecret
|
||||
result = client.make_credential(
|
||||
{
|
||||
"rp": rp,
|
||||
"user": user,
|
||||
"challenge": challenge,
|
||||
"pubKeyCredParams": [{"type": "public-key", "alg": -7}],
|
||||
"extensions": {"hmacCreateSecret": True},
|
||||
},
|
||||
)
|
||||
|
||||
# HmacSecret result:
|
||||
if not result.extension_results.get("hmacCreateSecret"):
|
||||
print("Failed to create credential with HmacSecret")
|
||||
sys.exit(1)
|
||||
|
||||
credential = result.attestation_object.auth_data.credential_data
|
||||
print("New credential created, with the HmacSecret extension.")
|
||||
# Saving credential to file with pickle
|
||||
with open("credential.pkl", "wb") as f:
|
||||
pickle.dump(credential, f)
|
||||
return credential
|
||||
|
||||
def load_credential():
|
||||
try:
|
||||
with open("data/credential.pkl", "rb") as f:
|
||||
print("Loading credential from file")
|
||||
return pickle.load(f)
|
||||
except FileNotFoundError:
|
||||
print("No credential found, creating new one")
|
||||
return None
|
||||
except Exception as e:
|
||||
print("Failed to load credential from file:", e)
|
||||
return None
|
22
libs/word_gen.py
Normal file
22
libs/word_gen.py
Normal file
@ -0,0 +1,22 @@
|
||||
import binascii
|
||||
|
||||
# Can generate a mnemonic seed from data of 16, 20, 24, 28 or 32 bytes
|
||||
def gen_from_data(data):
|
||||
# Checking if the data length is correct
|
||||
if len(data) not in [16, 20, 24, 28, 32]:
|
||||
raise ValueError(
|
||||
"Data length should be one of the following: [16, 20, 24, 28, 32], but it is not (%d).” % len(data)"
|
||||
)
|
||||
|
||||
# Converting the data to binary
|
||||
bindata = bin(int(binascii.hexlify(data), 16))[2:].zfill(8 * len(data))
|
||||
|
||||
# Opening wordlist
|
||||
with open("data/wordlist.txt", "r") as f:
|
||||
wordlist = [w.strip() for w in f.readlines()]
|
||||
# Generating a seed from the binary data
|
||||
seed = []
|
||||
for i in range(len(bindata)//11):
|
||||
index = int(bindata[11*i:11*(i+1)],2)
|
||||
seed.append(wordlist[index])
|
||||
return seed
|
132
main.py
Normal file
132
main.py
Normal file
@ -0,0 +1,132 @@
|
||||
# Copyright (c) 2018 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or
|
||||
# without modification, are permitted provided that the following
|
||||
# conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""
|
||||
Connects to the first FIDO device found which supports the HmacSecret extension,
|
||||
creates a new credential for it with the extension enabled, and uses it to
|
||||
derive two separate secrets.
|
||||
"""
|
||||
from fido2.hid import CtapHidDevice
|
||||
from fido2.client import Fido2Client, UserInteraction
|
||||
from getpass import getpass
|
||||
import libs.word_gen as word_gen
|
||||
import libs.cred_manager as cred_manager
|
||||
import hashlib
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Utilities
|
||||
|
||||
def sha256(data):
|
||||
enc = hashlib.sha256()
|
||||
enc.update(data.encode())
|
||||
return enc.digest()
|
||||
|
||||
# FIDO2
|
||||
|
||||
try:
|
||||
from fido2.pcsc import CtapPcscDevice
|
||||
except ImportError:
|
||||
CtapPcscDevice = None
|
||||
|
||||
|
||||
def enumerate_devices():
|
||||
for dev in CtapHidDevice.list_devices():
|
||||
yield dev
|
||||
if CtapPcscDevice:
|
||||
for dev in CtapPcscDevice.list_devices():
|
||||
yield dev
|
||||
|
||||
# Handle user interaction
|
||||
class CliInteraction(UserInteraction):
|
||||
def prompt_up(self):
|
||||
print("\nTouch your authenticator device now...\n")
|
||||
|
||||
def request_pin(self, permissions, rd_id):
|
||||
return getpass("Enter PIN: ")
|
||||
|
||||
def request_uv(self, permissions, rd_id):
|
||||
print("User Verification required.")
|
||||
return True
|
||||
|
||||
def locate_device():
|
||||
# Locate a device
|
||||
for dev in enumerate_devices():
|
||||
client = Fido2Client(dev, "https://localhost", user_interaction=CliInteraction())
|
||||
if "hmac-secret" in client.info.extensions:
|
||||
break
|
||||
else:
|
||||
print("No Authenticator with the HmacSecret extension found!")
|
||||
sys.exit(1)
|
||||
return client
|
||||
|
||||
# Main
|
||||
if __name__ == "__main__":
|
||||
# Prepare parameters for makeCredential and getAssertion
|
||||
rp = {"id": "localhost", "name": "HMyWallet"}
|
||||
user = {"id": b"1", "name": "HMyWalletUser"}
|
||||
|
||||
# Locate a device
|
||||
client = CliInteraction.locate_device()
|
||||
|
||||
# Loading or creating a new credential
|
||||
credential = cred_manager.load_credential()
|
||||
if not credential:
|
||||
credential = cred_manager.create_credential(client, rp, user)
|
||||
|
||||
# Prepare parameters for getAssertion
|
||||
challenge = os.urandom(16) # Use a new challenge for each call.
|
||||
allow_list = [{"type": "public-key", "id": credential.credential_id}]
|
||||
|
||||
# Ask user for password
|
||||
password = getpass("[+] Enter your password: ")
|
||||
# Generate a salt for HmacSecret:
|
||||
password_hash = sha256(password) #os.urandom(32)
|
||||
print("[OK] Password hashed and ready for authentication")
|
||||
|
||||
# Authenticate the credential
|
||||
result = client.get_assertion(
|
||||
{
|
||||
"rpId": rp["id"],
|
||||
"challenge": challenge,
|
||||
"allowCredentials": allow_list,
|
||||
"extensions": {"hmacGetSecret": {"salt1": password_hash}},
|
||||
},
|
||||
).get_response(
|
||||
0
|
||||
) # Only one cred in allowList, only one response.
|
||||
|
||||
secret = result.extension_results["hmacGetSecret"]["output1"]
|
||||
print("[OK] Authenticated and secret retrieved")
|
||||
|
||||
mnemonic_words = word_gen.gen_from_data(secret)
|
||||
mnemonic_string = " ".join(mnemonic_words)
|
||||
print("\nALERT: This is your mnemonic seed. Keep it in a safe place. You will need it to recover your wallet.")
|
||||
print("If you lose it, you will lose access to your wallet. There is no way to recover it later.")
|
||||
print("Anyone with access to your mnemonic seed can recover your wallet. Keep it in a secure location.")
|
||||
print("For security reasons, it is adviced to not save it in plain text: you can use your password and this script to recover it.")
|
||||
print("\nMnemonic words:\n", mnemonic_string)
|
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
fido2
|
Loading…
x
Reference in New Issue
Block a user