diff --git a/Cargo.lock b/Cargo.lock index 8b2b9b6..bc9b37a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,3 +5,61 @@ version = 3 [[package]] name = "lore" version = "2.0.1" +dependencies = [ + "serde", +] + +[[package]] +name = "proc-macro2" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" diff --git a/Cargo.toml b/Cargo.toml index 0472559..fb38224 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,9 @@ keywords = ["algorithms", "compression", "cryptography", "md5", "hash"] categories = ["algorithms", "compression", "cryptography"] exclude = [] -[dev-dependencies] +[dependencies] +serde = { version = ">=1.0.0", features = ["derive"], optional = true } + +[features] +default = [] +serde = ["dep:serde"] \ No newline at end of file diff --git a/README.md b/README.md index 5e64921..9738075 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,32 @@ # Lore -Hashing algorithms with a straight-forward API and no dependencies. +Nightly-only hashing algorithms with a straight-forward API and no required dependencies. -Nightly toolchain required. - -Currently implements: +This crate currently implements: - MD2, MD4, and MD5 +- SHA-1 -## Example +Performance is not a priority of this crate, rather, the primary purpose of this crate is learning, as well as providing tests for the intermediate steps of algorithms. +This includes padding, checksums and round step functions. + +The functions of this crate should probably not be used for production purposes. + +Once [`slice::array_chunks`] is stabilized, this crate can be made to work on stable Rust. +The crate could be rewritten to use stable already, but this would increase the verbosity of many expressions. + +[`slice::array_chunks`]: https://doc.rust-lang.org/std/primitive.slice.html#method.array_chunks + +# Features + +[Serde](https://crates.io/crates/serde) support is included, and is gated behind the `serde` feature. + +# Examples + +Basic usage: ```rust -fn main() { - let input = "lol xd"; - let digest = lore::md5(input); - assert_eq!(digest, "982d7f24f8985a6baa5cf129acc73561"); -} +let input = "lol xd"; +let digest = lore::md5(input); +assert_eq!(digest.to_string(), "982d7f24f8985a6baa5cf129acc73561"); ``` diff --git a/src/bin/sandbox.rs b/src/bin/sandbox.rs index bd95776..f328e4d 100644 --- a/src/bin/sandbox.rs +++ b/src/bin/sandbox.rs @@ -1,3 +1 @@ -fn main() { - println!("{}", lore::md2("abc")); -} +fn main() {} diff --git a/src/hash.rs b/src/hash.rs index 1693236..43bb2d1 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -1,12 +1,31 @@ -use std::fmt::Display; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +use std::{fmt::Display, hash::Hash}; pub mod md2; pub mod md4; pub mod md5; +pub mod sha1; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +/// A variable-size digest, which can easily be converted into a hexadecimal string for user-facing output. +/// +/// This struct is returned by all hashing functions. +/// +/// # Examples +/// +/// [`Digest`] implements [`Display`], which means it can automatically be converted to a human-readable format by formatting it: +/// +/// ```rust +/// let digest = lore::md5("example"); +/// println!("Digest: {}", digest); // -> Digest: 1a79a4d60de6718e8e5b326e338ae533 +/// ``` +/// +/// Digest contains [`From`] implementations which convert +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Digest([u8; S]); +/// Convert the digest into a hexadecimal string representation. impl Display for Digest { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str( @@ -14,13 +33,36 @@ impl Display for Digest { .0 .iter() .map(|u| format!("{:02x}", u)) - .collect::>() - .join(""), + .collect::(), ) } } -pub(crate) fn bytes_to_words_le(bytes: impl AsRef<[u8]>) -> Vec { +impl From> for [u8; S] { + fn from(digest: Digest) -> Self { + digest.0 + } +} + +impl<'a, const S: usize> From<&'a Digest> for &'a [u8] { + fn from(digest: &'a Digest) -> Self { + digest.0.as_slice() + } +} + +impl From> for Vec { + fn from(digest: Digest) -> Self { + digest.0.to_vec() + } +} + +impl AsRef<[u8]> for Digest { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} + +pub fn bytes_to_words_le(bytes: impl AsRef<[u8]>) -> Vec { bytes .as_ref() .array_chunks::<4>() @@ -28,7 +70,7 @@ pub(crate) fn bytes_to_words_le(bytes: impl AsRef<[u8]>) -> Vec { .collect() } -pub(crate) fn words_to_bytes_le(words: impl AsRef<[u32]>) -> Vec { +pub fn words_to_bytes_le(words: impl AsRef<[u32]>) -> Vec { words .as_ref() .iter() @@ -43,12 +85,12 @@ mod tests { #[test] fn bytes_to_words_le_works() { assert_eq!( - vec![0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476], + vec![0x6745_2301, 0xefcd_ab89, 0x98ba_dcfe, 0x1032_5476], bytes_to_words_le([ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10 ]) - ) + ); } #[test] @@ -58,7 +100,7 @@ mod tests { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10 ], - words_to_bytes_le([0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]) - ) + words_to_bytes_le([0x6745_2301, 0xefcd_ab89, 0x98ba_dcfe, 0x1032_5476]) + ); } } diff --git a/src/hash/md2.rs b/src/hash/md2.rs index 3f0b63a..2edd7f9 100644 --- a/src/hash/md2.rs +++ b/src/hash/md2.rs @@ -43,8 +43,14 @@ fn checksum(message: impl AsRef<[u8]>) -> Vec { message } -/// Compute the MD2 hash of the input bytes +/// Computes the MD2 digest of the input bytes. +/// +/// Returns a `Digest<16>` which implements `Display` in order to get at hexadecimal-string representation. +/// /// # Examples +/// +/// Basic usage: +/// /// ``` /// let input = "abc"; /// let digest = lore::md2(input); diff --git a/src/hash/md4.rs b/src/hash/md4.rs index 8d888eb..c04bdb9 100644 --- a/src/hash/md4.rs +++ b/src/hash/md4.rs @@ -27,7 +27,7 @@ const G: fn(u32, u32, u32) -> u32 = |x: u32, y: u32, z: u32| (x & y) | (x & z) | const H: fn(u32, u32, u32) -> u32 = |x: u32, y: u32, z: u32| x ^ y ^ z; // pad the message to next 512-bit interval -pub(crate) fn pad(message: impl AsRef<[u8]>) -> Vec { +pub fn pad(message: impl AsRef<[u8]>) -> Vec { let mut message = message.as_ref().to_vec(); let message_length = message.len().wrapping_mul(8) as u64; @@ -36,7 +36,7 @@ pub(crate) fn pad(message: impl AsRef<[u8]>) -> Vec { // add 0 bits until length in bits is congruent to 448 mod 512 while (message.len()) % 64 != 56 { - message.push(0u8) + message.push(0u8); } // append message length (64 bits) @@ -48,7 +48,7 @@ pub(crate) fn pad(message: impl AsRef<[u8]>) -> Vec { // compute an invidiual step in the md4 algorithm fn step([mut a, b, c, d]: [u32; 4], words: &[u32], index: usize) -> [u32; 4] { // choose function and constant based on which round is currently active - let (rc, f) = match index { + let (constant, round_function) = match index { 0..=15 => (C1, F), 16..=31 => (C2, G), 32..=47 => (C3, H), @@ -56,17 +56,23 @@ fn step([mut a, b, c, d]: [u32; 4], words: &[u32], index: usize) -> [u32; 4] { }; // main operation - a = f(b, c, d) + a = round_function(b, c, d) .wrapping_add(a) .wrapping_add(words[W[index]]) - .wrapping_add(rc) + .wrapping_add(constant) .rotate_left(S[index]); [a, b, c, d] } -/// Compute the MD4 hash of the input bytes +/// Computes the MD4 digest of the input bytes. +/// +/// Returns a `Digest<16>` which implements `Display` in order to get at hexadecimal-string representation. +/// /// # Examples +/// +/// Basic usage: +/// /// ``` /// let input = "abc"; /// let digest = lore::md4(input); @@ -165,7 +171,7 @@ mod tests { assert_eq!( hash("Rosetta Code").to_string(), "a52bcfc6a0d0d300cdc5ddbfbefe478b" - ) + ); } #[test] diff --git a/src/hash/md5.rs b/src/hash/md5.rs index 32d09d8..e6476f4 100644 --- a/src/hash/md5.rs +++ b/src/hash/md5.rs @@ -36,7 +36,7 @@ const H: fn(u32, u32, u32) -> u32 = |x: u32, y: u32, z: u32| x ^ y ^ z; const I: fn(u32, u32, u32) -> u32 = |x: u32, y: u32, z: u32| y ^ (x | !z); fn step([mut a, b, c, d]: [u32; 4], words: &[u32], index: usize) -> [u32; 4] { - let f = match index { + let round_function = match index { 0..=15 => F, 16..=31 => G, 32..=47 => H, @@ -44,7 +44,7 @@ fn step([mut a, b, c, d]: [u32; 4], words: &[u32], index: usize) -> [u32; 4] { _ => panic!("This function shouldn't be called using an index outside 0..48"), }; - a = f(b, c, d) + a = round_function(b, c, d) .wrapping_add(a) .wrapping_add(words[W[index]]) .wrapping_add(K[index]) @@ -54,13 +54,20 @@ fn step([mut a, b, c, d]: [u32; 4], words: &[u32], index: usize) -> [u32; 4] { [a, b, c, d] } -/// Compute the MD5 hash of the input bytes +/// Computes the MD5 digest of the input bytes. +/// +/// Returns a `Digest<16>` which implements `Display` in order to get at hexadecimal-string representation. +/// /// # Examples +/// +/// Basic usage: +/// /// ``` /// let input = "lol"; /// let digest = lore::md5(input); /// /// assert_eq!(digest.to_string(), "9cdfb439c7876e703e307864c9167a15"); +/// /// ``` pub fn hash(message: impl AsRef<[u8]>) -> Digest<16> { let padded = pad(message); diff --git a/src/hash/sha1.rs b/src/hash/sha1.rs index ff9bed9..d4d2b89 100644 --- a/src/hash/sha1.rs +++ b/src/hash/sha1.rs @@ -1,7 +1,27 @@ +use crate::hash::Digest; + +/// Computes the SHA1 digest of the input bytes. +/// +/// Returns a `Digest<20>` which implements `Display` in order to get at hexadecimal-string representation. +/// +/// # Examples +/// +/// Basic usage: +/// +/// ```rust +/// let input = "abc"; +/// let digest = lore::sha1(input); +/// +/// assert_eq!(digest.to_string(), "a9993e364706816aba3e25717850c26c9cd0d89d") +/// ``` +pub fn hash(message: impl AsRef<[u8]>) -> Digest<20> { + todo!() +} + #[cfg(test)] mod tests { use super::*; #[test] - fn sha1() {} + fn sha1_hash() {} } diff --git a/src/lib.rs b/src/lib.rs index 398f617..6e75b5c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,20 @@ +// rust nightly features #![feature(array_chunks)] +// lints +#![deny(missing_docs)] +#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)] +#![allow( + clippy::unreadable_literal, + clippy::missing_panics_doc, + clippy::cast_possible_truncation +)] +// docs +#![doc = include_str!("../README.md")] mod hash; pub use hash::md2::hash as md2; pub use hash::md4::hash as md4; pub use hash::md5::hash as md5; +pub use hash::sha1::hash as sha1; +pub use hash::Digest;