0

I'm attempting to implement SHA3-512 the way it is done by NIST in FIPS PUB 202:

https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf.

It works whenever I plug in the test vector's bit values + "01"(look for HERE in code), and for the blank value test case. Example working test vector:

https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/SHA3-512_Msg5.pdf

For some reason, whenever string values such as "abc" are plugged in, the hash function fails to work. Despite the conversion from string to binary string being fine, I believe the issue is with the padding function but I cant see where I messed up.

/* Keccak */
struct Keccak<'a> {
    b: usize,
    d: usize,
    r: usize,
    l: usize,
    w: usize,
    delim: &'a str,
}

impl Keccak<'_> {
    pub fn new(digest: usize, bits: usize, delim: &str) -> Keccak {
        Keccak {
            b: bits,
            d: digest,
            r: (bits - digest * 2),
            w: bits / 25,
            l: (((bits / 25) as f32).log2()) as usize,
            delim,
        }
    }

    pub fn hash(&mut self, msg: &mut String) {
        self.sponge(msg);
    }

    // SPONGE the provided string of binary m
    fn sponge(&mut self, msg: &mut String) {
        // Pad the message using pad10*1
        // HERE: let mut msg = &mut String::from("01");
        let len = msg.len() as isize;
        self.pad101(msg, self.r as isize, len);

        // Let n be size of P
        let n = msg.len() / self.r;

        // Create vector s of size b/8, each value is a byte
        let mut s: Vec<String> = vec!["0".repeat(8); self.b / 8];

        // Xor s and p for every p in P
        let c = self.b - self.r;
        for i in 0..n {
            // let p be the message of len(r) + 0^c of size b
            let p = format!("{}{}", &msg[i * self.r..(i + 1) * self.r], "0".repeat(c));
            // Xor s and p in byte sized intervals, then do a round of keccakf
            s = self.keccackf(
                &(0..p.len())
                    .step_by(8)
                    .map(|x| {
                        format!(
                            "{:08?}",
                            p[x..x + 8].parse::<u32>().unwrap() ^ s[x / 8].parse::<u32>().unwrap()
                        )
                    })
                    .collect::<Vec<String>>()
                    .join(""),
            );

            let mut z = String::new();
            while z.len() <= self.d {
                let mut str = s.join("");
                str.truncate(self.r);
                z += &str;
                s = self.keccackf(&(s.join("")));
            }
            z.truncate(self.d);
            self.from_bin_string(&z);
        }
    }

    // Convert string m to state array
    fn to_state(&self, msg: &str) -> Vec<Vec<Vec<u8>>> {
        let mut s: Vec<Vec<Vec<u8>>> = Vec::new();
        for x in 0..5 {
            let mut row = Vec::new();
            for y in 0..5 {
                let mut column = Vec::new();
                for z in 0..self.w {
                    let i = self.w * (5 * y + x) + z;
                    column.push(msg[i..i + 1].parse::<u8>().unwrap());
                }
                row.push(column)
            }
            s.push(row);
        }
        s
    }

    // Convert state array to vector of string
    fn from_state(&self, state: Vec<Vec<Vec<u8>>>) -> Vec<String> {
        let mut result: Vec<String> = Vec::new();
        let mut temp = String::new();
        for i in 0..5 {
            for j in 0..5 {
                for w in 0..self.w {
                    temp += &format!("{}", state[j][i][w]);

                    if (temp.len() == 8) {
                        result.push(temp.clone());
                        temp = String::new();
                    }
                }
            }
        }
        result
    }

    fn theta(&self, state: &mut Vec<Vec<Vec<u8>>>) {
        // C NIST
        fn c(x: usize, z: usize, state: &Vec<Vec<Vec<u8>>>) -> u8 {
            state[x][0][z] ^ state[x][1][z] ^ state[x][2][z] ^ state[x][3][z] ^ state[x][4][z]
        }

        // D NIST
        fn d(x: usize, z: usize, state: &Vec<Vec<Vec<u8>>>, w: isize) -> u8 {
            c(((x as isize) - 1).rem_euclid(5) as usize, z, state)
                ^ c(
                    (x + 1).rem_euclid(5),
                    ((z as isize) - 1).rem_euclid(w) as usize,
                    state,
                )
        }

        // Let s be a'
        let mut s = vec![vec![vec![0; self.w]; 5]; 5];

        // Save xor'd values into a'
        for x in 0..5 {
            for y in 0..5 {
                for z in 0..self.w {
                    s[x][y][z] = state[x][y][z] ^ d(x, z, &state, self.w as isize);
                }
            }
        }

        s.clone_into(state);
    }

    fn rho(&self, state: &mut Vec<Vec<Vec<u8>>>) {
        // Let s be a'
        let mut s = vec![vec![vec![0; self.w]; 5]; 5];
        // Set all z values of a' = a
        state[0][0].clone_into(&mut s[0][0]);

        // Let coords represent (x, y)
        let mut coords = [1, 0];
        for t in 0..24 {
            for z in 0..self.w {
                s[coords[0]][coords[1]][z] = state[coords[0]][coords[1]]
                    [((z as isize) - (t + 1) * (t + 2) / 2).rem_euclid(self.w as isize) as usize];
            }
            coords = [coords[1], (2 * coords[0] + 3 * coords[1]).rem_euclid(5)];
        }
        s.clone_into(state);
    }

    fn pi(&self, state: &mut Vec<Vec<Vec<u8>>>) {
        let mut s: Vec<Vec<Vec<u8>>> = Vec::new();
        for x in 0..5 {
            let mut row = Vec::new();
            for y in 0..5 {
                let mut column = Vec::new();
                for z in 0..self.w {
                    column.push(state[((x as usize) + 3 * y).rem_euclid(5)][x][z]);
                }
                row.push(column)
            }
            s.push(row);
        }
        s.clone_into(state)
    }

    fn chi(&self, state: &mut Vec<Vec<Vec<u8>>>) {
        let mut s: Vec<Vec<Vec<u8>>> = Vec::new();
        for x in 0..5 {
            let mut row = Vec::new();
            for y in 0..5 {
                let mut column = Vec::new();
                for z in 0..self.w {
                    column.push(
                        state[x][y][z]
                            ^ ((state[(x + 1).rem_euclid(5)][y][z] ^ 1)
                                * state[(x + 2).rem_euclid(5)][y][z]),
                    );
                }
                row.push(column)
            }
            s.push(row);
        }
        s.clone_into(state)
    }

    fn rc(&self, t: usize) -> u8 {
        if t.rem_euclid(255) == 0 {
            return 1;
        }

        let mut r = std::collections::VecDeque::new();
        r.extend(
            "10000000"
                .chars()
                .map(|x| x.to_string().parse::<u8>().unwrap()),
        );

        for _ in 0..t.rem_euclid(255) {
            r.push_front(0);
            r[0] ^= r[8];
            r[4] ^= r[8];
            r[5] ^= r[8];
            r[6] ^= r[8];
            r.truncate(8);
        }
        return r[0];
    }

    fn iota(&self, state: &mut Vec<Vec<Vec<u8>>>, round: usize) {
        let mut r: Vec<u8> = vec![0; self.w];
        for j in 0..=self.l {
            r[((2 as isize).pow(j as u32) - 1) as usize] = self.rc(j + 7 * round);
        }

        for z in 0..self.w {
            state[0][0][z] ^= r[z];
        }
    }

    fn keccackf(&self, msg: &str) -> Vec<String> {
        let mut state = self.to_state(msg);

        // Go through all the rounds
        for r in 0..(12 + 2 * self.l) {
            self.theta(&mut state);
            self.rho(&mut state);
            self.pi(&mut state);
            self.chi(&mut state);
            self.iota(&mut state, r);
        }

        let res = self.from_state(state);
        res
    }

    // Pad10*1
    fn pad101(&mut self, msg: &mut String, x: isize, m: isize) {
        let j = (-m - 2).rem_euclid(x);

        msg.push_str("1");
        msg.push_str(&"0".repeat(j as usize));
        msg.push('1');
    }

    // For nist test vectors
    fn from_bin_string(&self, str: &str) {
        let z = (0..str.len())
            .step_by(8)
            .map(|i| (&str[i..i + 8]).chars().rev().collect::<String>())
            .collect::<Vec<String>>();
        for i in 0..z.len() {
            print!("{:02x}", u32::from_str_radix(&z[i], 2).unwrap());
        }
    }
}

