poly1305 #1

Merged
neemek merged 3 commits from poly1305 into main 2025-11-08 18:14:57 +00:00
5 changed files with 102 additions and 28 deletions

8
Cargo.lock generated
View file

@ -113,6 +113,7 @@ dependencies = [
"blake3",
"chacha",
"diffie-hellman",
"poly1305",
"rand",
]
@ -131,6 +132,13 @@ dependencies = [
"autocfg",
]
[[package]]
name = "poly1305"
version = "0.1.0"
dependencies = [
"crypto-bigint",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"

View file

@ -6,5 +6,6 @@ edition = "2024"
[dependencies]
diffie-hellman = { path = "../diffie-hellman", features = ["rand"] }
chacha = { path = "../chacha" }
poly1305 = { path = "../poly1305" }
rand = "0.9.2"
blake3 = "1.8.2"

View file

@ -3,10 +3,10 @@ i mek protokol (pls don't guillotine me, is for fun)
## bad things (*that i should really fix*)
- no verification of authenticity
- [ ] message/MAC ([poly1305](https://en.wikipedia.org/wiki/Poly1305)?)
- [x] message/MAC ([poly1305](https://en.wikipedia.org/wiki/Poly1305)?)
- [ ] server/client (static keys)
- protocol structure
- [ ] how to prevent nonce reuse/how to get shared nonce? (current version uses one nonce for the entire connection :P)
- [x] how to prevent nonce reuse/how to get shared nonce? (current version uses one nonce for the entire connection :P)
- pseudorandom function? (but how would you seed it??? shared key?)
- you would need to seed it with something else than *just* the shared key, or else the output would be completely predictable and an attacker could easily decrypt all your traffic if they manage to get the secret key (if they get the secret key, then your data is probably screwed anyways). however, if the nonce is ephemeral (not saved), and reasonably unpredictable for an attacker, then that would mean they would have to search up to $2^{96}$ blocks in order to get the data. its *nothing* compared to the secret key ($2^{256}$), and it is *not* where the security of the protocol or the chacha stream cipher should be derived.
- seeding it with the mac or using the hash of the previous message(s) would mean that if they were to replay or repeat the same message blocks, then an attacker would be able to see the same pattern of messages. it would also make it vulnerable to replay attacks, where an attacker could replay a message to the server, given that they somehow know the same nonce is reused.
@ -23,4 +23,5 @@ i mek protokol (pls don't guillotine me, is for fun)
## things that would be sick but also idk if i have the time or willpower before my attention gets taken by other shit i really want to do like implementing a post-quantum secure KEM (maybe KYBER..)
- [ ] custom blake3 implementation (~ *IM NOT A BIG FAN OF THE GOVERNMENT* ~)
- [ ] custom poly1305 implementation (it would be fine to use one from `crates.io`, but it would *be so awesome, it would be so cool* if i made it myself :D + fun)
- [x] custom poly1305 implementation (it would be fine to use one from `crates.io`, but it would *be so awesome, it would be so cool* if i made it myself :D + fun)
- that was actually surprisingly simple

View file

@ -1,5 +1,7 @@
use crate::EncryptionError::IO;
use chacha::Block;
use diffie_hellman::{Public, Secret};
use poly1305::oneoff_authenticate;
use rand::{RngCore, rng};
use std::io::{Error, Read, Write};
use std::net::{Shutdown, TcpStream};
@ -17,7 +19,14 @@ pub struct EncryptedStream {
block: Block,
}
const KEY_DERIVATION_CONTEXT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
const KEY_DERIVATION_CONTEXT: &str =
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
#[derive(Debug)]
pub enum EncryptionError {
InvalidMAC,
IO(Error),
}
impl EncryptedStream {
/// connect and negotiate an encrypted stream as a client
@ -36,24 +45,19 @@ impl EncryptedStream {
let shared_secret = secret.diffie_hellman(&their_public);
let mut nonce_bytes = [0u8; 12];
socket.read_exact(&mut nonce_bytes)?;
let key =
shared_secret.derive_key(|bytes| blake3::derive_key(KEY_DERIVATION_CONTEXT, bytes));
let key = shared_secret.derive_key(|bytes| blake3::derive_key(KEY_DERIVATION_CONTEXT, &bytes));
Ok(EncryptedStream::wrap(socket, key, nonce_bytes))
Ok(EncryptedStream::wrap(socket, key))
}
pub fn wrap(socket: TcpStream, key: [u8; 32], nonce: [u8; 12]) -> EncryptedStream {
let mut nonce_u32s = [0u32; 3];
u8_to_u32(&mut nonce_u32s, &nonce);
pub fn wrap(socket: TcpStream, key: [u8; 32]) -> EncryptedStream {
let mut key_u32s = [0u32; 8];
u8_to_u32(&mut key_u32s, &key);
EncryptedStream {
socket,
block: Block::new(key_u32s, 0, nonce_u32s, 20),
block: Block::new(key_u32s, 0, [0u32; 3], 20),
}
}
@ -64,6 +68,70 @@ impl EncryptedStream {
pub fn reset(&mut self, next_nonce: [u32; 3]) {
self.block.reset(next_nonce)
}
pub fn read_packet(&mut self) -> Result<Vec<u8>, EncryptionError> {
// the first bytes should be the nonce
let mut nonce_bytes = [0u8; 12];
if let Err(err) = self.socket.read(&mut nonce_bytes) {
return Err(IO(err));
}
let mut nonce = [0u32; 3];
u8_to_u32(&mut nonce, &nonce_bytes);
self.block.reset(nonce);
let poly_key = self.block.get_bytes();
self.block.advance();
let mut mac = [0u8; 16];
if let Err(err) = self.socket.read(&mut mac) {
return Err(IO(err));
}
let mut len_bytes = [0u8; 4];
if let Err(err) = self.socket.read(&mut len_bytes) {
return Err(IO(err));
}
let len = u32::from_le_bytes(len_bytes);
let mut buf = vec![0u8; len as usize];
if let Err(err) = self.read(&mut buf) {
return Err(IO(err));
}
let my_mac = oneoff_authenticate(&buf, &poly_key[0..32].try_into().unwrap());
if mac != my_mac {
return Err(EncryptionError::InvalidMAC);
}
Ok(buf)
}
pub fn write_packet(&mut self, buf: &[u8]) -> Result<(), Error> {
let mut nonce_bytes = [0u8; 12];
rng().fill_bytes(&mut nonce_bytes);
let mut nonce = [0u32; 3];
u8_to_u32(&mut nonce, &nonce_bytes);
self.block.reset(nonce);
let poly_key: [u8; 32] = self.block.get_bytes()[0..32].try_into().unwrap();
self.block.advance();
let mac = oneoff_authenticate(&buf, &poly_key);
self.socket.write(&nonce_bytes)?;
self.socket.write(&mac)?;
self.socket.write(&(buf.len() as u32).to_le_bytes())?;
self.write(&buf)?;
Ok(())
}
}
impl TryFrom<TcpStream> for EncryptedStream {
@ -83,14 +151,10 @@ impl TryFrom<TcpStream> for EncryptedStream {
let shared_secret = secret.diffie_hellman(&their_public);
let mut nonce_bytes = [0u8; 12];
rng().fill_bytes(&mut nonce_bytes);
let key =
shared_secret.derive_key(|bytes| blake3::derive_key(KEY_DERIVATION_CONTEXT, bytes));
socket.write(&nonce_bytes)?;
let key = shared_secret.derive_key(|bytes| blake3::derive_key(KEY_DERIVATION_CONTEXT, &bytes));
Ok(EncryptedStream::wrap(socket, key, nonce_bytes))
Ok(EncryptedStream::wrap(socket, key))
}
}

View file

@ -1,6 +1,6 @@
use hermes::EncryptedStream;
use std::env::args;
use std::io::{Read, Write};
use std::io::Write;
use std::net::{Shutdown, TcpListener};
use std::process::exit;
@ -22,7 +22,9 @@ fn main() {
println!("successfully encrypted stream");
encrypted
.write(b"the fitness gram pacer test is a multi-stage aerobic fitness test...")
.write_packet(
b"the fitness gram pacer test is a multi-stage aerobic fitness test...",
)
.unwrap();
println!("successfully wrote data");
@ -32,13 +34,11 @@ fn main() {
}
}
"c" => {
let mut client = EncryptedStream::connect("127.0.0.1:2007").unwrap();
println!("successfully connected client");
let mut client = EncryptedStream::connect("127.0.0.1:2007").expect("Failed to connect");
let mut buf = Vec::new();
client.read_to_end(&mut buf).unwrap();
println!("successfully read data");
let buf = client.read_packet().unwrap();
println!("success!");
println!("data: {}", String::from_utf8_lossy(&buf));
}
_ => {