1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//! Rust implementation of OpenSSL [EVP_bytesToKey] function.
//!
//! `evpkdf` derives key from the given password and salt.
//!
//! Notice that this approach is **too weak** for modern standard
//! now. Newer applications should choice a more modern algorithm
//! like [bcrypt], [pbkdf2] or [scrypt].
//!
//! [EVP_bytesToKey]: https://www.openssl.org/docs/man1.0.2/man3/EVP_BytesToKey.html
//! [bcrypt]: https://crates.io/crates/bcrypt
//! [pbkdf2]: https://crates.io/crates/pbkdf2
//! [scrypt]: https://crates.io/crates/scrypt
//!
//! # Basic Usage
//!
//! ```rust
//! use evpkdf::evpkdf;
//! use hex_literal::hex;
//! use md5::Md5;   // from md-5 crate
//! use sha1::Sha1; // from sha-1 crate
//!
//! let mut output = [];
//!
//! evpkdf::<Md5>(b"password", b"saltsalt", 1000, &mut output);
//!
//! assert_eq!(output, []);
//!
//! let mut output = [0; 128 / 8];
//!
//! evpkdf::<Md5>(b"password", b"saltsalt", 1000, &mut output);
//!
//! assert_eq!(output, hex!("8006de5d2a5d15f9bbdb8f40196d5af1"));
//!
//! let mut output = [0; 128 / 8];
//!
//! evpkdf::<Sha1>(b"password", b"saltsalt", 1000, &mut output);
//!
//! assert_eq!(output, hex!("f8833429b112582447bc66f433497f75"));
//! ```
//!
//! # Compatible with crypto-js
//!
//! Below sinppet generates the same result as
//! `CryptoJS.kdf.OpenSSL.execute('password', 256 / 32, 128 / 32, 'saltsalt')`.
//!
//! ```rust
//! use evpkdf::evpkdf;
//! use hex_literal::hex;
//! use md5::Md5;   // from md-5 crate
//!
//! const KEY_SIZE: usize = 256;
//! const IV_SIZE: usize = 128;
//!
//! let mut output = [0; (KEY_SIZE + IV_SIZE) / 8];
//!
//! evpkdf::<Md5>(b"password", b"saltsalt", 1, &mut output);
//!
//! let (key, iv) = output.split_at(KEY_SIZE / 8);
//!
//! assert_eq!(
//!     key,
//!     hex!("fdbdf3419fff98bdb0241390f62a9db35f4aba29d77566377997314ebfc709f2")
//! );
//!
//! assert_eq!(
//!     iv,
//!     hex!("0b5ca7b1081f94b1ac12e3c8ba87d05a")
//! );
//! ```
//!
//! # License
//!
//! MIT
use digest::Digest;

/// Derives key from the given arguments.
///
/// ```rust
/// use evpkdf::evpkdf;
/// use hex_literal::hex;
/// use md5::Md5;   // from md-5 crate
///
/// let mut output = [0; 128 / 8]; // key size, 128 bits
///
/// evpkdf::<Md5>(
///     b"password", // password
///     b"saltsalt", // salt
///     1000,        // iteration count
///     &mut output
/// );
/// ```
pub fn evpkdf<D: Digest>(pass: &[u8], salt: &[u8], count: usize, output: &mut [u8]) {
    let mut hasher = D::new();
    let mut derived_key = Vec::with_capacity(output.len());
    let mut block = Vec::new();

    while derived_key.len() < output.len() {
        if !block.is_empty() {
            hasher.input(block);
        }
        hasher.input(pass);
        hasher.input(salt.as_ref());
        block = hasher.result_reset().to_vec();

        // avoid subtract with overflow
        if count > 1 {
            for _ in 0..(count - 1) {
                hasher.input(block);
                block = hasher.result_reset().to_vec();
            }
        }

        derived_key.extend_from_slice(&block);
    }

    output.copy_from_slice(&derived_key[0..output.len()]);
}