Last active
August 29, 2024 12:09
-
-
Save roccodev/8fa130f1946f89702f799f89b8469bc9 to your computer and use it in GitHub Desktop.
Minecraft SHA-1 complement hash calculation in Rust
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (C) 2019 RoccoDev | |
// Licensed under the MIT license. | |
// <https://opensource.org/licenses/MIT> | |
// Bench results: | |
// First hash: 152ms | |
// Second hash: 1ms | |
// Third hash: 0ms | |
extern crate crypto; // Tested with 0.2.36 | |
extern crate num_bigint; // Tested with 0.2 | |
extern crate rustc_serialize; // Tested with ^0.3 | |
extern crate regex; // Tested with 1 | |
use regex::Regex; | |
use crypto::digest::Digest; | |
use crypto::sha1::Sha1; | |
use std::iter; | |
use rustc_serialize::hex::ToHex; | |
const LEADING_ZERO_REGEX: &str = r#"^0+"#; | |
fn calc_hash(name: &str) -> String { | |
let mut hasher = Sha1::new(); | |
hasher.input_str(name); | |
let mut hex: Vec<u8> = iter::repeat(0).take((hasher.output_bits() + 7)/8).collect(); | |
hasher.result(&mut hex); | |
let negative = (hex[0] & 0x80) == 0x80; | |
let regex = Regex::new(LEADING_ZERO_REGEX).unwrap(); | |
if negative { | |
two_complement(&mut hex); | |
format!("-{}", regex.replace(hex.as_slice().to_hex().as_str(), "").to_string()) | |
} | |
else { | |
regex.replace(hex.as_slice().to_hex().as_str(), "").to_string() | |
} | |
} | |
fn two_complement(bytes: &mut Vec<u8>) { | |
let mut carry = true; | |
for i in (0..bytes.len()).rev() { | |
bytes[i] = !bytes[i] & 0xff; | |
if carry { | |
carry = bytes[i] == 0xff; | |
bytes[i] = bytes[i] + 1; | |
} | |
} | |
} | |
mod tests { | |
#[test] | |
pub fn calc_hashes() { | |
assert_eq!("-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1", crate::calc_hash("jeb_")); | |
assert_eq!("4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48", crate::calc_hash("Notch")); | |
assert_eq!("88e16a1019277b15d58faf0541e11910eb756f6", crate::calc_hash("simon")); | |
} | |
} |
For future readers: You can do this in a single line of code with the num-bigint
crate.
let hex = BigInt::from_signed_bytes_be(&Sha1::digest("jeb_")).to_str_radix(16);
assert_eq!(hex, "-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1");
For future readers: You can do this in a single line of code with the
num-bigint
crate.let hex = BigInt::from_signed_bytes_be(&Sha1::digest("jeb_")).to_str_radix(16); assert_eq!(hex, "-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1");
Does this still work? For me it says:
function or associated item not found in 'Sha1'
refering to Sha1::digest()
Im using rust-crypto 0.2.36 and num-bigint 0.4
Does this still work? For me it says:
function or associated item not found in 'Sha1'
refering toSha1::digest()
Im using rust-crypto 0.2.36 and num-bigint 0.4
Is the Digest
trait in scope?
This might be better...
fn notchian_digest(mut array: [u8; 20]) -> String {
let mut hex;
if array[0] & 0b1000_0000 != 0 {
hex = String::with_capacity(41);
hex.push('-');
array[0] &= 0b0111_1111;
} else {
hex = String::with_capacity(40);
}
for byte in array {
write!(&mut hex, "{:X} ", byte)
.expect("failed to write hex?");
}
hex
}
and with the hasher
use sha1::Digest;
use sha1::Sha1;
use sha1::digest::generic_array::GenericArray;
fn notchian_hash(plaintext: impl AsRef<[u8]>) -> String {
let mut hasher = sha1::Sha1::default();
hasher.update(plaintext.as_ref());
let mut alloc = [0u8; 20];
sha1::Digest::finalize_into(hasher, GenericArray::from_mut_slice(&mut alloc));
notchian_digest(alloc)
}
Another example (no regex needed):
use sha1::{Digest, Sha1};
pub fn calc_hash(name: &str) -> String {
let mut hash: [u8; 20] = Sha1::new().chain_update(name).finalize().into();
let negative = (hash[0] & 0x80) == 0x80;
// Digest is 20 bytes, so 40 hex digits plus the minus sign if necessary.
let mut hex = String::with_capacity(40 + negative as usize);
if negative {
hex.push('-');
// two's complement
let mut carry = true;
for b in hash.iter_mut().rev() {
(*b, carry) = (!*b).overflowing_add(carry as u8);
}
}
hex.extend(
hash.into_iter()
// extract hex digits
.flat_map(|x| [x >> 4, x & 0xf])
// skip leading zeroes
.skip_while(|&x| x == 0)
.map(|x| char::from_digit(x as u32, 16).expect("x is always valid base16")),
);
hex
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_cases() {
let pairs = &[
("Notch", "4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48"),
("jeb_", "-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1"),
("simon", "88e16a1019277b15d58faf0541e11910eb756f6"),
];
for (input, output) in pairs {
assert_eq!(&calc_hash(input), output);
}
}
}
num-bigint
Thanks man
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
When the the hash start with a '0' this is removed like for my name: "RedstoneHero"
does this need to be added again or?