HW Accelerated AES CTR for BSMS and passphrase saver
This commit is contained in:
parent
5e2e435583
commit
d8dd62732c
@ -15,6 +15,7 @@
|
||||
imports that have no file, in which case name was created from descriptor checksum.
|
||||
- Enhancement: Allow keys with same origin, differentiated only by change index derivation
|
||||
in miniscript descriptor.
|
||||
- Enhancement: HW Accelerated AES CTR for BSMS and passphrase saver
|
||||
- Bugfix: Do not allow to import duplicate miniscript wallets (thanks to [AnchorWatch](https://www.anchorwatch.com/))
|
||||
|
||||
## 6.2.1X - 2023-10-26
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
# For faster testing...
|
||||
# ./simulator.py --seq 99y3y4y
|
||||
#
|
||||
import ngu, os, stash, chains
|
||||
import ngu, os, stash, chains, aes256ctr
|
||||
from ubinascii import b2a_base64, a2b_base64
|
||||
from ubinascii import unhexlify as a2b_hex
|
||||
from ubinascii import hexlify as b2a_hex
|
||||
@ -82,7 +82,7 @@ def msg_auth_code(key, token_hex, data):
|
||||
def bsms_decrypt(key, data_bytes):
|
||||
mac, ciphertext = data_bytes[:32], data_bytes[32:]
|
||||
iv = mac[:16]
|
||||
decrypt = ngu.aes.CTR(key, iv)
|
||||
decrypt = aes256ctr.new(key, iv)
|
||||
decrypted = decrypt.cipher(ciphertext)
|
||||
try:
|
||||
plaintext = decrypted.decode()
|
||||
@ -98,7 +98,7 @@ def bsms_encrypt(key, token_hex, data_str):
|
||||
hmac_k = hmac_key(key)
|
||||
mac = msg_auth_code(hmac_k, token_hex, data_str)
|
||||
iv = mac[:16]
|
||||
encrypt = ngu.aes.CTR(key, iv)
|
||||
encrypt = aes256ctr.new(key, iv)
|
||||
ciphertext = encrypt.cipher(data_str)
|
||||
|
||||
return mac + ciphertext
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
# pwsave.py - Save bip39 passphrases into encrypted file on MicroSD (if desired)
|
||||
#
|
||||
import stash, ujson, ngu, pyb, os
|
||||
import stash, ujson, ngu, pyb, os, aes256ctr
|
||||
from files import CardSlot, CardMissingError, needs_microsd
|
||||
from ux import ux_dramatic_pause, ux_confirm, ux_show_story
|
||||
from utils import xfp2str
|
||||
@ -36,7 +36,7 @@ class PassphraseSaver:
|
||||
def _read(self, card):
|
||||
# Return a list of saved passphrases, or empty list if fail.
|
||||
# Fail silently in all cases. Expect to see lots of noise here.
|
||||
decrypt = ngu.aes.CTR(self.key)
|
||||
decrypt = aes256ctr.new(self.key)
|
||||
|
||||
try:
|
||||
fname = self.filename(card)
|
||||
@ -60,7 +60,7 @@ class PassphraseSaver:
|
||||
data = self._read(card) if self.key else []
|
||||
yield data # yield data that can be modified
|
||||
|
||||
encrypt = ngu.aes.CTR(self.key)
|
||||
encrypt = aes256ctr.new(self.key)
|
||||
|
||||
msg = encrypt.cipher(ujson.dumps(data))
|
||||
|
||||
@ -326,7 +326,7 @@ class MicroSD2FA(PassphraseSaver):
|
||||
|
||||
data = dict(nonce=nonce)
|
||||
|
||||
encrypt = ngu.aes.CTR(self.key)
|
||||
encrypt = aes256ctr.new(self.key)
|
||||
msg = encrypt.cipher(ujson.dumps(data))
|
||||
|
||||
with open(self.filename(card), 'wb') as fd:
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
# utils.py - Misc utils. My favourite kind of source file.
|
||||
#
|
||||
import gc, sys, ustruct, ngu, chains, ure, uos, uio, time
|
||||
import gc, sys, ustruct, chains, ure, uos, uio, time, aes256ctr
|
||||
from ubinascii import unhexlify as a2b_hex
|
||||
from ubinascii import hexlify as b2a_hex
|
||||
from ubinascii import a2b_base64, b2a_base64
|
||||
@ -524,7 +524,7 @@ def chunk_writer(fd, body):
|
||||
def decrypt_tapsigner_backup(backup_key, data):
|
||||
try:
|
||||
backup_key = a2b_hex(backup_key)
|
||||
decrypt = ngu.aes.CTR(backup_key, bytes(16)) # IV 0
|
||||
decrypt = aes256ctr.new(backup_key, bytes(16)) # IV 0
|
||||
decrypted = decrypt.cipher(data).decode().strip()
|
||||
# format of TAPSIGNER backup is known in advance
|
||||
# extended private key is expected at the beginning of the first line
|
||||
|
||||
27
testing/devtest/proof_hw_accel_aes.py
Normal file
27
testing/devtest/proof_hw_accel_aes.py
Normal file
@ -0,0 +1,27 @@
|
||||
import utime, ngu, aes256ctr, math
|
||||
|
||||
# Cifra
|
||||
start = utime.ticks_ms()
|
||||
for i in range(100):
|
||||
enc = ngu.aes.CTR(b"a" * 32, "b"*16)
|
||||
dec = ngu.aes.CTR(b"a" * 32, "b"*16)
|
||||
em = enc.cipher(b"msg" * i)
|
||||
dm = dec.cipher(em)
|
||||
assert dm == b"msg" * i
|
||||
end = utime.ticks_ms()
|
||||
cifra_res = utime.ticks_diff(end, start)
|
||||
|
||||
|
||||
# aes256ctr
|
||||
start = utime.ticks_ms()
|
||||
for i in range(100):
|
||||
enc = aes256ctr.new(b"a" * 32, "b"*16)
|
||||
dec = aes256ctr.new(b"a" * 32, "b"*16)
|
||||
em = enc.cipher(b"msg" * i)
|
||||
dm = dec.cipher(em)
|
||||
assert dm == b"msg" * i
|
||||
end = utime.ticks_ms()
|
||||
hwa_res = utime.ticks_diff(end, start)
|
||||
|
||||
r = math.ceil(cifra_res / hwa_res)
|
||||
print("Hardware accelerated AES is approximatelly %dX faster than Cifra AES." % r)
|
||||
84
testing/devtest/unit_aes_compat.py
Normal file
84
testing/devtest/unit_aes_compat.py
Normal file
@ -0,0 +1,84 @@
|
||||
import ngu, aes256ctr, ujson, ustruct
|
||||
|
||||
key = b"a" * 32
|
||||
|
||||
bsms_signer = b"""BSMS 1.0
|
||||
a54044308ceac9b7
|
||||
[eedff89a/48'/0'/0'/2']xpub6EhJvMneoLWAf8cuyLBLQiKiwh89RAmqXEqYeFuaCEHdHwxSRfzLrUxKXEBap7nZSHAYP7Jfq6gZmucotNzpMQ9Sb1nTqerqW8hrtmx6Y6o
|
||||
Signer 2 key
|
||||
H/IHW5dMGYsrRdYEz3ux+kKnkWBtxHzfYkREpnYbco38VnMvIxCbDuf7iu6960qDhBLR/RLjlb9UPtLmCMbczDE="""
|
||||
|
||||
bsms_coord = b"""BSMS 1.0
|
||||
wsh(sortedmulti(2,[b7868815/48'/0'/0'/2']xpub6FA5rfxJc94K1kNtxRby1hoHwi7YDyTWwx1KUR3FwskaF6HzCbZMz3zQwGnCqdiFeMTPV3YneTGS2YQPiuNYsSvtggWWMQpEJD4jXU7ZzEh/**,[eedff89a/48'/0'/0'/2']xpub6EhJvMneoLWAf8cuyLBLQiKiwh89RAmqXEqYeFuaCEHdHwxSRfzLrUxKXEBap7nZSHAYP7Jfq6gZmucotNzpMQ9Sb1nTqerqW8hrtmx6Y6o/**))
|
||||
/0/*,/1/*
|
||||
bc1qhs4u273g4azq7kqqpe6vh5wfhasfmrq7nheyzsnq77humd7rwtkqagvakf"""
|
||||
|
||||
pws = [dict(xfp=0x4369050f, pw=pw) for pw in ["about abandon about", "@#$%^&*()", "ksjdfh78$%"]]
|
||||
pws_ser = ujson.dumps(pws).encode()
|
||||
|
||||
# mimic real data that we use
|
||||
TEST_CASES = [
|
||||
b'Hello World!',
|
||||
pws_ser,
|
||||
bsms_coord,
|
||||
bsms_signer
|
||||
]
|
||||
|
||||
|
||||
def secret_msg_exchange(alice, bob, msg):
|
||||
e_msg = alice.cipher(msg)
|
||||
assert bob.cipher(e_msg) == msg
|
||||
return_msg = msg + b"\x00ACK"
|
||||
e_msg = bob.cipher(return_msg)
|
||||
assert alice.cipher(e_msg) == return_msg
|
||||
|
||||
|
||||
for i, msg in enumerate(TEST_CASES):
|
||||
# 16 bytes random IV
|
||||
# encrypt with Cifra, decrypt with HW accelerated AES
|
||||
iv = ngu.random.bytes(16)
|
||||
encrypt = ngu.aes.CTR(key, iv)
|
||||
decrypt = aes256ctr.new(key, iv)
|
||||
secret_msg_exchange(encrypt, decrypt, msg)
|
||||
print("Cifra AES --> HW AES\tIV=0b16\t\tOK")
|
||||
|
||||
# encrypt with HW accelerated AES, decrypt with Cifra
|
||||
encrypt = aes256ctr.new(key, iv)
|
||||
decrypt = ngu.aes.CTR(key, iv)
|
||||
secret_msg_exchange(encrypt, decrypt, msg)
|
||||
print("HW AES --> Cifra AES\tIV=0b16\t\tOK")
|
||||
|
||||
# empty IV
|
||||
# encrypt with Cifra, decrypt with HW accelerated AES
|
||||
encrypt = ngu.aes.CTR(key)
|
||||
decrypt = aes256ctr.new(key)
|
||||
secret_msg_exchange(encrypt, decrypt, msg)
|
||||
print("Cifra AES --> HW AES\tIV=NONE\t\tOK")
|
||||
|
||||
# encrypt with HW accelerated AES, decrypt with Cifra
|
||||
encrypt = aes256ctr.new(key)
|
||||
decrypt = ngu.aes.CTR(key)
|
||||
secret_msg_exchange(encrypt, decrypt, msg)
|
||||
print("HW AES --> Cifra AES\tIV=NONE\t\tOK")
|
||||
|
||||
|
||||
print("RANDOM TEST CASES")
|
||||
for i in range(10):
|
||||
key = ngu.random.bytes(32)
|
||||
iv = ngu.random.bytes(16)
|
||||
|
||||
msg = (key + iv)
|
||||
if i:
|
||||
msg = msg * i
|
||||
|
||||
# encrypt with Cifra, decrypt with HW accelerated AES
|
||||
encrypt = ngu.aes.CTR(key, iv)
|
||||
decrypt = aes256ctr.new(key, iv)
|
||||
secret_msg_exchange(encrypt, decrypt, msg)
|
||||
print("Cifra AES --> HW AES\tIV=0b16\t\tOK")
|
||||
|
||||
# encrypt with HW accelerated AES, decrypt with Cifra
|
||||
encrypt = aes256ctr.new(key, iv)
|
||||
decrypt = ngu.aes.CTR(key, iv)
|
||||
secret_msg_exchange(encrypt, decrypt, msg)
|
||||
print("HW AES --> Cifra AES\tIV=0b16\t\tOK")
|
||||
@ -274,4 +274,8 @@ def test_is_dir(microsd_path, sim_exec):
|
||||
shutil.rmtree(microsd_path("my_dir"))
|
||||
|
||||
|
||||
def test_aes_compatibility(sim_execfile):
|
||||
res = sim_execfile('devtest/unit_aes_compat.py')
|
||||
assert res == ""
|
||||
|
||||
# EOF
|
||||
|
||||
Loading…
Reference in New Issue
Block a user