Repo of no-std crates for my personal embedded projects

Sachy's crypto scheme lmao #13

merged opened by sachy.dev targeting main from sachy-crypto
Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:usjm3ynnir6y4inkcdovrfei/sh.tangled.repo.pull/3mhklndgukc22
+76 -65
Interdiff #10 #11
Cargo.lock

This file has not been changed.

Cargo.toml

This file has not been changed.

sachy-crypto/Cargo.toml

This file has not been changed.

sachy-crypto/README.md

This file has not been changed.

+76 -65
sachy-crypto/src/lib.rs
··· 3 3 use core::ops::AddAssign; 4 4 5 5 use chacha20poly1305::{ 6 - AeadCore, AeadInOut, ChaCha20Poly1305, KeyInit, 7 - aead::{self, Buffer, array::Array, common::array::typenum::Unsigned}, 6 + AeadInOut, ChaCha20Poly1305, KeyInit, 7 + aead::{self, Buffer}, 8 8 }; 9 9 use dhkem::{ 10 - Encapsulate, Generate, Kem, Secp256k1DecapsulationKey, Secp256k1EncapsulationKey, Secp256k1Kem, 10 + Encapsulate, Kem, Secp256k1DecapsulationKey, Secp256k1EncapsulationKey, Secp256k1Kem, 11 11 TryDecapsulate, 12 12 kem::{Ciphertext, SharedKey}, 13 13 }; ··· 41 41 42 42 pub struct EncapsulatedPublicKey(Secp256k1EncapsulationKey); 43 43 44 + #[derive(Debug, PartialEq, Eq, Clone, Copy)] 45 + pub enum Role { 46 + Client, 47 + Server, 48 + } 49 + 44 50 impl EncapsulatedPublicKey { 45 51 pub fn serialize(&self) -> Sec1Point { 46 52 self.0.to_sec1_point(true) ··· 70 76 .try_decapsulate_slice(ciphertext) 71 77 .map_err(|_| ProtoError)?; 72 78 73 - TransportState::init(psk, shared) 79 + TransportState::init(psk, shared, Role::Client) 74 80 } 75 81 } 76 82 ··· 86 92 } 87 93 88 94 pub fn finish(self, psk: &[u8; 32]) -> Result<TransportState, ProtoError> { 89 - TransportState::init(psk, self.0) 95 + TransportState::init(psk, self.0, Role::Server) 90 96 } 91 97 } 92 98 ··· 104 110 /// Value to use when incrementing the Transport counter (i.e. one) 105 111 const COUNTER_INCR: Self::Counter; 106 112 113 + /// Maximum number of messages allowed to be sent via Transport 114 + const COUNTER_MAX: Self::Counter; 115 + 107 116 /// Encrypt an AEAD message in-place at the given position in the Transport. 108 117 fn encrypt_in_place( 109 118 &self, ··· 130 139 pub fn encrypt(&mut self, msg: &mut alloc::vec::Vec<u8>) -> Result<(), ProtoError> { 131 140 let counter = self.counter.to_be_bytes(); 132 141 133 - // Nonce is randomised to act as a OTP when mixed with the counter state 134 - let mut epstein = Array::generate(); 135 - 136 - self.transport.encrypt_in_place(&epstein, &counter, msg)?; 137 - 138 - msg.extend(Self::mix_nonce(&mut epstein, &counter)); 142 + self.transport.encrypt_in_place( 143 + &self.transport.mix_nonce(&counter, true), 144 + &counter, 145 + msg, 146 + )?; 139 147 140 148 self.counter = self.counter.wrapping_add(TransportState::COUNTER_INCR); 141 149 142 150 // If we wrapped around and equal the finish value, we have maxed out the amount of 143 151 // messages we can send. 144 - if self.counter.ct_eq(&self.transport.finish).into() { 152 + if self.counter.ct_eq(&TransportState::COUNTER_MAX).into() { 145 153 Err(ProtoError) 146 154 } else { 147 155 Ok(()) 148 156 } 149 157 } 150 - 151 - fn mix_nonce<'a>( 152 - epstein: &'a mut aead::Nonce<ChaCha20Poly1305>, 153 - position: &'a [u8; 8], 154 - ) -> &'a aead::Nonce<ChaCha20Poly1305> { 155 - epstein[..position.len()] 156 - .iter_mut() 157 - .zip(position) 158 - .for_each(|(byte, count)| *byte ^= *count); 159 - 160 - epstein 161 - } 162 158 } 163 159 164 160 pub struct ReceivingState<'a> { ··· 170 166 pub fn decrypt(&mut self, msg: &mut alloc::vec::Vec<u8>) -> Result<(), ProtoError> { 171 167 let counter = self.counter.to_be_bytes(); 172 168 173 - // Extract the nonce from the payload as this does not need to be decrypted 174 - let epstein = Self::extract_nonce(&counter, msg)?; 175 - 176 - self.transport.decrypt_in_place(&epstein, &counter, msg)?; 169 + self.transport.decrypt_in_place( 170 + &self.transport.mix_nonce(&counter, false), 171 + &counter, 172 + msg, 173 + )?; 177 174 178 175 self.counter = self.counter.wrapping_add(TransportState::COUNTER_INCR); 179 176 180 177 // If we wrapped around and equal the finish value, we have maxed out the amount of 181 178 // messages we can send. 182 - if self.counter.ct_eq(&self.transport.finish).into() { 179 + if self.counter.ct_eq(&TransportState::COUNTER_MAX).into() { 183 180 Err(ProtoError) 184 181 } else { 185 182 Ok(()) 186 183 } 187 184 } 188 - 189 - fn extract_nonce( 190 - position: &[u8; 8], 191 - msg: &mut alloc::vec::Vec<u8>, 192 - ) -> Result<aead::Nonce<ChaCha20Poly1305>, ProtoError> { 193 - let index = msg 194 - .len() 195 - .checked_sub(<ChaCha20Poly1305 as AeadCore>::NonceSize::to_usize()) 196 - .ok_or(ProtoError)?; 197 - 198 - let mut epstein = Array::try_from(&msg[index..]).map_err(|_| ProtoError)?; 199 - 200 - epstein[..position.len()] 201 - .iter_mut() 202 - .zip(position) 203 - .for_each(|(keyed, count)| { 204 - *keyed ^= *count; 205 - }); 206 - 207 - msg.truncate(index); 208 - 209 - Ok(epstein) 210 - } 211 185 } 212 186 213 187 impl TransportPrimitive<ChaCha20Poly1305> for TransportState { ··· 215 189 216 190 const COUNTER_INCR: Self::Counter = 1; 217 191 192 + const COUNTER_MAX: Self::Counter = u64::MAX; 193 + 218 194 fn encrypt_in_place( 219 195 &self, 220 196 epstein: &aead::Nonce<ChaCha20Poly1305>, ··· 238 214 } 239 215 } 240 216 217 + #[repr(align(4))] 241 218 pub struct TransportState { 242 219 aead: ChaCha20Poly1305, 243 - finish: u64, 220 + client: aead::Nonce<ChaCha20Poly1305>, 221 + server: aead::Nonce<ChaCha20Poly1305>, 222 + role: Role, 244 223 } 245 224 246 225 impl TransportState { 247 - pub fn init(psk: &[u8; 32], shared: impl Into<SharedSecret>) -> Result<Self, ProtoError> { 226 + pub fn init( 227 + psk: &[u8; 32], 228 + shared: impl Into<SharedSecret>, 229 + role: Role, 230 + ) -> Result<Self, ProtoError> { 248 231 let noncer = shared.into(); 249 232 let kdf = noncer.extract::<sha2::Sha256>(Some(psk)); 250 233 251 234 let mut key = [0u8; 32]; 252 235 253 - let mut finish = [0u8; 8]; 236 + let mut client = aead::Nonce::<ChaCha20Poly1305>::default(); 237 + let mut server = aead::Nonce::<ChaCha20Poly1305>::default(); 254 238 255 239 kdf.expand(b"SachY-Crypt0", &mut key) 256 240 .map_err(|_| ProtoError)?; 257 241 258 - kdf.expand(b"PickANumber", &mut finish) 259 - .map_err(|_| ProtoError)?; 242 + kdf.expand(b"Client", &mut client).map_err(|_| ProtoError)?; 243 + kdf.expand(b"Server", &mut server).map_err(|_| ProtoError)?; 260 244 261 245 Ok(Self { 262 246 aead: ChaCha20Poly1305::new(&key.into()), 263 - finish: u64::from_le_bytes(finish), 247 + client, 248 + server, 249 + role, 264 250 }) 265 251 } 266 252 267 253 pub fn split(&self) -> (SendingState<'_>, ReceivingState<'_>) { 268 - let counter = self.finish; 269 - 270 254 ( 271 255 SendingState { 272 256 transport: self, 273 - counter, 257 + counter: 0, 274 258 }, 275 259 ReceivingState { 276 260 transport: self, 277 - counter, 261 + counter: 0, 278 262 }, 279 263 ) 280 264 } 265 + 266 + fn mix_nonce(&self, position: &[u8; 8], send: bool) -> aead::Nonce<ChaCha20Poly1305> { 267 + let mut trump = aead::Nonce::<ChaCha20Poly1305>::default(); 268 + 269 + let epstein = match (self.role, send) { 270 + (Role::Client, true) | (Role::Server, false) => &self.client, 271 + (Role::Server, true) | (Role::Client, false) => &self.server, 272 + }; 273 + 274 + let (head, tail) = trump.split_at_mut(position.len()); 275 + let (first, second) = epstein.split_at(position.len()); 276 + 277 + head.iter_mut() 278 + .zip(first) 279 + .zip(position) 280 + .for_each(|((head, ep), pos)| *head = ep ^ pos); 281 + tail.iter_mut() 282 + .zip(second) 283 + .for_each(|(tail, ep)| *tail = *ep); 284 + 285 + trump 286 + } 281 287 } 282 288 283 289 #[cfg(test)] 284 290 mod tests { 285 291 use alloc::vec; 292 + use dhkem::Generate; 293 + use elliptic_curve::array::Array; 286 294 287 295 use super::*; 288 296 ··· 336 344 132, 45, 174, 183, 65, 89, 73, 107, 177, 77, 90, 164, 251, 337 345 ]; 338 346 339 - let alice = TransportState::init(&psk, Array(shared_secret))?; 340 - let bob = TransportState::init(&psk, Array(shared_secret))?; 347 + let alice = TransportState::init(&psk, Array(shared_secret), Role::Client)?; 348 + let bob = TransportState::init(&psk, Array(shared_secret), Role::Server)?; 341 349 342 350 let (mut alice_send, mut alice_recv) = alice.split(); 343 351 let (mut bob_send, mut bob_recv) = bob.split(); ··· 345 353 // Have to be synchronised on both ends, so the counter state matches between the two 346 354 // and thus messages can be encrypted/decrypted statefully. But the actual number is 347 355 // "random", making it harder to guess the position state. 348 - assert_eq!(alice.finish, bob.finish); 356 + assert_eq!(alice.client, bob.client); 357 + assert_eq!(alice.server, bob.server); 358 + assert_ne!(alice.client, alice.server); 359 + assert_ne!(bob.client, bob.server); 349 360 350 361 let orig = b"Test Message, Please ignore."; 351 362

