Solutions for 2022, as well as 2015-2018 and 2019 up to day 11
This commit is contained in:
commit
1895197c49
722 changed files with 375457 additions and 0 deletions
9
2016/day14-one-time_pad/Cargo.toml
Normal file
9
2016/day14-one-time_pad/Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "day14-one-time_pad"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
md-5 = "0.10.5"
|
61
2016/day14-one-time_pad/challenge.txt
Normal file
61
2016/day14-one-time_pad/challenge.txt
Normal file
|
@ -0,0 +1,61 @@
|
|||
\--- Day 14: One-Time Pad ---
|
||||
----------
|
||||
|
||||
In order to communicate securely with Santa while you're on this mission, you've been using a [one-time pad](https://en.wikipedia.org/wiki/One-time_pad) that you [generate](https://en.wikipedia.org/wiki/Security_through_obscurity) using a pre-agreed algorithm. Unfortunately, you've run out of keys in your one-time pad, and so you need to generate some more.
|
||||
|
||||
To generate keys, you first get a stream of random data by taking the [MD5](https://en.wikipedia.org/wiki/MD5) of a pre-arranged [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) (your puzzle input) and an increasing integer index (starting with `0`, and represented in decimal); the resulting MD5 hash should be represented as a string of *lowercase* hexadecimal digits.
|
||||
|
||||
However, not all of these MD5 hashes are *keys*, and you need `64` new keys for your one-time pad. A hash is a key *only if*:
|
||||
|
||||
* It contains *three* of the same character in a row, like `777`. Only consider the first such triplet in a hash.
|
||||
* One of the next `1000` hashes in the stream contains that same character *five* times in a row, like `77777`.
|
||||
|
||||
Considering future hashes for five-of-a-kind sequences does not cause those hashes to be skipped; instead, regardless of whether the current hash is a key, always resume testing for keys starting with the very next hash.
|
||||
|
||||
For example, if the pre-arranged salt is `abc`:
|
||||
|
||||
* The first index which produces a triple is `18`, because the MD5 hash of `abc18` contains `...cc38887a5...`. However, index `18` does not count as a key for your one-time pad, because none of the next thousand hashes (index `19` through index `1018`) contain `88888`.
|
||||
* The next index which produces a triple is `39`; the hash of `abc39` contains `eee`. It is also the first key: one of the next thousand hashes (the one at index 816) contains `eeeee`.
|
||||
* None of the next six triples are keys, but the one after that, at index `92`, is: it contains `999` and index `200` contains `99999`.
|
||||
* Eventually, index `22728` meets all of the criteria to generate the `64`th key.
|
||||
|
||||
So, using our example salt of `abc`, index `22728` produces the `64`th key.
|
||||
|
||||
Given the actual salt in your puzzle input, *what index* produces your `64`th one-time pad key?
|
||||
|
||||
Your puzzle answer was `23769`.
|
||||
|
||||
\--- Part Two ---
|
||||
----------
|
||||
|
||||
Of course, in order to make this process [even more secure](https://en.wikipedia.org/wiki/MD5#Security), you've also implemented [key stretching](https://en.wikipedia.org/wiki/Key_stretching).
|
||||
|
||||
Key stretching forces attackers to spend more time generating hashes. Unfortunately, it forces everyone else to spend more time, too.
|
||||
|
||||
To implement key stretching, whenever you generate a hash, before you use it, you first find the MD5 hash of that hash, then the MD5 hash of *that* hash, and so on, a total of *`2016` additional hashings*. Always use lowercase hexadecimal representations of hashes.
|
||||
|
||||
For example, to find the stretched hash for index `0` and salt `abc`:
|
||||
|
||||
* Find the MD5 hash of `abc0`: `577571be4de9dcce85a041ba0410f29f`.
|
||||
* Then, find the MD5 hash of that hash: `eec80a0c92dc8a0777c619d9bb51e910`.
|
||||
* Then, find the MD5 hash of that hash: `16062ce768787384c81fe17a7a60c7e3`.
|
||||
* ...repeat many times...
|
||||
* Then, find the MD5 hash of that hash: `a107ff634856bb300138cac6568c0f24`.
|
||||
|
||||
So, the stretched hash for index `0` in this situation is `a107ff...`. In the end, you find the original hash (one use of MD5), then find the hash-of-the-previous-hash `2016` times, for a total of `2017` uses of MD5.
|
||||
|
||||
The rest of the process remains the same, but now the keys are entirely different. Again for salt `abc`:
|
||||
|
||||
* The first triple (`222`, at index `5`) has no matching `22222` in the next thousand hashes.
|
||||
* The second triple (`eee`, at index `10`) hash a matching `eeeee` at index `89`, and so it is the first key.
|
||||
* Eventually, index `22551` produces the `64`th key (triple `fff` with matching `fffff` at index `22859`.
|
||||
|
||||
Given the actual salt in your puzzle input and using `2016` extra MD5 calls of key stretching, *what index* now produces your `64`th one-time pad key?
|
||||
|
||||
Your puzzle answer was `20606`.
|
||||
|
||||
Both parts of this puzzle are complete! They provide two gold stars: \*\*
|
||||
|
||||
At this point, all that is left is for you to [admire your Advent calendar](/2016).
|
||||
|
||||
Your puzzle input was `cuanljph`.
|
75
2016/day14-one-time_pad/src/lib.rs
Normal file
75
2016/day14-one-time_pad/src/lib.rs
Normal file
|
@ -0,0 +1,75 @@
|
|||
use md5::{Md5, Digest};
|
||||
|
||||
pub fn run(input: &str) -> (usize, usize) {
|
||||
let first = iv_for_nth_key(input.trim(), 64, 1);
|
||||
let second = iv_for_nth_key(input.trim(), 64, 2017);
|
||||
(first, second)
|
||||
}
|
||||
|
||||
fn get_hash(salt: &str, iv: usize, stretching: u16, known_hashes: &mut Vec<([u8; 32], Option<u8>, bool)>) -> ([u8; 32], Option<u8>, bool) {
|
||||
if known_hashes.len() > iv {
|
||||
return known_hashes[iv];
|
||||
}
|
||||
let mut previous = salt.to_owned() + &(iv.to_string()[..]);
|
||||
let mut hex = [0_u8; 32];
|
||||
(0..stretching).for_each(|_| {
|
||||
let mut hasher = Md5::new();
|
||||
hasher.update(&previous);
|
||||
hex = hasher.finalize().iter().flat_map(|byte| [byte / 16, byte % 16]).collect::<Vec<u8>>().try_into().unwrap();
|
||||
previous = hex.iter().map(|i| match i {
|
||||
digit if digit < &10 => (b'0' + digit) as char,
|
||||
alpha => (b'a' + alpha - 10) as char,
|
||||
}).collect();
|
||||
});
|
||||
let first_3_tuple = hex.windows(3).find(|&w| w[0] == w[1] && w[1] == w[2]).map(|w| w[0]);
|
||||
let contains_5_tuple = first_3_tuple.is_some() && hex.windows(5).any(|w| w[0] == w[1] && w[1] == w[2] && w[2] == w[3] && w[3] == w[4]);
|
||||
known_hashes.push((hex, first_3_tuple, contains_5_tuple));
|
||||
(hex, first_3_tuple, contains_5_tuple)
|
||||
}
|
||||
|
||||
fn is_key(first_3_tuple: u8, salt: &str, next_iv: usize, stretching: u16, known_hashes: &mut Vec<([u8; 32], Option<u8>, bool)>) -> bool {
|
||||
for iv in next_iv..next_iv+1000 {
|
||||
let (hash, _, contains_5_tuple) = get_hash(salt, iv, stretching, known_hashes);
|
||||
if contains_5_tuple && hash.windows(5).any(|w| w[0] == first_3_tuple && w[0] == w[1] && w[0] == w[2] && w[0] == w[3] && w[0] == w[4]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn iv_for_nth_key(salt: &str, n: usize, stretching: u16) -> usize {
|
||||
let mut iv = 0;
|
||||
let mut known_hashes = Vec::new();
|
||||
(0..n).for_each(|_| {
|
||||
loop {
|
||||
iv += 1;
|
||||
if let Some(chars) = get_hash(salt, iv-1, stretching, &mut known_hashes).1 {
|
||||
if is_key(chars, salt, iv, stretching, &mut known_hashes) { break; }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
iv-1
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
fn read_file(name: &str) -> String {
|
||||
read_to_string(name).expect(&format!("Unable to read file: {name}")[..])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sample() {
|
||||
let sample_input = read_file("tests/sample_input");
|
||||
assert_eq!(run(&sample_input), (22728, 22551));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge() {
|
||||
let challenge_input = read_file("tests/challenge_input");
|
||||
assert_eq!(run(&challenge_input), (23769, 20606));
|
||||
}
|
||||
}
|
1
2016/day14-one-time_pad/tests/challenge_input
Normal file
1
2016/day14-one-time_pad/tests/challenge_input
Normal file
|
@ -0,0 +1 @@
|
|||
cuanljph
|
1
2016/day14-one-time_pad/tests/sample_input
Normal file
1
2016/day14-one-time_pad/tests/sample_input
Normal file
|
@ -0,0 +1 @@
|
|||
abc
|
Loading…
Add table
Add a link
Reference in a new issue