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
+45 -51
Interdiff #8 #9
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.

+45 -51
sachy-crypto/src/lib.rs
··· 1 1 #![no_std] 2 2 3 - use core::ops::{AddAssign, Sub}; 3 + use core::ops::AddAssign; 4 4 5 5 use chacha20poly1305::{ 6 6 AeadCore, AeadInOut, ChaCha20Poly1305, KeyInit, 7 - aead::{ 8 - self, Buffer, 9 - array::{Array, ArraySize}, 10 - common::array::typenum::Unsigned, 11 - }, 12 - consts::U8, 7 + aead::{self, Buffer, array::Array, common::array::typenum::Unsigned}, 13 8 }; 14 9 use dhkem::{ 15 10 Encapsulate, Generate, Kem, Secp256k1DecapsulationKey, Secp256k1EncapsulationKey, Secp256k1Kem, ··· 95 90 } 96 91 } 97 92 98 - /// Nonce as used by a given AEAD construction and STREAM primitive. 99 - pub type Nonce<A, S> = chacha20poly1305::aead::array::Array<u8, NonceSize<A, S>>; 100 - 101 - /// Size of a nonce as used by a STREAM construction, sans the overhead of 102 - /// the STREAM protocol itself. 103 - pub type NonceSize<A, S> = 104 - <<A as AeadCore>::NonceSize as Sub<<S as TransportPrimitive<A>>::NonceOverhead>>::Output; 105 - 106 - /// Low-level STREAM implementation. 93 + /// Low-level Transport implementation. 107 94 /// 108 - /// This trait provides a particular "flavor" of STREAM, as there are 95 + /// This trait provides a particular "flavor" of transport, as there are 109 96 /// different ways the specifics of the construction can be implemented. 110 - /// 111 - /// Deliberately immutable and stateless to permit parallel operation. 112 97 pub trait TransportPrimitive<A> 113 98 where 114 99 A: AeadInOut, 115 - A::NonceSize: Sub<Self::NonceOverhead>, 116 - NonceSize<A, Self>: ArraySize, 117 100 { 118 - /// Number of bytes this STREAM primitive requires from the nonce. 119 - type NonceOverhead: ArraySize; 120 - 121 - /// Type used as the STREAM counter. 101 + /// Type used as the Trasnport counter. 122 102 type Counter: AddAssign + Copy + Default + Eq; 123 103 124 - /// Value to use when incrementing the STREAM counter (i.e. one) 104 + /// Value to use when incrementing the Transport counter (i.e. one) 125 105 const COUNTER_INCR: Self::Counter; 126 106 127 - /// Maximum value of the STREAM counter. 128 - const COUNTER_MAX: Self::Counter; 129 - 130 - /// Encrypt an AEAD message in-place at the given position in the STREAM. 107 + /// Encrypt an AEAD message in-place at the given position in the Transport. 131 108 fn encrypt_in_place( 132 109 &self, 133 110 nonce: &aead::Nonce<A>, ··· 135 112 buffer: &mut dyn Buffer, 136 113 ) -> Result<(), ProtoError>; 137 114 138 - /// Decrypt an AEAD message in-place at the given position in the STREAM. 115 + /// Decrypt an AEAD message in-place at the given position in the Transport. 139 116 fn decrypt_in_place( 140 117 &self, 141 118 nonce: &aead::Nonce<A>, ··· 144 121 ) -> Result<(), ProtoError>; 145 122 } 146 123 147 - pub struct TransportState { 148 - aead: ChaCha20Poly1305, 149 - } 150 - 151 124 pub struct SendingState<'a> { 152 125 transport: &'a TransportState, 153 126 counter: u64, ··· 157 130 pub fn encrypt(&mut self, msg: &mut alloc::vec::Vec<u8>) -> Result<(), ProtoError> { 158 131 let counter = self.counter.to_be_bytes(); 159 132 133 + // Nonce is randomised to act as a OTP when mixed with the counter state 160 134 let mut epstein = Array::generate(); 161 135 162 136 self.transport.encrypt_in_place(&epstein, &counter, msg)?; 163 137 164 - msg.extend_from_slice(Self::mix_nonce(&mut epstein, &counter)); 138 + msg.extend(Self::mix_nonce(&mut epstein, &counter)); 165 139 166 - self.counter += TransportState::COUNTER_INCR; 140 + self.counter = self.counter.wrapping_add(TransportState::COUNTER_INCR); 167 141 168 - if self.counter.ct_eq(&TransportState::COUNTER_MAX).into() { 142 + // If we wrapped around and equal the finish value, we have maxed out the amount of 143 + // messages we can send. 144 + if self.counter.ct_eq(&self.transport.finish).into() { 169 145 Err(ProtoError) 170 146 } else { 171 147 Ok(()) ··· 173 149 } 174 150 175 151 fn mix_nonce<'a>( 176 - nonce: &'a mut aead::Nonce<ChaCha20Poly1305>, 152 + epstein: &'a mut aead::Nonce<ChaCha20Poly1305>, 177 153 position: &'a [u8; 8], 178 154 ) -> &'a aead::Nonce<ChaCha20Poly1305> { 179 - nonce[..position.len()] 155 + epstein[..position.len()] 180 156 .iter_mut() 181 157 .zip(position) 182 158 .for_each(|(byte, count)| *byte ^= *count); 183 159 184 - nonce 160 + epstein 185 161 } 186 162 } 187 163 ··· 199 175 200 176 self.transport.decrypt_in_place(&epstein, &counter, msg)?; 201 177 202 - self.counter += TransportState::COUNTER_INCR; 178 + self.counter = self.counter.wrapping_add(TransportState::COUNTER_INCR); 203 179 204 - if self.counter.ct_eq(&TransportState::COUNTER_MAX).into() { 180 + // If we wrapped around and equal the finish value, we have maxed out the amount of 181 + // messages we can send. 182 + if self.counter.ct_eq(&self.transport.finish).into() { 205 183 Err(ProtoError) 206 184 } else { 207 185 Ok(()) ··· 233 211 } 234 212 235 213 impl TransportPrimitive<ChaCha20Poly1305> for TransportState { 236 - type NonceOverhead = U8; 237 - 238 214 type Counter = u64; 239 215 240 216 const COUNTER_INCR: Self::Counter = 1; 241 217 242 - const COUNTER_MAX: Self::Counter = u64::MAX; 243 - 244 218 fn encrypt_in_place( 245 219 &self, 246 220 epstein: &aead::Nonce<ChaCha20Poly1305>, ··· 264 238 } 265 239 } 266 240 241 + pub struct TransportState { 242 + aead: ChaCha20Poly1305, 243 + finish: u64, 244 + } 245 + 267 246 impl TransportState { 268 247 pub fn init(psk: &[u8; 32], shared: impl Into<SharedSecret>) -> Result<Self, ProtoError> { 269 248 let noncer = shared.into(); ··· 271 250 272 251 let mut key = [0u8; 32]; 273 252 274 - kdf.expand(b"sachy-crypto", &mut key) 253 + let mut finish = [0u8; 8]; 254 + 255 + kdf.expand(b"SachY-Crypt0", &mut key) 256 + .map_err(|_| ProtoError)?; 257 + 258 + kdf.expand(b"PickANumber", &mut finish) 275 259 .map_err(|_| ProtoError)?; 276 260 277 261 Ok(Self { 278 262 aead: ChaCha20Poly1305::new(&key.into()), 263 + finish: u64::from_le_bytes(finish), 279 264 }) 280 265 } 281 266 282 267 pub fn split(&self) -> (SendingState<'_>, ReceivingState<'_>) { 268 + let counter = self.finish; 269 + 283 270 ( 284 271 SendingState { 285 272 transport: self, 286 - counter: 0, 273 + counter, 287 274 }, 288 275 ReceivingState { 289 276 transport: self, 290 - counter: 0, 277 + counter, 291 278 }, 292 279 ) 293 280 } ··· 321 308 let mut buffer2 = vec![0u8; 64]; 322 309 323 310 // Using the same nonce to check that the internal states match. Normally, client/server 324 - // would work with different nonces, because nonce reuse is BAD 311 + // would work with randomised nonces, because nonce reuse is BAD 325 312 client_transport 326 313 .aead 327 314 .encrypt_in_place(&nonce, &[], &mut buffer1)?; ··· 355 342 let (mut alice_send, mut alice_recv) = alice.split(); 356 343 let (mut bob_send, mut bob_recv) = bob.split(); 357 344 345 + // Have to be synchronised on both ends, so the counter state matches between the two 346 + // and thus messages can be encrypted/decrypted statefully. But the actual number is 347 + // "random", making it harder to guess the position state. 348 + assert_eq!(alice.finish, bob.finish); 349 + 358 350 let orig = b"Test Message, Please ignore.".to_vec(); 359 351 360 352 let mut msg = orig.clone(); ··· 388 380 assert_eq!(orig.as_slice(), msg.as_slice()); 389 381 assert_eq!(alice_send.counter, bob_recv.counter); 390 382 assert_eq!(bob_send.counter, alice_recv.counter); 383 + assert_ne!(alice_send.counter, alice_recv.counter); 384 + assert_ne!(bob_send.counter, bob_recv.counter); 391 385 392 386 Ok(()) 393 387 }

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
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 #9
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