first upload

This commit is contained in:
tcsenpai 2024-07-13 18:49:17 +02:00
commit 2c3d0ecae5
7 changed files with 2275 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
data/credential.pkl
__pycache__

28
README.md Normal file
View 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

File diff suppressed because it is too large Load Diff

42
libs/cred_manager.py Normal file
View 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
View 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
View 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
View File

@ -0,0 +1 @@
fido2