Safenet: Part Three

Posted: Feburary 2024

Introduction

Safenet has been reworked. I have changed the internal code structure to allow easier addition of other encryption protocols. As of now, there are three encryption protocols that are supported out of the box.

SigningKey NegotiationHashing (KDF)Symmetrical Encryption
LegacyECDSAECDHBlake2bChaCha20Poly1305
KyberKyberKyberBlake2bChaCha20Poly1305
Kyber-DithDilithiumKyberBlake2bChaCha20Poly1305

CRYSTALS-Kyber & CRYSTALS-Dilithium

CRYSTALS stands for Cryptographic Suite for Algebraic Lattices. It is a group of quantum-resilient algorithms.

Kyber is a key negotiation algorithm, like ECDH. Dilithium is a digital verification (sigining) algorithm, like ECDSA. Unlike ECDSA and ECDH, Kyber and Dilithium are quantum-reslient, making them future-proof.

Kyber supports MAKE (Mutually Authenticated Key Exchange), which defeats the purpose of a signing algorithm. However, this requires multiple trips back and forth between clients. You would not be able to perform this exchange within one HTTP POST. If you're using a different network protocol, this might not matter. The Safenet Chat demo uses websockets, so it is able to use Kyber MAKE without much hassle.

This is why I also added a Kyber-Dilithium hybrid. It does not use the MAKE provided by Kyber and uses Dilithium as a signing algorithm. It can perform a key exchange within one HTTP POST. The only drawback is the payload size. It is around 10kb each way (that's the cost of PQE). The Legacy protocol only requires ~450 bytes.

API

In order to communicate using DataFrames, you must complete a round of pairing with a client. Ultimately, it's up to you but it depends on the network protocol you are using.

HTTP

// Client let init_frame = InitFrame::default(); let response = post("http://<enter address here>/conn/init", init_frame.to_bytes()); // Use whatever HTTP client library/crate to send a POST request init_frame.from_peer(response.bytes());
// Server #[post("/conn/init")] fn conn_init(request_body: Vec<u8>) -> Vec<u8> { let init_frame = InitFrame::default() // Defaults to Legacy encryption for now, most likely will change init_frame.from_peer(request_body) // .from_peer() returns Vec<u8> }

As I said, you can use whatever network protcol you want. In my demo, I use websockets. You will have to handle the InitFrames accordingly.

Another change I have made to the API, is the initialization of the App State. All client/server keys are saved in the aforementioned App State. The server keys used to be generated everytime at runtime, now you can save/load keys for reuse.

use safenet::app_state::AppState; // Generate keys fn main() { AppState::init().unwrap(); // Generates server keys, fails if AppState is already init'd let bytes = AppState::priv_key_to_bytes(); // Returns server keys in bytes } // Reuse previously generated keys fn main() { let bytes = vec![]; // Obviously don't use an empty vector. AppState::init_with_priv_key(bytes); // Fails if AppState is already init'd }

made by ~~> Mateo