bassoon/contrabassoon and DJ gear Ride a lot of trains (Rails!) (travelled on 99% of JR) Build keyboards Organized RubyKaigi 2025 as a Local Organizer if anything catches your interest let's talk! 5
talk: Freelance web developer focused on Digital Identity and Security Worked/ing on writing/editing and implementing standards HTTPS in Local Network CG / Web of Things WG @ W3C, OAuth / Messaging Layer Security WG @ IETF Worked as an Officer of Internet Society Japan Chapter (2020-23) 6
misuse Operational Security is also very difficult I've done my research, but I don't consider myself a cryptography expert a.k.a. "I am not djb" If you're not sure, please have your system audited by a security expert before going to production also I don't have a PhD/Master's degree in this field so yeah... 7
progress Passes most test vectors Lacks validation on error cases Client implementation is underway Hopefully release 1.0 by the next IETF (2025/11)...? I was aiming for 1.0 before IETF 126 (2025/7) but yeah... 8
affairs Powerful people wanting to control the Internet even in democratic countries! Provides a safe method to communicate under oppression / in war zones 15
by authorities even in democratic countries! Even if you ban E2EE, criminals will use it anyways Banning E2EE disproportionately harms vulnerable people 16
provides key exchange Protocol (RFC 9420), Architecture (RFC 9750) Today's topic is the implementation of the Protocol Now we (kinda) have RFC 9420 in Ruby! for MLS Architecture getting an RFC number 20
for both encryption and decryption Asymmetric crypto: Also known as Public Key Cryptography Alice keeps a pair of keys, public and private Bob can encrypt a plaintext using Alice's Public Key Then Alice decrypts the ciphertext with her Private Key 26
Key" , formalized A combination of the following: Key Encapsulation Mechanism (KEM) ( ≒ asymmetric crypto) Key Derivation Function (KDF) ( ≒ hash) Authenticated Encryption with Associated Data (AEAD) (= symmetric crypto) Available in Ruby: hpke gem Seen this? I've talked about this at RubyConf Taiwan 2023 27
group member was compromised at some point in the past Each member updates their key so that group secrets are not always encrypted with private keys that have been compromised RFC 9420, Section 16.6 29
key 0 chain_key[0] = "some common secret" # when sending a message... message_key[n] = hmac_sha256(chain_key[n], 0x02) # encrypt the message #(n) using message_key[n] chain_key[n+1] = hmac_sha256(chain_key[n], 0x01) Symmetric Key Ratchet part of the Double Ratchet algorithm 31
... } unless Melos::Tree.leaf?(index) left_secret = Melos::Crypto.expand_with_label(suite, secret, "tree", "left", suite.kdf.n_h) right_secret = Melos::Crypto.expand_with_label(suite, secret, "tree", "right", suite.kdf.n_h) populate_tree_impl(suite, tree, Melos::Tree.left(index), left_secret) populate_tree_impl(suite, tree, Melos::Tree.right(index), right_secret) end end From the base encryption secret, we recursively populate the tree down to its leaves file lib/melos/secret_tree.rb 41
Crypto.derive_tree_secret(ratchet_secret[n], "key", n) nonce = Crypto.derive_tree_secret(ratchet_secret[n], "nonce", n) ratchet_secret[n + 1] = Crypto.derive_tree_secret(ratchet_secret[n], "secret", n) This gives you the key and nonce to encrypt the actual messages. Compare with this (two-party hash ratcheting): chain_key[0] = "some common secret" message_key[n] = hmac_sha256(chain_key[n], 0x02) chain_key[n+1] = hmac_sha256(chain_key[n], 0x01) 43
parent's path_secret is calculated using the child's path_secret Calculate that up to the root The commit_secret is calculated from the root's path_secret 48
copath nodes along the UpdatePath (green) If all ParentNode s in the tree are populated, we encrypt the path secret to the key of its copath node Public keys for parent nodes are derived from path_secret s 50
We calculate the resolution of the copath node to figure out which node's keys are available. Then, we encrypt the path secret of the UpdatePath node to each key 52
based on epoch_secret Key Schedule gives you the next epoch_secret based on current epoch_secret and commit_secret TreeKEM gives you the commit_secret 57
called proposals and commits Proposals Add and remove users Notifies an update of user's leaf key Injects Pre-Shared Keys Commits will fix those information and advances the group's epoch UpdatePath s are conveyed in the Commit 59
a user's LeafNode into a 0-node group Starts with a random epoch_secret def init_group(node) # single node, a leaf node containing an HPKE PK and credential for the creator @ratchet_tree = [node] @leaf_index = 0 @tree_hash = Melos::Struct::RatchetTree.root_tree_hash(@cipher_suite, @ratchet_tree) @confirmed_transcript_hash = '' @epoch_secret = SecureRandom.random_bytes(@cipher_suite.kdf.n_h) ... @group_initialized = true end lib/group.rb from a work-in-progress version 60
group publishes their identity (including its public key) to a Directory using a KeyPackage They will receive a Welcome message that has the group state inside it Figure 2 in RFC 9420 Section 3.2. The Delivery Service is out of scope for this implementation 61
To add User B , A sends an Add proposal adding B , then Commit s A sends a Welcome message to introduce B into the group Same for adding C Figure 3 in RFC 9420 Section 3.2. 62
notifying the group of its leaf key update Then User A Commit s that update to include it in the epoch advancement Figure 4 in RFC 9420 Section 3.2. 64
node of the sender with the leaf_node inside the Update proposal Then blank all nodes on its direct path up to the root Updating the keys regularly gives the Group Post-Compromise Security 65
node in the proposal Then blank all nodes on its direct path up to the root If the right half of the tree is fully empty, the tree is shrunk 3 becomes the new root 67
During the processing of a Commit When processing a Commit , the UpdatePath inside the Commit is merged into the ratchet tree ParentNode s are created based on the UpdatePath content 68
Hashes Hashes that summarize the proposals/commits taken place in the last epoch Signing/Verification of messages There are public messages and private messages 69
Parsing messages now use a StringIO instead of two String s Moved group state into a Group object Needs a user-friendly client API Needs interoperability testing 72
worked on RatchetTree is now represented as a raw array Interoperability testing Learn other implementations and create test scenarios There is an automated interop suite that uses gRPC :contribute-chance: is a Slack emoji in #ruby-jp used for indicating good first issues 75
Protocols need the whole cipher suite Not only encap/decap and open/seal Also want access to constants such as hash/key length OpenSSL's HPKE context remembers which algorithm to use, but to use them separately you have to call them separately 78
expand_with_label(suite, sender_data_secret, "key", ciphertext_sample, suite.hpke.n_k) end def self.sender_data_nonce(suite, sender_data_secret, ciphertext) ciphertext_sample = ciphertext[0..(suite.kdf.n_h - 1)] expand_with_label(suite, sender_data_secret, "nonce", ciphertext_sample, suite.hpke.n_n) end n_h is hash length, n_k and n_n is the key and nonce length for the symmetric crypto 79
pairs def self.signature_key_pair_corresponds?(suite, private_key, public_key) private_pkey = suite.pkey.deserialize_private_signature_key(private_key) public_pkey = suite.pkey.deserialize_public_signature_key(public_key) if suite.pkey.equal?(Melos::Crypto::CipherSuite::X25519) || suite.pkey.equal?(Melos::Crypto::CipherSuite::X448) # is an Edwards curve; check equality of the raw public key private_pkey.raw_public_key == public_pkey.raw_public_key else # is an EC; check equality of the public key Point private_pkey.public_key == public_pkey.public_key end end 80
in UncompressedPointRepresentation form def self.derive_key_pair(suite, secret) pkey = suite.hpke.kem.derive_key_pair(secret) if suite.pkey.equal?(Melos::Crypto::CipherSuite::X25519) || suite.pkey.equal?(Melos::Crypto::CipherSuite::X448) # is an Edwards curve [pkey.raw_private_key, pkey.raw_public_key] else # is an EC [pkey.private_key.to_s(2), pkey.public_key.to_bn.to_s(2)] end end 81
libraries Desktop: OpenSSL Compatible (to an extent) with LibreSSL, BoringSSL, and the like Browser: Web Crypto API Embedded: Mbed TLS, wolfSSL, ... We have Mbed TLS support in PicoRuby Can we have a unified API wrapper for these libraries? Actually ruby-wasm has OpenSSL compiled in wasm, but doing it through browser APIs is faster 85
Crypto API's random number generator require "js" array = JS::eval('return new Uint8Array(16)') JS.global[:window][:crypto].getRandomValues(array) p array 86
StructWithVectors; Based on variable-length integer encoding in RFC 9000, Section 16 2-bit prefix if 00 , length is encoded with 6 bits if 01 , length is encoded with 14 bits if 11 , length is encoded with 30 bits 98
type when :uint8 value = buf.byteslice(0, 1).unpack1('C') buf = buf.byteslice(1..) when :uint16 value = buf.byteslice(0, 2).unpack1('S>') buf = buf.byteslice(2..) (snip) when :vec value, buf = Melos::Vec.parse_vec(buf) (snip) lib/melos/struct/base.rb; I'm likely rewriting this with a StringIO instead of using strings as buffers 102
that def deserialize_select_elem_with_context( buf, context, predicate, type, type_param) if predicate.(context) deserialize_elem(buf, type, type_param) else [nil, buf] end end lib/melos/struct/base.rb 106