Skip to content

Implement PBKDF2, Scrypt, and HMAC #8989

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

Closed
wants to merge 9 commits into from
128 changes: 127 additions & 1 deletion src/libextra/crypto/cryptoutil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,112 @@ pub fn read_u32v_le(dst: &mut[u32], input: &[u8]) {
}
}

/// Read the value of a vector of bytes as a u32 value in little-endian format.
pub fn read_u32_le(input: &[u8]) -> u32 {
use std::cast::transmute;
use std::unstable::intrinsics::to_le32;
assert!(input.len() == 4);
unsafe {
let tmp: *i32 = transmute(input.unsafe_ref(0));
return to_le32(*tmp) as u32;
}
}

/// Read the value of a vector of bytes as a u32 value in big-endian format.
pub fn read_u32_be(input: &[u8]) -> u32 {
use std::cast::transmute;
use std::unstable::intrinsics::to_be32;
assert!(input.len() == 4);
unsafe {
let tmp: *i32 = transmute(input.unsafe_ref(0));
return to_be32(*tmp) as u32;
}
}


#[cfg(target_arch = "x86")]
#[cfg(target_arch = "x86_64")]
#[inline(never)]
unsafe fn fixed_time_eq_asm(mut lhsp: *u8, mut rhsp: *u8, mut count: uint) -> bool {
use std::unstable::intrinsics::uninit;

let mut result: u8 = 0;
let mut tmp: u8 = uninit();

asm!(
"
fixed_time_eq_loop:

mov ($1), $4
xor ($2), $4
or $4, $0

inc $1
inc $2
dec $3
jnz fixed_time_eq_loop
"
: "=&r" (result), "=&r" (lhsp), "=&r" (rhsp), "=&r" (count), "=&r" (tmp) // output
: "0" (result), "1" (lhsp), "2" (rhsp), "3" (count), "4" (tmp) // input
: "cc" // clobbers
: // flags
);

return result == 0;
}

#[cfg(target_arch = "arm")]
#[inline(never)]
unsafe fn fixed_time_eq_asm(mut lhsp: *u8, mut rhsp: *u8, mut count: uint) -> bool {
use std::unstable::intrinsics::uninit;

let mut result: u8 = 0;
let mut tmp1: u8 = uninit();
let mut tmp2: u8 = uninit();

asm!(
"
fixed_time_eq_loop:

ldrb $4, [$1]
ldrb $5, [$2]
eor $4, $4, $5
orr $0, $0, $4

add $1, $1, #1
add $2, $2, #1
subs $3, $3, #1
bne fixed_time_eq_loop
"
// output
: "=&r" (result), "=&r" (lhsp), "=&r" (rhsp), "=&r" (count), "=&r" (tmp1), "=&r" (tmp2)
: "0" (result), "1" (lhsp), "2" (rhsp), "3" (count), "4" (tmp1), "5" (tmp2) // input
: "cc" // clobbers
: // flags
);

return result == 0;
}

/// Compare two vectors using a fixed number of operations. If the two vectors are not of equal
/// length, the function returns false immediately.
pub fn fixed_time_eq(lhs: &[u8], rhs: &[u8]) -> bool {
if lhs.len() != rhs.len() {
return false;
}
if lhs.len() == 0 {
return true;
}

let count = lhs.len();

unsafe {
let lhsp = lhs.unsafe_ref(0);
let rhsp = rhs.unsafe_ref(0);
return fixed_time_eq_asm(lhsp, rhsp, count);
}
}


