// HKDF-SHA512 (RFC 5869) and PBKDF2-HMAC-SHA512 (RFC 8018).
//
// HKDF turns a secret of high entropy into a key of any length; PBKDF2
// turns a passphrase into a key, at the cost of a deliberate slowness that
// makes a dictionary attack expensive.
//
// The reference implementation this package follows uses HKDF-SHA512 for the
// key of a container and PBKDF2 for the passphrase that protects a secret
// key on disk, so both are here.
///|
/// The length of a SHA-512 digest.
pub const SHA512_BYTES : Int32 = 64
///|
/// The HKDF extract step of RFC 5869: PRK = HMAC-Hash(salt, IKM).
pub fn hkdf_extract_sha512(salt : Bytes, ikm : Bytes) -> Bytes {
hmac_sha512(salt, ikm)
}
///|
/// The HKDF expand step of RFC 5869: OKM = T(1) || T(2) || ...
///
/// T(0) is empty and T(i) = HMAC-Hash(PRK, T(i-1) || info || i), where the
/// counter is a single byte.
pub fn hkdf_expand_sha512(prk : Bytes, info : Bytes, length : Int32) -> Bytes {
let out = FixedArray::make(length, b'\x00')
let mut written = 0
let mut counter = 1
let mut previous = zero_bytes(0)
while written < length {
let block_input = bytes_concat(
bytes_concat(previous, info),
Bytes::from_array([counter.to_byte()]),
)
let block = hmac_sha512(prk, block_input)
let take = if length - written < SHA512_BYTES {
length - written
} else {
SHA512_BYTES
}
let mut i = 0
while i < take {
out[written + i] = block.at(i)
i = i + 1
}
written = written + take
previous = block
counter = counter + 1
}
Bytes::from_array(out[:])
}
///|
/// HKDF-SHA512: the extract step followed by the expand step.
///
/// A salt of zero length is the same as no salt at all: RFC 5869 says that
/// a string of HashLen zeros is used then.
pub fn hkdf_sha512(
ikm : Bytes,
salt : Bytes,
info : Bytes,
length : Int32,
) -> Bytes {
let real_salt = if salt.length() == 0 {
zero_bytes(SHA512_BYTES)
} else {
salt
}
let prk = hkdf_extract_sha512(real_salt, ikm)
hkdf_expand_sha512(prk, info, length)
}
///|
/// PBKDF2-HMAC-SHA512 (RFC 8018): a key of `length` bytes from a passphrase.
///
/// The result is the concatenation of T(1) || T(2) || ..., where
/// T(i) = U(1) xor U(2) xor ... xor U(iterations) and
/// U(1) = HMAC(password, salt || INT_BE32(i)) and U(j) = HMAC(password, U(j-1)).
pub fn pbkdf2_sha512(
password : Bytes,
salt : Bytes,
iterations : Int32,
length : Int32,
) -> Bytes {
let out = FixedArray::make(length, b'\x00')
let mut written = 0
let mut block_index = 1
while written < length {
let counter = Bytes::from_array([
((block_index >> 24) & 0xff).to_byte(),
((block_index >> 16) & 0xff).to_byte(),
((block_index >> 8) & 0xff).to_byte(),
(block_index & 0xff).to_byte(),
])
let mut u = hmac_sha512(password, bytes_concat(salt, counter))
let acc = FixedArray::make(SHA512_BYTES, b'\x00')
let mut i = 0
while i < SHA512_BYTES {
acc[i] = u.at(i)
i = i + 1
}
let mut iteration = 1
while iteration < iterations {
u = hmac_sha512(password, u)
i = 0
while i < SHA512_BYTES {
acc[i] = (acc[i].to_int() ^ u.at(i).to_int()).to_byte()
i = i + 1
}
iteration = iteration + 1
}
let take = if length - written < SHA512_BYTES {
length - written
} else {
SHA512_BYTES
}
i = 0
while i < take {
out[written + i] = acc[i]
i = i + 1
}
written = written + take
block_index = block_index + 1
}
Bytes::from_array(out[:])
}