Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kupyna hasher #601

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ members = [
"groestl",
"jh",
"k12",
"kupyna",
"md2",
"md4",
"md5",
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Additionally all crates do not require the standard library (i.e. `no_std` capab
| [Grøstl] (Groestl) | [`groestl`] | [![crates.io](https://img.shields.io/crates/v/groestl.svg)](https://crates.io/crates/groestl) | [![Documentation](https://docs.rs/groestl/badge.svg)](https://docs.rs/groestl) | ![MSRV 1.71][msrv-1.71] | :green_heart: |
| [JH] | [`jh`] | [![crates.io](https://img.shields.io/crates/v/jh.svg)](https://crates.io/crates/jh) | [![Documentation](https://docs.rs/jh/badge.svg)](https://docs.rs/jh) | ![MSRV 1.71][msrv-1.71] | :green_heart: |
| [KangarooTwelve] | [`k12`] | [![crates.io](https://img.shields.io/crates/v/k12.svg)](https://crates.io/crates/k12) | [![Documentation](https://docs.rs/k12/badge.svg)](https://docs.rs/k12) | ![MSRV 1.71][msrv-1.71] | :green_heart: |
| [Kupyna] | [`kupyna`] | [![crates.io](https://img.shields.io/crates/v/kupyna.svg)](https://crates.io/crates/kupyna) | [![Documentation](https://docs.rs/kupyna/badge.svg)](https://docs.rs/kupyna) | ![MSRV 1.71][msrv-1.71] | :green_heart: |
| [MD2] | [`md2`] | [![crates.io](https://img.shields.io/crates/v/md2.svg)](https://crates.io/crates/md2) | [![Documentation](https://docs.rs/md2/badge.svg)](https://docs.rs/md2) | ![MSRV 1.71][msrv-1.71] | :broken_heart: |
| [MD4] | [`md4`] | [![crates.io](https://img.shields.io/crates/v/md4.svg)](https://crates.io/crates/md4) | [![Documentation](https://docs.rs/md4/badge.svg)](https://docs.rs/md4) | ![MSRV 1.71][msrv-1.71] | :broken_heart: |
| [MD5] | [`md5`] [:exclamation:] | [![crates.io](https://img.shields.io/crates/v/md-5.svg)](https://crates.io/crates/md-5) | [![Documentation](https://docs.rs/md-5/badge.svg)](https://docs.rs/md-5) | ![MSRV 1.72][msrv-1.72] | :broken_heart: |
Expand Down Expand Up @@ -290,6 +291,7 @@ Unless you explicitly state otherwise, any contribution intentionally submitted
[Grøstl]: https://en.wikipedia.org/wiki/Grøstl
[JH]: https://www3.ntu.edu.sg/home/wuhj/research/jh
[KangarooTwelve]: https://keccak.team/kangarootwelve.html
[Kupyna]: https://eprint.iacr.org/2015/885.pdf
[MD2]: https://en.wikipedia.org/wiki/MD2_(cryptography)
[MD4]: https://en.wikipedia.org/wiki/MD4
[MD5]: https://en.wikipedia.org/wiki/MD5
Expand Down
30 changes: 30 additions & 0 deletions kupyna/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "kupyna"
version = "0.1.0"
description = "Hashing algorithm for Kupyna, 8 to 512 length"
authors = [
"Joshua Koudys <[email protected]>",
"Raj Singh <[email protected]>",
"RustCrypto Developers",
]
license = "MIT"
readme = "README.md"
edition = "2021"
documentation = "https://docs.rs/kupyna"
repository = "https://github.com/RustCrypto/hashes"
keywords = ["crypto", "hash", "kupyna"]
categories = ["cryptography", "no-std"]
rust-version = "1.71"

[dependencies]
digest = { version = "=0.11.0-pre.8", default-features = false, features = ["core-api"] }

[dev-dependencies]

[features]
default = ["std"]
std = ["digest/std"]

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
21 changes: 21 additions & 0 deletions kupyna/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Joshua Koudys

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
85 changes: 85 additions & 0 deletions kupyna/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Kupyna Hash Function Implementation in Rust

## Overview

The Kupyna hash function is a cryptographic hash developed in Ukraine, designed for high security and efficiency. This implementation supports generating hash codes of various lengths.

## Authors

Joshua Koudys
Email: [[email protected]](mailto:[email protected])

Raj Singh Bisen
Email: [[email protected]](mailto:[email protected])

## Summary

Kupyna is a hash function standardized in Ukraine as DSTU 7564:2014. It is designed to provide a high level of security with a focus on robustness against various cryptographic attacks. The function supports different hash lengths, providing flexibility in usage depending on security requirements.

### Key Features of Kupyna:
- **High Security:** Resistant to known cryptographic attacks.
- **Efficiency:** Optimized for performance.
- **Flexibility:** Supports variable hash output lengths.

## Implementation Details

### TODO
Implement the excellent digest::Digest trait.
Work on streams of arbitrary size, so long as they have a known size by the end of them. Right
now it's just running on byte slices because it's easy.

### Usage

To compute the hash of a message using this implementation, you can call the `kupyna_hash` function with your message and desired hash length. Below is a basic usage example:

```rust
fn main() {
let message = b"hello world";

let hash = kupyna::hash(message);

println!("Hash: {:?}", hash);
}
```

### Running Tests

This implementation includes several unit tests to verify the correctness of the functions. You can run these tests using the following command:

```sh
cargo test
```

## Getting Started

I'm working on getting this read to go into a crate, or possibly merge it into an existing set of hashing functions. In the meantime, feel free to work with it directly.

### Installation

Clone the repository:
```sh
git clone https://github.com/jkoudys/kupyna.git
cd kupyna-rust
```

### Building the Project

Build the project using Cargo:
```sh
cargo build
```

### Running the Example

Run the example provided in the `main` function:
```sh
cargo run
```

## License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.

## Contact

For any questions or suggestions, feel free to contact me at [[email protected]](mailto:[email protected]). Pull requests welcome!
90 changes: 90 additions & 0 deletions kupyna/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
mod sub_units;
#[cfg(test)]
mod tests;

const STATE_SIZE: usize = 1024;
const HASH_SIZE: usize = 512;

fn pad_message(message: &[u8], msg_len_bits: usize, state_size: usize) -> Vec<u8> {
let round_msg_len = message.len() * 8;
let d =
((-((round_msg_len + 97) as isize) % (state_size as isize)) + state_size as isize) as usize;

// Calculate the length of padding to be added
let pad_len = d / 8;

// We set the padded message size upfront to reduce allocations
let padded_len = (msg_len_bits + 7) / 8 + pad_len + 13;
let mut padded_message = vec![0x00; padded_len];

// Copy n bits from the input message
let full_bytes = msg_len_bits / 8;
let remaining_bits = msg_len_bits % 8;

padded_message[..full_bytes].copy_from_slice(&message[..full_bytes]);

if remaining_bits > 0 {
let last_byte = message[full_bytes];
padded_message[full_bytes] = last_byte & ((1 << remaining_bits) - 1);
}

// Set the n+1'th bit to high
padded_message[msg_len_bits / 8] |= 1 << (7 - (msg_len_bits % 8));

// Convert the length to a byte array and copy it into the padded message
let n_bytes = (msg_len_bits as u128).to_le_bytes(); // message length in little-endian
padded_message[padded_len - 12..].copy_from_slice(&n_bytes[0..12]);

padded_message
}

fn divide_into_blocks(padded_message: &[u8], state_size: usize) -> Vec<&[u8]> {
padded_message.chunks(state_size / 8).collect()
}

fn truncate(block: &[u8], n: usize) -> Vec<u8> {
let bytes_to_keep = n / 8;
let start_index = if block.len() > bytes_to_keep {
block.len() - bytes_to_keep
} else {
0
};
block[start_index..].to_vec()
}

pub fn hash(message: Vec<u8>, length: Option<usize>) -> Result<Vec<u8>, &'static str> {
let mut message = message;
let message_length: usize;
if let Some(len) = length {
if len > message.len() * 8 {
return Err("Message length is less than the provided length");
}

let mut trimmed_message = message[..(len / 8)].to_vec();

if len % 8 != 0 {
let extra_byte = message[len / 8];
let extra_bits = len % 8;
let mask = 0xFF << (8 - extra_bits);
trimmed_message.push(extra_byte & mask);
}

message = trimmed_message;
message_length = len;
} else {
message_length = message.len() * 8;
}

let padded_message = pad_message(&message, message_length, STATE_SIZE);

let blocks = divide_into_blocks(&padded_message, STATE_SIZE);

let mut init_vector: Vec<u8> = vec![0; STATE_SIZE / 8];
init_vector[0] = 0x80; // set the first bit of this init vector to high

let fin_vector = sub_units::plant(blocks, &init_vector);

let hash = truncate(&fin_vector, HASH_SIZE);

Ok(hash)
}
8 changes: 8 additions & 0 deletions kupyna/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
fn main() {
let message = b"Hello, World!".to_vec();
let _message_length = 0;

let hash_code = kupyna::hash(message, None).unwrap();

println!("{:02X?}", hash_code);
}
34 changes: 34 additions & 0 deletions kupyna/src/sub_units/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
mod t_xor_plus;

use t_xor_plus::{t_plus_l, t_xor_l};

const ROUNDS: usize = 14;

fn xor_bytes(a: &[u8], b: &[u8]) -> Vec<u8> {
a.iter().zip(b.iter()).map(|(x, y)| x ^ y).collect()
}

fn silo(message_block: &[u8], prev_vector: &[u8]) -> Vec<u8> {
let m_xor_p = xor_bytes(message_block, prev_vector);

let t_xor_mp = t_xor_l(&m_xor_p, ROUNDS);

let t_plus_m = t_plus_l(message_block, ROUNDS);

xor_bytes(&(xor_bytes(&t_xor_mp, &t_plus_m)), prev_vector)
}

pub(crate) fn plant(message_blocks: Vec<&[u8]>, init_vector: &[u8]) -> Vec<u8> {
let mut last_vector = init_vector.to_vec();

for block in message_blocks {
last_vector = silo(block, &last_vector);
}

finalize(&last_vector)
}

fn finalize(ult_processed_block: &[u8]) -> Vec<u8> {
let t_xor_ult_processed_block = t_xor_l(ult_processed_block, ROUNDS);
xor_bytes(ult_processed_block, &t_xor_ult_processed_block)
}
Loading
Loading