Simulation¶
pyecsca is able to simulate computation of key generation, ECDH and ECDSA while tracing particular actions performed by the implementation as well as intermediate values. These traces are collected by the context (see the Context and DefaultContext classes). There is always one context active. For performance reasons, by default no context is active.
These traces are useful for attacks which rely on computing particular intermediate values during the ECC computation.
Initialisation¶
[ ]:
from binascii import hexlify
from copy import copy
from pyecsca.ec.key_generation import KeyGeneration
from pyecsca.ec.key_agreement import ECDH_SHA1
from pyecsca.ec.signature import ECDSA_SHA1
from pyecsca.ec.params import get_params
from pyecsca.ec.mult import LTRMultiplier
from pyecsca.ec.context import DefaultContext, local
[ ]:
p256 = get_params("secg", "secp256r1", "projective")
model = p256.curve.model
coords = p256.curve.coordinate_model
add = coords.formulas["add-2007-bl"]
dbl = coords.formulas["dbl-2007-bl"]
mult = LTRMultiplier(add, dbl, None)
Key generation¶
[ ]:
keygen = KeyGeneration(copy(mult), p256, False)
[ ]:
with local(DefaultContext()) as keygen_ctx:
private, public = keygen.generate()
print(private)
print(public)
print(keygen_ctx.actions)
Signing¶
[ ]:
ecdsa = ECDSA_SHA1(copy(mult), p256, add, public, private)
data = b"something"
[ ]:
with local(DefaultContext()) as sign_ctx:
signature = ecdsa.sign_data(data)
print(data)
print(signature)
print(sign_ctx.actions)
[ ]:
with local(DefaultContext()) as verify_ctx:
verified = ecdsa.verify_data(signature, data)
print(verified)
print(verify_ctx.actions)
Key agreement¶
[ ]:
other_private, other_public = keygen.generate()
ecdh_a = ECDH_SHA1(copy(mult), p256, public, other_private)
ecdh_b = ECDH_SHA1(copy(mult), p256, other_public, private)
with local(DefaultContext()) as ecdh_ctx:
ecdh_a_result = ecdh_a.perform()
ecdh_b_result = ecdh_b.perform()
print(hexlify(ecdh_a_result))
print(hexlify(ecdh_b_result))
print(ecdh_a_result == ecdh_b_result)
print(ecdh_ctx.actions)
Walking the trace¶
As visible from the outputs above, DefaultContext
traces the actions performed by the implementation in an ordered tree where the child relationship means that some actions happened during another action and order of children gives the order of operations. In the above example of ECDH, two ECDH executions are visible, each consisting of one scalar multuplication which consists of several applications of add
and dbl
formulas.
We can examine this tree in the first ECDH execution and see that the scalar multiplier used was not setup to be regular (see LTRMultiplier
argument always
) and that the order of operations leaks bits of the scalar. This fact will be easily exploitable on a power trace via SPA.
[ ]:
first_ecdh = ecdh_ctx.actions[0]
second_ecdh = ecdh_ctx.actions[1]
ecdh = first_ecdh.get_by_index([])
scalarmult = first_ecdh.get_by_index([0])
recovered_private = 1
for formula_node in scalarmult.children:
formula_call = formula_node.action
if formula_call.formula.shortname == "add":
recovered_private |= 1
elif formula_call.formula.shortname == "dbl":
recovered_private <<= 1
print(formula_call.formula.shortname)
print(bin(int(other_private)))
print(bin(int(recovered_private)))
print(other_private == recovered_private)
One can navigate the tree by indices, to get the second formula call of the second ECDH scalar multiplication:
[ ]:
node = first_ecdh.get_by_index([0, 1])
print(repr(node.action))
The [0,1]
path given above represented a walk through the execution trace, taking the first child (a scalar multiplication) and then a second child (a second formula application).
[ ]: