ECDH and CEK (Content Encryption Key) using COSE and CBOR

In computer security, we often have to represent binary data, in single values or groups of characters, bytes, words, long words, signed…

ECDH and CEK (Content Encryption Key) using COSE and CBOR

In computer security, we often have to represent binary data, in single values or groups of characters, bytes, words, long words, signed integers, floating-point values, double-precision floating-point values, and so on. This might be in the form of a data object, a signature or even encrypted content. For this, the ANS.1 DER format is often used, such as presenting digital certificates and signatures. An improvement on this for small messages with security is Concise Binary Object Representation (CBOR) — and which is defined in RFC8949 [1]. While JSON represents text-based data objects CBOR focuses on binary objects. It has been designed to create a lightweight encoder and decoder. This supports the use of CBOR within an IoT infrastructure. The data, also, does not require a data schema to be able to decode it, along with being extensible.

CBOR defines the format of binary data with data objects and messages. We can then integrate security with CBOR Object Signing and Encryption (COSE), and which is defined in RFC8152 [here]. This includes signatures, message authentication codes (MACs), encryption and creating serialised objects. So let’s using Bob and Alice’s public key to derive a shared key (using ECDH), and which can only be decrypted with Alice’s private key. The key will encrypt a message sent from Bob to Alice, and which can only be decrypted with the derived symmetric key (using AES-128 GCM).

Initially, Bob and Alice create the key pairs using the P-256 curve (bob and alice):

alice_private_key = urandom(32)
bob_private_key = urandom(32)
alice= EC2Key(crv='P_256', d=alice_private_key, optional_params={'KpAlg': 'EcdhEsHKDF256','KpKty': 'KtyEC2'})
alice = CoseKey.from_dict(alice)
bob= EC2Key(crv='P_256', d=alice_private_key, optional_params={'KpAlg': 'EcdhEsHKDF256','KpKty': 'KtyEC2'})

We can then remove Alice’s private key for Alice’s public key (alicepub) and remove Bob’s private key:

alicepub = deepcopy(alice) 
del alicepub[EC2KpD]
del bob[EC2KpD]

Next, we can derive a shared key using ECDH with Bob’s public key and Alice’s public key:

shared = DirectKeyAgreement(
phdr = {Algorithm: EcdhEsHKDF256},
uhdr = {EphemeralKey: alicepub})
shared.key = alice
shared.local_attrs = {StaticKey: bob}

Next, Bob can derive an encrypted message for Alice:

IVal = urandom(16)
msg = EncMessage(
phdr = {Algorithm: A128GCM},
uhdr = {IV: IVal},
payload = mymsg.encode(),
recipients = [shared])
encoded = msg.encode()
print ("IV: ",hexlify(IVal))
print ("Encoded message: ",hexlify(encoded))
decoded = CoseMessage.decode(encoded)
print ("\nDecoded: ",decoded)

print ("\nDecoded recipients",decoded.recipients)

Alice can then determine the encryption key using her private key from her key pair. For this we will derive a shared key from the public key of Alice and Bob, and then define a single recipient which can derive the CEK (Content Encryption Key) using their private key:

static_receiver_key = CoseKey.from_dict(alice)
decoded.recipients[0].key = alice
print ("\nDecrypt: ",decoded.decrypt(decoded.recipients[0]))

The following is the coding [here]:

from binascii import hexlify
from copy import deepcopy
from os import urandom
from cose.messages import EncMessage,CoseMessage
from cose.keys import CoseKey
from cose.messages.recipient import DirectKeyAgreement
from cose.headers import Algorithm, StaticKey, EphemeralKey, IV
from cose.algorithms import EcdhEsHKDF256, A128GCM
from cose.keys.keyparam import EC2KpD
from cose.keys import EC2Key
import sys
mymsg="hello"
if (len(sys.argv)>1):
mymsg=str(sys.argv[1])
alice_private_key = urandom(32)
bob_private_key = urandom(32)
alice= EC2Key(crv='P_256', d=alice_private_key, optional_params={'KpAlg': 'EcdhEsHKDF256','KpKty': 'KtyEC2'})
alice = CoseKey.from_dict(alice)
bob= EC2Key(crv='P_256', d=alice_private_key, optional_params={'KpAlg': 'EcdhEsHKDF256','KpKty': 'KtyEC2'})
print ("Message: ",mymsg)
print ("\nAlice Key: ",alice)
print ("\nBob Key: ",bob)
bob = CoseKey.from_dict(bob)
alicepub = deepcopy(alice)
del alicepub[EC2KpD]
shared = DirectKeyAgreement(
phdr = {Algorithm: EcdhEsHKDF256},
uhdr = {EphemeralKey: alicepub})
shared.key = alice
shared.local_attrs = {StaticKey: bob}
print ("\nShared: ",shared)

IVal = urandom(16)
msg = EncMessage(
phdr = {Algorithm: A128GCM},
uhdr = {IV: IVal},
payload = mymsg.encode(),
recipients = [shared])
encoded = msg.encode()
print ("IV: ",hexlify(IVal))
print ("Encoded message: ",hexlify(encoded))
decoded = CoseMessage.decode(encoded)
print ("\nDecoded: ",decoded)

print ("\nDecoded recipients",decoded.recipients)
static_receiver_key = CoseKey.from_dict(alice)
decoded.recipients[0].key = alice
print ("\nDecrypt: ",decoded.decrypt(decoded.recipients[0]))

And a sample run [here]:

Message:  hello
Alice Key:  <COSE_Key(EC2Key): {'EC2KpD': "b'\\xf6!\\xe5\\xbe@' ... (32 B)" , 'EC2KpY' : "b'\\xd2q\\xb3&\\xb4' ... (32 B)" , 'EC2KpX' : "b'*\\xa3\\xfc<\\xef' ... (32 B)" , 'EC2KpCurve' : 'P256' , 'KpKty' : 'KtyEC2' , 'KpAlg' : 'EcdhEsHKDF256' }>
Bob Key:  <COSE_Key(EC2Key): {'EC2KpD': "b'\\xf6!\\xe5\\xbe@' ... (32 B)" , 'EC2KpY' : "b'\\xd2q\\xb3&\\xb4' ... (32 B)" , 'EC2KpX' : "b'*\\xa3\\xfc<\\xef' ... (32 B)" , 'EC2KpCurve' : 'P256' , 'KpKty' : 'KtyEC2' , 'KpAlg' : 'EcdhEsHKDF256' }>
Shared:  <COSE_Recipient: [{'Algorithm': 'EcdhEsHKDF256' }, {'EphemeralKey': <COSE_Key(EC2Key): {'EC2KpY': "b'\\xd2q\\xb3&\\xb4' ... (32 B)" , 'EC2KpX' : "b'*\\xa3\\xfc<\\xef' ... (32 B)" , 'EC2KpCurve' : 'P256' , 'KpKty' : 'KtyEC2' , 'KpAlg' : 'EcdhEsHKDF256' }>
}, b'' ... (0 B), []]>
IV:  b'43a7411a331c666ccac6f90d19d0264d'
Encoded message: b'd8608443a10101a1055043a7411a331c666ccac6f90d19d0264d55845849350f99d62ffbf9d41f4086625d5db207bfcc818344a1013818a120a60102654b70416c676d456364684573484b4446323536654b704b7479664b747945433220012158202aa3fc3cef298f75a3141ef71764af90be81384701df3ac2203c6ce92d66853b225820d271b326b4dd4ad9afeeaccd01921d032a38c4b8b19a0d8fdf35c17fd428f14840'
Decoded:  <COSE_Encrypt: [{'Algorithm': 'A128GCM' }, {'IV': "b'C\\xa7A\\x1a3' ... (16 B)" }, b'\x84XI5\x0f' ... (21 B), [<COSE_Recipient: [{'Algorithm': 'EcdhEsHKDF256' }, {'EphemeralKey': <COSE_Key(EC2Key): {'EC2KpY': "b'\\xd2q\\xb3&\\xb4' ... (32 B)" , 'EC2KpX' : "b'*\\xa3\\xfc<\\xef' ... (32 B)" , 'EC2KpCurve' : 'P256' , 'KpKty' : 'KtyEC2' , 'KpAlg' : 'EcdhEsHKDF256' }>
}, b'' ... (0 B), []]>]]>
Decoded recipients [<COSE_Recipient: [{'Algorithm': 'EcdhEsHKDF256'}, {'EphemeralKey': <COSE_Key(EC2Key): {'EC2KpY': "b'\\xd2q\\xb3&\\xb4' ... (32 B)", 'EC2KpX': "b'*\\xa3\\xfc<\\xef' ... (32 B)", 'EC2KpCurve' : 'P256' , 'KpKty' : 'KtyEC2' , 'KpAlg' : 'EcdhEsHKDF256' }>
}, b'' ... (0 B), []]>]
Decrypt:  b'hello'

Here is the running code:

Conclusions

CBOR and COSE are cool and just right for creating trustworthy IoT infrastructures.

Subscribe: https://billatnapier.medium.com/membership

References

[1] Bormann, C., & Hoffman, P. (2020). RFC 8949 Concise Binary Object Representation (CBOR). [here].

[2] Schaad, J. (2017), RFC 8152 — CBOR Object Signing and Encryption (COSE), 2017. [here]

[3] Jones, M., Wahlstroem, E., Erdtman, S., & Tschofenig, H. (2018). Cbor web token (cwt). RFC 8392, Standards Track, IETF. [here]