This chapter privides details about the cryptographic algorithms used throughout luca. All primitives have been selected in accordance with the Technical Guideline TR-02102-1 of the German BSI 1.
AES with a key length of 128 bits in Counter Mode (
AES-128-CTR) for symmetric encryption.
Data is authenticated using
luca uses elliptic curves for the required asymmetric cryptography.
Asymmetric encryption in luca is based on the DLIES encryption scheme as specified in section 3.5 of BSI TR-02102-1 1. For the components required, luca uses the following primitives:
asymmetric key parameters
message authentication code
key derivation function
We use ECDSA with the
NIST P-256) elliptic curve.
On the Use of
secp256r1 is recommended by NIST 2 for use with Discrete Logarithm-Based Cryptography.
It is, however, criticized for using unexplained inputs in the curve-generation process and hence rumored to be backdoored by the NSA 3.
At the time of writing, those rumors can neither be proven nor disproven.
Consequently it would be preferable to use a different curve that is generally considered safe.
However, as of 2021,
secp256r1 is the only curve widely supported by many mobile phone hardware-based key managers 45.
We chose to use
secp256r1 because we value the security benefit of using the trusted module higher than the risks laid out above.
Based on the scheme specified above, asymmetric encryption is implemented by the components in luca as follows:
# pseudocode # given the inputs: # * data: the data to encrypt # * receiver_public_key: the public key of the receiver # * iv (optional): the initialization vector ephemeral_keys = a new secp256r1 key pair (for DLIES with the receiver's public key) iv = random_bytes(16) # Note: in some contexts an external input (usually # an ephemeral public key) is used as IV dh_key = ECDH(ephemeral_keys.private, receiver_public_key) encryption_key = SHA256(dh_key || 0x01) # truncated to 16 bytes authentication_key = SHA256(dh_key || 0x02) encrypted_data = AES-128-CTR(data, encryption_key, iv) mac = HMAC-SHA256(encrypted_data, authentication_key)
The function’s output includes
On the Use of the ephemeral public key as IV¶
As indicated in the pseudocode snippet above, we sometimes re-use the DLIES ephemeral key pair’s public key as the initialization vector for
Our motivation for this is the limited capacity of the Check-In QR codes generated by the Guest App. The receiver of the encrypted data needs both, the ephemeral public key and the IV, to decrypt the data. Re-using the public key as IV saves space.
This construction is uncommon, but secure.
AES-CTR only requires the initialization vector to be unique, which is satisfied here.
Accordingly, decryption and authentication is implemented as follows:
# pseudocode # given the inputs: # * encrypted_data: the data to decrypt # * receiver_private_key: the private key of the receiver # * iv: the initialization vector # * ephemeral_public_key: the sender's public key for DLIES (ephemeral_keys.public) # * mac: the message authentication code dh_key = ECDH(receiver_private_key, ephemeral_public_key) encryption_key = SHA256(dh_key || 0x01) # truncated to 16 bytes authentication_key = SHA256(dh_key || 0x02) verify_mac(mac, HMAC-SHA256(encryptedData, authentication_key)) decrypted_data = AES-128-CTR(data, encryption_key, iv)
If the provided
mac is valid, the function returns the
BSI TR-02102-1. Cryptographic Mechanisms: Recommendations and Key Lengths, accessed 2021/03/04
Apple Developer documentation on SecureEnclave, accessed 2021/03/04
Android Developer documentation on HSM, accessed 2021/03/08