↑Programmieren in Rust
Eine Stromchiffre ist ein durch einen Schlüssel (key) und eine Nonce (number used exactly once) parametrisierter deterministischer kryptografischer Zufallszahlengenerator. Der vom Generator erzeugte geheime Datenstrom heißt Schlüsselstrom. Der Schlüsselstrom muss die gleichen Eigenschaften wie ein One-Time-Pad besitzen und wird wie dieses bitweise mit den Klardaten XOR-verknüpft um das Chiffrat zu erhalten. Zur Entschlüsselung wird diese Operation ein zweites Mal mit gleichem Schlüsselstrom angewandt – die Reinform einer Stromchiffre ist also involutorisch. Wie bei einem One-Time-Pad darf der Schlüsselstrom nur ein einziges Mal benutzt werden.
Die moderne Kryptografie kennt Stromchiffren als Mittel der Wahl. Dies hat die folgenden Gründe:
Zu bemerken ist noch, dass Echtzeitanwendungen und schnelle Datenübertragungen, wie sie gerade im Internet überall vorkommen, eine erhöhte Anforderung an die Implementation einer Chiffre haben. Bei solchen Anwendungen muss das Verfahren vor Seitenkanalattacken wie Rechenzeitattacken (timing attacks) geschützt werden. [1]
Zudem besteht das grundsätzliche Problem, dass aus der Länge des Chiffrats unter manchen Umständen auf die Klardaten geschlossen werden kann. Natürlich ist eine Verschleierung möglich, allerdings verträgt sich dies nicht mit der gewünschten Datensparsamkeit im Internet.
// Die Stromchiffre ChaCha20 von Daniel Bernstein. fn u32_from_bytes_le(a: &[u8]) -> u32 { u32::from(a[0]) | u32::from(a[1])<<8 | u32::from(a[2])<<16 | u32::from(a[3])<<24 } fn quarter_round(x: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize ) { x[a] = x[a].wrapping_add(x[b]); x[d] = (x[d]^x[a]).rotate_left(16); x[c] = x[c].wrapping_add(x[d]); x[b] = (x[b]^x[c]).rotate_left(12); x[a] = x[a].wrapping_add(x[b]); x[d] = (x[d]^x[a]).rotate_left(8); x[c] = x[c].wrapping_add(x[d]); x[b] = (x[b]^x[c]).rotate_left(7); } fn chacha20_stream(key: &[u8; 32], iv: &[u8; 8], callback: &mut dyn FnMut(u8) -> bool ) { let mut state: [u32; 16] = [0; 16]; let x: &mut [u32; 16] = &mut [0; 16]; state[0] = u32_from_bytes_le("expa".as_bytes()); state[1] = u32_from_bytes_le("nd 3".as_bytes()); state[2] = u32_from_bytes_le("2-by".as_bytes()); state[3] = u32_from_bytes_le("te k".as_bytes()); for i in 0..8 { state[4+i] = u32_from_bytes_le(&key[4*i..4*(i+1)]); } state[12] = 0; // Counter, niederwertige Bits state[13] = 0; // Counter, höherwertige Bits state[14] = u32_from_bytes_le(&iv[0..4]); state[15] = u32_from_bytes_le(&iv[4..8]); 'stream: loop { *x = state; for _ in 0..10 { quarter_round(x, 0, 4, 8, 12); quarter_round(x, 1, 5, 9, 13); quarter_round(x, 2, 6, 10, 14); quarter_round(x, 3, 7, 11, 15); quarter_round(x, 0, 5, 10, 15); quarter_round(x, 1, 6, 11, 12); quarter_round(x, 2, 7, 8, 13); quarter_round(x, 3, 4, 9, 14); } for i in 0..16 { let value = state[i].wrapping_add(x[i]); if callback(value as u8) {break 'stream;}; if callback((value>>8) as u8) {break 'stream;}; if callback((value>>16) as u8) {break 'stream;}; if callback((value>>24) as u8) {break 'stream;}; } state[12] = state[12].wrapping_add(1); if state[12] == 0 {state[13] += 1;} } } fn encipher(key: &[u8; 32], iv: &[u8; 8], data: &mut [u8]) { let mut i = data.iter_mut(); chacha20_stream(key, iv, &mut |y| { if let Some(x) = i.next() {*x ^= y; false} else {true} }); } fn key_from(text: &str) -> [u8; 32] { let mut key: [u8; 32] = [0; 32]; let n = text.len().min(32); key[0..n].clone_from_slice(&text.as_bytes()[0..n]); key } fn input(prompt: &str) -> String { use std::{io,io::Write}; let mut buffer = String::new(); print!("{}",prompt); io::stdout().flush().unwrap(); io::stdin().read_line(&mut buffer).unwrap(); if buffer.ends_with('\n') { buffer.pop(); if buffer.ends_with('\r') {buffer.pop();} } buffer } fn main() -> std::io::Result<()> { let argv: Vec<String> = std::env::args().collect(); let mut data = std::fs::read(&argv[1])?; let nonce: [u8; 8] = [0; 8]; // Eine Nonce wird bei einem Einmalschlüssel nicht benötigt, // der dann aber nur ein einziges Mal benutzt werden darf. // In der Praxis z.B. mit /dev/urandom initialisieren und // öffentlich den verschlüsselten Daten voranstellen. let key = key_from(&input("Schlüssel: ")); // Schlüssel mit mindestens 128 Bit Entropie eintippen, // z.B. 28 zufällige Zeichen (Zahlen und kleine Buchstaben). // In der Praxis ein aufwändiges Hashverfahren auf die // Eingabe anwenden -- da gibt es extra Algorithmen für -- // und die Nonce als Salt benutzen. encipher(&key, &nonce, &mut data); std::fs::write(&argv[2], data)?; Ok(()) }
Kommt ChaCha20 als kryptografischer Zufallszahlengenerator zur Anwendung, besteht Bedarf an einer externen Iteration. Das führt zu der folgenden Modifikation, wobei der zugrundeliegende Algorithmus identisch bleibt.
struct ChaCha20Stream { state: [u32; 16], obuff: [u8; 64], index: usize } impl ChaCha20Stream { fn new(key: &[u8; 32], iv: &[u8; 8]) -> Self { let mut state: [u32; 16] = [0; 16]; state[0] = u32_from_bytes_le("expa".as_bytes()); state[1] = u32_from_bytes_le("nd 3".as_bytes()); state[2] = u32_from_bytes_le("2-by".as_bytes()); state[3] = u32_from_bytes_le("te k".as_bytes()); for i in 0..8 { state[4+i] = u32_from_bytes_le(&key[4*i..4*(i+1)]); } state[12] = 0; // Counter, niederwertige Bits state[13] = 0; // Counter, höherwertige Bits state[14] = u32_from_bytes_le(&iv[0..4]); state[15] = u32_from_bytes_le(&iv[4..8]); Self {state, obuff: [0; 64], index: 64} } fn next_block(&mut self) { let mut x: [u32;16] = self.state; for _ in 0..10 { quarter_round(&mut x, 0, 4, 8, 12); quarter_round(&mut x, 1, 5, 9, 13); quarter_round(&mut x, 2, 6, 10, 14); quarter_round(&mut x, 3, 7, 11, 15); quarter_round(&mut x, 0, 5, 10, 15); quarter_round(&mut x, 1, 6, 11, 12); quarter_round(&mut x, 2, 7, 8, 13); quarter_round(&mut x, 3, 4, 9, 14); } for i in 0..16 { let value = self.state[i].wrapping_add(x[i]); self.obuff[4*i] = value as u8; self.obuff[4*i+1] = (value>>8) as u8; self.obuff[4*i+2] = (value>>16) as u8; self.obuff[4*i+3] = (value>>24) as u8; } self.state[12] = self.state[12].wrapping_add(1); if self.state[12] == 0 {self.state[13] += 1;} } } impl Iterator for ChaCha20Stream { type Item = u8; fn next(&mut self) -> Option<u8> { if self.index == 64 { self.next_block(); self.index = 0; } self.index += 1; Some(self.obuff[self.index-1]) } } fn encipher(key: &[u8; 32], iv: &[u8; 8], data: &mut [u8]) { let stream = ChaCha20Stream::new(key, iv); for (x, y) in data.iter_mut().zip(stream) { *x ^= y; } }
Parameter | key = [0; 32] ; iv = [0; 8]
|
---|---|
Schlüsselstrom |
76 b8 e0 ad a0 f1 3d 90 40 5d 6a e5 53 86 bd 28 bd d2 19 b8 a0 8d ed 1a a8 36 ef cc 8b 77 0d c7 da 41 59 7c 51 57 48 8d 77 24 e0 3f b8 d8 4a 37 6a 43 b8 f4 15 18 a1 1c c3 87 b6 69 b2 ee 65 86 9f 07 e7 be 55 51 38 7a 98 ba 97 7c 73 2d 08 0d cb 0f 29 a0 48 e3 65 69 12 c6 53 3e 32 ee 7a ed |
SHA-2-256 von 1 MiB |
fd71 55b0 3a35 4976 e6a9 85c0 f381 d313 b7af 4513 7a51 4ca7 457b 7e76 254f 1a9a |
Parameter | key = [0xff; 32] ; iv = [0xff; 8]
|
---|---|
Schlüsselstrom |
d9 bf 3f 6b ce 6e d0 b5 42 54 55 77 67 fb 57 44 3d d4 77 89 11 b6 06 05 5c 39 cc 25 e6 74 b8 36 3f ea bc 57 fd e5 4f 79 0c 52 c8 ae 43 24 0b 79 d4 90 42 b7 77 bf d6 cb 80 e9 31 27 0b 7f 50 eb 5b ac 2a cd 86 a8 36 c5 dc 98 c1 16 c1 21 7e c3 1d 3a 63 a9 45 13 19 f0 97 f3 b4 d6 da b0 77 87 |
SHA-2-256 von 1 MiB |
a826 1c94 10cd 7198 2ea5 372c 3ffa 4e1b 35eb 7391 c9bf 0251 baf0 d7f6 59a6 4760 |
Parameter | key = b"x152uhlwqtkr4sijepgy3ndmobavfz0c" ;iv = b"35270416"
|
---|---|
Schlüsselstrom |
04 15 c9 98 5f c3 af c9 0b 51 ff 22 52 cc 24 8d 83 3c 2a a6 a0 96 3d 01 25 1e 75 8f bd f4 c0 43 86 67 4a a0 5b 07 db 9e 23 a2 d1 5b cc 9e 37 8a 47 28 aa ca 17 f6 4d 21 1e b1 4a 4f 77 3e 19 ee 4a 62 c3 c2 f7 1a 43 37 c7 0a 8a 72 a6 e6 05 64 3c 5a e4 d2 9b 54 c6 96 d0 ec be 6d 40 23 d0 aa |
SHA-2-256 von 1 MiB |
1446 5ea6 98b7 29b8 ccda a026 c110 d061 8cbd 888a 0953 7559 57eb 78ef 4a15 cf32 |