/* Sha3 */
pub struct SHA3<'a> {
    kec: Option<Keccak<'a>>,
}

impl SHA3<'_> {
    // Creates new hash variable with default hash function, currently SHA512
    pub fn new() -> SHA3<'static> {
        SHA3 { kec: None }
    }

    // Sha512
    fn SHA512(&mut self, mut msg: String) {
        match self.kec.as_mut() {
            // Provided kec hash msg
            Some(kec) => kec.hash(&mut msg),
            // Otherwise create new kec and call function again
            None => {
                // Creating new keccack struct, l & w & state are defined in NIST
                self.kec = Some(Keccak::new(512, 1600, "0110"));
                self.SHA512(msg)
            }
        }
    }

    // Return hashed vector
    pub fn hash(&mut self, m: &str) {
        self.SHA512(self.to_bin_string(m) + "01");
    }

    // Return hexdigest output
    pub fn hexdigest(&self, m: &str) {}

    // Convert string slice to binary string
    fn to_bin_string(&self, str: &str) -> String {
        String::from(str)
            .as_bytes()
            .into_iter()
            .map(|x| format!("{:08b}", x))
            .collect::<Vec<String>>()
            .join("")
    }
}
Non Reply
  • 3
  • 1
  • What have you done to resolve this? Have you looked at the `j` values you are generating and if they are according to spec? Note that personally, if I have to implement core cryptographic functionality using character strings, I would feel that I have failed. Similarly, you should not make your message mutable. Are you sure you aren't hashing that message after mutation in your control function? – Maarten Bodewes Oct 15 '22 at 03:39
  • Msg is mutable so the pad10*1 function can change the value without having to pass around a new variable for message. It’s not a issue because the sha3 struct creates the mutable string from a string literal anyways. The J value is fine, the documentation and implementation for that variable are nearly identical. Ive compared the padded output of a functional sha3-512 program to this code and their not the same which is what makes me believe either the sponge or padding is being done incorrectly. However I have so far only seen implementations that are based off of the keccak pseudocode – Non Reply Oct 15 '22 at 04:56
  • In what fashion are they not the same? Like all hash functions, Keccak processes the message block-by-block. So what you do is that you copy the message bits of each block into a buffer before processing, and *then only pad the final buffers*, rather than the entire message. – Maarten Bodewes Oct 15 '22 at 15:53
  • This is mainly why I'm surprised. I've created a Skein implementation from spec, but that still used block-by-block processing - on bytes rather than bits. Yes, the spec will probably tell you to "pad the message" but that's horrid from a developers perspective. Even if I'd wanted to use bits I would just indicate the number of (un)occupied bits in the final byte. – Maarten Bodewes Oct 15 '22 at 15:59
  • I know this function isn’t the most efficient, but I want it to be implemented the way NIST does it in FIPS 202. – Non Reply Oct 15 '22 at 19:59

0 Answers0