README
¶
people can generae key pairs (public/private) use GOPROXY=direct go get -u github.com/Johnkhk/libsignal-go/protocol@latest
Signing and Verifying Messages
Signing
- A private key is used to sign a message, signature is generated and can be sent along with the message.
signature = sign(message, private_key)
- Other people using the public key can verify the signature.
hash = func(message, signature, public_key)
if hash != signature then the message has been tampered with.
e2ee to start a convo
Say Alice wants to talk to Bob:
-
Before hand everyone on the app has:
- Identity Key Pair: Generated when the user is created.
- Public part is uploaded to the server.
- Signed Pre-Key Pair: Generated when the user is created. & rotated every 30 days.
- Public part is signed by the private identity key. & uploaded to the server.
- Private part is stored on-device and used on conversation initiation.
- One-Time Pre-Key Pair: Generated on conversation start.
- Used once per session establishment (then discarded).
- Uploaded in batches (usually 100-200).
- Below a certain threshold, new keys are generated.
- Identity Key Pair: Generated when the user is created.
-
Alice wants to start a conversation with Bob:
- Alice fetches Bob's pre-key bundle from the server. (all 3 public keys)
- Alice verifies Signed Pre-Key provided by the server is valid by checking its signature against Bob’s Identity Public Key
- Alice generates an Ephemeral Key Pair
- Used during the initial key exchange for forward secrecy.
- Alice Derives the Shared Secret
- Elliptic Curve Diffie-Hellman (ECDH) exchanges are used to derive a shared secret
- ECDH 1: Between Alice's Ephemeral Key and Bob's Identity Public Key.
- ECDH 2: Between Alice's Ephemeral Key and Bob's Signed Pre-Key.
- ECDH 3: Between Alice's Identity Public Key and Bob's Signed Pre-Key.
- (Optional) ECDH 4: Between Alice's Ephemeral Key and Bob's One-Time Pre-Key.
- Alice Derives Root Key and Session Keys:
- Using the results of the ECDH exchanges, Alice derives a Root Key and an initial Chain Key using a Key Derivation Function (KDF).
- Root Key is the master key from which all other keys in the session are derived, it is updated when there is a new Diffie-Hellman (DH) ratchet step. DH ratchet step usually occurs when one sends the first message after receiving one from the other party
- Chain Key is derived from the Root Key using a Key Derivation Function (KDF) and used to derive Message Keys for encrypting and decrypting individual messages
- Message Keys: Each time a message is sent or received, a new Message Key is generated by applying the KDF to the Chain Key
- As Alice and Bob exchange messages, they ratchet forward the Chain Key and derive new Message Keys, thus providing forward secrecy (compromising a current key does not reveal past keys)
-
Alice sends an initial message to Bob:
- Alice Constructs a PreKeySignalMessage
- Includes: Her Identity Public Key, Ephemeral Public Key, and the Encrypted Message ("Hello Bob!").
- PreKeySignalMessage allows Bob to initiate the session on his end.
- Alice Constructs a PreKeySignalMessage
-
Bob Receives the Initial Message from Alice:
- Bob Performs the Same ECDH Exchanges
- Bob uses the same keys to derive the same shared secret.
- ECDH 1: Between Bob's Identity Private Key and Alice’s Ephemeral Key
- ECDH 2: Between Bob's Signed Pre-Key Private Key and Alice’s Ephemeral Key.
- ECDH 3: Between Bob's Signed Pre-Key Private Key and Alice’s Identity Public Key.
- (Optional) ECDH 4: Between Bob's One-Time Pre-Key and Alice’s Ephemeral Key. (if a One-Time Pre-Key was included)
- Bob uses the same keys to derive the same shared secret.
- Bob Derives the Same Root Key and Session Keys
- Bob Performs the Same ECDH Exchanges
-
Bob Decrypts the Initial Message from Alice:
- Bob Uses the Derived Message Key to Decrypt the Encrypted Message.
- Bob creates a local session state for Alice, storing:
- The Root Key, Chain Keys, Ratchet Keys (see above), and Message Keys
-
Ongoing Secure Communication:
- Sending Messages:
- Whenever Alice or Bob sends a message, they:
- Ratchet Forward: Generate a new Chain Key and derive a new Message Key using a KDF.
- Encrypt the message with the newly derived Message Key.
- Receiving Messages:
- When a message is received, the recipient uses the current Message Key to decrypt it.
- After decryption, the recipient ratchets forward to derive the next Chain Key and Message Key.
- Sending Messages:
Example of Ratcheting Forward in Practice Alice Sends a Message to Bob:
Alice uses her current Chain Key, CK_1, to derive a Message Key, MK_1. She uses MK_1 to encrypt the message. After sending the message, she uses a KDF to ratchet forward her Chain Key to CK_2. Bob Receives Alice's Message:
Bob uses his corresponding Chain Key, CK_1, to derive the same Message Key, MK_1. He uses MK_1 to decrypt the message. After decrypting, Bob ratchets his Chain Key forward to CK_2 to be in sync with Alice's state. Next Message:
For the next message, Alice and Bob repeat this process using their new Chain Key (CK_2), deriving new Message Keys (MK_2, MK_3, etc.), and continuously ratcheting the Chain Key forward after each message.
High-Level Overview of the Steps
-
Key Generation and Bundle Creation:
- Generate the necessary cryptographic keys for both users (Alice and Bob).
- Create key bundles for Alice and Bob, including their identity keys, signed pre-keys, and optional one-time pre-keys.
-
Protocol Store Setup:
- Set up protocol stores (e.g., session stores, identity key stores, pre-key stores) for both users. These stores are essential for managing the cryptographic state of each user, including sessions and key material.
-
Session Initialization:
- Establish sessions between Alice and Bob using the Signal Protocol.
- This involves processing pre-key bundles to generate shared session secrets and initialize the cryptographic state for secure communication.
-
Start Encrypted Message Exchange:
- Use the established sessions to encrypt and decrypt messages as they are exchanged between Alice and Bob.
- Handle message transmission and reception over the RPC streaming mechanism.
Detailed Steps for E2EE Communication Using Signal Protocol
Step 1: Key Generation and Bundle Creation
-
Generate Identity Keys:
- Each user (Alice and Bob) needs an identity key pair. This key pair is long-term and uniquely identifies the user.
- Example:
aliceIdentityKeyPair, err := curve.GenerateKeyPair(rand.Reader) bobIdentityKeyPair, err := curve.GenerateKeyPair(rand.Reader)
-
Generate Signed Pre-Keys:
- Each user generates a signed pre-key, which is used for medium-term communication. This key is signed by the user's identity key to ensure authenticity.
- Example:
aliceSignedPreKeyPair, err := curve.GenerateKeyPair(rand.Reader) bobSignedPreKeyPair, err := curve.GenerateKeyPair(rand.Reader) aliceSignedPreKeySignature, err := aliceIdentityKeyPair.PrivateKey().Sign(rand.Reader, aliceSignedPreKeyPair.PublicKey().Bytes()) bobSignedPreKeySignature, err := bobIdentityKeyPair.PrivateKey().Sign(rand.Reader, bobSignedPreKeyPair.PublicKey().Bytes())
-
Create Pre-Key Bundles:
- Each user creates a pre-key bundle to share with the other user. The bundle includes the signed pre-key, the optional one-time pre-key, and the user's identity key.
- Example:
alicePreKeyBundle := &prekey.Bundle{ RegistrationID: aliceIdentityKeyStore.LocalRegistrationID(ctx), DeviceID: 1, SignedPreKeyID: 22, SignedPreKeyPublic: aliceSignedPreKeyPair.PublicKey(), SignedPreKeySignature: aliceSignedPreKeySignature, IdentityKey: aliceIdentityKeyPair.IdentityKey(), }
Step 2: Protocol Store Setup
- Create In-Memory or Persistent Protocol Stores:
- Set up stores to manage session data, identity keys, pre-keys, etc., for both users.
- Use either in-memory stores or persistent stores depending on your application's requirements.
- Example:
aliceStore := testInMemProtocolStore(t, rand.Reader) bobStore := testInMemProtocolStore(t, rand.Reader)
Step 3: Session Initialization
-
Process Pre-Key Bundles:
- Alice and Bob process each other's pre-key bundles to initialize their sessions.
- Example for Alice processing Bob's pre-key bundle:
aliceSession := &session.Session{ RemoteAddress: bobAddress, SessionStore: aliceStore.SessionStore(), IdentityKeyStore: aliceStore.IdentityStore(), } err := aliceSession.ProcessPreKeyBundle(ctx, rand.Reader, bobPreKeyBundle) if err != nil { // Handle error }
-
Store Initialized Sessions:
- After processing the pre-key bundles, store the initialized session states for future use.
- This ensures that Alice and Bob have the correct session information to encrypt and decrypt messages.
Step 4: Start Encrypted Message Exchange
-
Send Encrypted Messages:
- Use the established sessions to encrypt messages before sending them over the network.
- Example:
encryptedMessage, err := aliceSession.EncryptMessage(ctx, []byte("Hello, Bob!")) if err != nil { // Handle error } // Send encryptedMessage over RPC
-
Receive and Decrypt Messages:
- When a message is received, use the session to decrypt it.
- Example:
decryptedMessage, err := bobSession.DecryptMessage(ctx, rand.Reader, encryptedMessage) if err != nil { // Handle error } // Process decryptedMessage
-
Handle Message Ordering and Replays:
- Ensure that the application logic handles message ordering, out-of-order messages, and replay attacks as needed.
- The Signal Protocol handles most of this internally, but your application should account for network conditions that could affect message delivery.
Summary of Steps
- Key Generation: Generate identity keys, signed pre-keys, and optional one-time pre-keys for both users.
- Store Setup: Create protocol stores for managing session states and key material.
- Session Initialization: Process pre-key bundles to establish secure sessions.
- Message Exchange: Use the established sessions to securely send and receive encrypted messages over RPC streaming.
Additional Considerations
- Session Management: Periodically refresh sessions and keys to maintain security. This can be done by generating new pre-key bundles and reinitializing sessions as needed.
- Error Handling: Implement robust error handling to manage scenarios where key exchange or message decryption fails.
- User Identity Verification: Ensure that each user's identity is verified properly during the initial key exchange to prevent impersonation attacks.
- Replay Protection: The Signal Protocol provides built-in replay protection, but your application should handle any edge cases related to message replay.
Conclusion
By following these steps, you can implement E2EE communication using the Signal Protocol in your chat app, leveraging RPC streaming for real-time message delivery between two users. The key steps involve generating keys, creating protocol stores, initializing sessions, and then securely exchanging messages.
Would you like more details on any specific part of this process, or is there something else you'd like to explore?