History

18 rounds 0 comments
sign up or login to add to the discussion
1 commit
expand
Sachy's crypto scheme lmao
2/2 success
expand
expand 0 comments
pull request successfully merged
1 commit
expand
Sachy's crypto scheme lmao
1/2 failed, 1/2 success
expand
expand 0 comments
1 commit
expand
Sachy's crypto scheme lmao
2/2 success
expand
expand 0 comments
1 commit
expand
Sachy's crypto scheme lmao
2/2 success
expand
expand 0 comments
1 commit
expand
Sachy's crypto scheme lmao
2/2 success
expand
expand 0 comments
1 commit
expand
Sachy's crypto scheme lmao
1/2 failed, 1/2 success
expand
expand 0 comments
sachy.dev submitted #11
1 commit
expand
Sachy's crypto scheme lmao
2/2 success
expand
expand 0 comments
1 commit
expand
Sachy's crypto scheme lmao
2/2 success
expand
expand 0 comments
1 commit
expand
Sachy's crypto scheme lmao
2/2 success
expand
expand 0 comments
1 commit
expand
Sachy's crypto scheme lmao
2/2 success
expand
expand 0 comments
1 commit
expand
Sachy's crypto scheme lmao
2/2 success
expand
expand 0 comments
1 commit
expand
Sachy's crypto scheme lmao
2/2 failed
expand
expand 0 comments
1 commit
expand
Sachy's crypto scheme lmao
2/2 success
expand
expand 0 comments
1 commit
expand
Sachy's crypto scheme lmao
2/2 success
expand
expand 0 comments
1 commit
expand
Sachy's crypto scheme lmao
2/2 success
expand
expand 0 comments
1 commit
expand
Sachy's crypto scheme lmao
2/2 success
expand
expand 0 comments
1 commit
expand
Sachy's crypto scheme lmao
2/2 success
expand
expand 0 comments
sachy.dev submitted #0
1 commit
expand
Sachy's crypto scheme lmao
2/2 success
expand
expand 0 comments