trait ToBits {
/// Convert the value in bytes to the number of bits, a tuple where the 1st item is the
Expand Down Expand Up @@ -351,7 +457,7 @@ mod test {
use std::rand::RngUtil;
use std::vec;

use cryptoutil::{add_bytes_to_bits, add_bytes_to_bits_tuple};
use cryptoutil::{add_bytes_to_bits, add_bytes_to_bits_tuple, fixed_time_eq};
use digest::Digest;

/// Feed 1,000,000 'a's into the digest with varying input sizes and check that the result is
Expand Down Expand Up @@ -423,4 +529,24 @@ mod test {
let value: u64 = Bounded::max_value();
add_bytes_to_bits_tuple::<u64>((value - 1, 0), 0x8000000000000000);
}

#[test]
pub fn test_fixed_time_eq() {
let a = [0, 1, 2];
let b = [0, 1, 2];
let c = [0, 1, 9];
let d = [9, 1, 2];
let e = [2, 1, 0];
let f = [2, 2, 2];
let g = [0, 0, 0];

assert!(fixed_time_eq(a, a));
assert!(fixed_time_eq(a, b));

assert!(!fixed_time_eq(a, c));
assert!(!fixed_time_eq(a, d));
assert!(!fixed_time_eq(a, e));
assert!(!fixed_time_eq(a, f));
assert!(!fixed_time_eq(a, g));
}
}
12 changes: 12 additions & 0 deletions src/libextra/crypto/digest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ pub trait Digest {
*/
fn output_bits(&self) -> uint;

/**
* Get the output size in bytes.
*/
fn output_bytes(&self) -> uint {
return (self.output_bits() + 7) / 8;
}

/**
* Get the block size in bytes.
*/
fn block_size(&self) -> uint;

/**
* Convenience function that feeds a string into a digest.
*
Expand Down
203 changes: 203 additions & 0 deletions src/libextra/crypto/hmac.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

/*!
* This module implements the Hmac function - a Message Authentication Code using a Digest.
*/

use std::vec;

use digest::Digest;
use mac::{Mac, MacResult};

/**
* The Hmac struct represents an Hmac function - a Message Authentication Code using a Digest.
*/
pub struct Hmac<D> {
priv digest: D,
priv i_key: ~[u8],
priv o_key: ~[u8],
priv finished: bool
}

fn derive_key(key: &mut [u8], mask: u8) {
for elem in key.mut_iter() {
*elem ^= mask;
}
}

// The key that Hmac processes must be the same as the block size of the underlying Digest. If the
// provided key is smaller than that, we just pad it with zeros. If its larger, we hash it and then
// pad it with zeros.
fn expand_key<D: Digest>(digest: &mut D, key: &[u8]) -> ~[u8] {
let bs = digest.block_size();
let mut expanded_key = vec::from_elem(bs, 0u8);
if key.len() <= bs {
vec::bytes::copy_memory(expanded_key, key, key.len());
for elem in expanded_key.mut_slice_from(key.len()).mut_iter() {
*elem = 0;
}
} else {
let output_size = digest.output_bytes();
digest.input(key);
digest.result(expanded_key.mut_slice_to(output_size));
digest.reset();
for elem in expanded_key.mut_slice_from(output_size).mut_iter() {
*elem = 0;
}
}
return expanded_key;
}

// Hmac uses two keys derived from the provided key - one by xoring every byte with 0x36 and another
// with 0x5c.
fn create_keys<D: Digest>(digest: &mut D, key: &[u8]) -> (~[u8], ~[u8]) {
let mut i_key = expand_key(digest, key);
let mut o_key = i_key.clone();
derive_key(i_key, 0x36);
derive_key(o_key, 0x5c);
return (i_key, o_key);
}

impl <D: Digest> Hmac<D> {
/**
* Create a new Hmac instance.
*
* # Arguments
* * digest - The Digest to use.
* * key - The key to use.
*
*/
#[experimental="EXPERIMENTAL. USE AT YOUR OWN RISK."]
pub fn new(mut digest: D, key: &[u8]) -> Hmac<D> {
let (i_key, o_key) = create_keys(&mut digest, key);
digest.input(i_key);
return Hmac {
digest: digest,
i_key: i_key,
o_key: o_key,
finished: false
}
}
}

impl <D: Digest> Mac for Hmac<D> {
fn input(&mut self, data: &[u8]) {
assert!(!self.finished);
self.digest.input(data);
}

fn reset(&mut self) {
self.digest.reset();
self.digest.input(self.i_key);
self.finished = false;
}

fn result(&mut self) -> MacResult {
let output_size = self.digest.output_bytes();
let mut code = vec::from_elem(output_size, 0u8);

self.raw_result(code);

return MacResult::new_from_owned(code);
}

fn raw_result(&mut self, output: &mut [u8]) {
if !self.finished {
self.digest.result(output);

self.digest.reset();
self.digest.input(self.o_key);
self.digest.input(output);

self.finished = true;
}

self.digest.result(output);
}

fn output_bytes(&self) -> uint { self.digest.output_bytes() }
}

#[cfg(test)]
mod test {
use mac::{Mac, MacResult};
use hmac::Hmac;
use digest::Digest;
use md5::Md5;

struct Test {
key: ~[u8],
data: ~[u8],
expected: ~[u8]
}

// Test vectors from: http://tools.ietf.org/html/rfc2104

fn tests() -> ~[Test] {
return ~[
Test {
key: ~[0x0b, ..16],
data: "Hi There".as_bytes().to_owned(),
expected: ~[
0x92, 0x94, 0x72, 0x7a, 0x36, 0x38, 0xbb, 0x1c,
0x13, 0xf4, 0x8e, 0xf8, 0x15, 0x8b, 0xfc, 0x9d ]
},
Test {
key: "Jefe".as_bytes().to_owned(),
data: "what do ya want for nothing?".as_bytes().to_owned(),
expected: ~[
0x75, 0x0c, 0x78, 0x3e, 0x6a, 0xb0, 0xb5, 0x03,
0xea, 0xa8, 0x6e, 0x31, 0x0a, 0x5d, 0xb7, 0x38 ]
},
Test {
key: ~[0xaa, ..16],
data: ~[0xdd, ..50],
expected: ~[
0x56, 0xbe, 0x34, 0x52, 0x1d, 0x14, 0x4c, 0x88,
0xdb, 0xb8, 0xc7, 0x33, 0xf0, 0xe8, 0xb3, 0xf6 ]
}
];
}

#[test]
fn test_hmac_md5() {
let tests = tests();
for t in tests.iter() {
let mut hmac = Hmac::new(Md5::new(), t.key);

hmac.input(t.data);
let result = hmac.result();
let expected = MacResult::new(t.expected);
assert!(result == expected);

hmac.reset();

hmac.input(t.data);
let result2 = hmac.result();
let expected2 = MacResult::new(t.expected);
assert!(result2 == expected2);
}
}

#[test]
fn test_hmac_md5_incremental() {
let tests = tests();
for t in tests.iter() {
let mut hmac = Hmac::new(Md5::new(), t.key);
for i in range(0, t.data.len()) {
hmac.input(t.data.slice(i, i + 1));
}
let result = hmac.result();
let expected = MacResult::new(t.expected);
assert!(result == expected);
}
}
}
Loading