mirror of
https://github.com/odin-lang/Odin.git
synced 2026-04-20 05:20:28 +00:00
core/crypto/pbkdf2: Initial import
This commit is contained in:
122
core/crypto/pbkdf2/pbkdf2.odin
Normal file
122
core/crypto/pbkdf2/pbkdf2.odin
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
package pbkdf2 implements the PBKDF2 password-based key derivation function.
|
||||
|
||||
See: https://www.rfc-editor.org/rfc/rfc2898
|
||||
*/
|
||||
package pbkdf2
|
||||
|
||||
import "core:crypto/hash"
|
||||
import "core:crypto/hmac"
|
||||
import "core:encoding/endian"
|
||||
import "core:mem"
|
||||
|
||||
// derive invokes PBKDF2-HMAC with the specified hash algorithm, password,
|
||||
// salt, iteration count, and outputs the derived key to dst.
|
||||
derive :: proc(
|
||||
hmac_hash: hash.Algorithm,
|
||||
password: []byte,
|
||||
salt: []byte,
|
||||
iterations: u32,
|
||||
dst: []byte,
|
||||
) {
|
||||
h_len := hash.DIGEST_SIZES[hmac_hash]
|
||||
|
||||
// 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long"
|
||||
// and stop.
|
||||
|
||||
dk_len := len(dst)
|
||||
switch {
|
||||
case dk_len == 0:
|
||||
return
|
||||
case u64(dk_len) > u64(max(u32)) * u64(h_len):
|
||||
// This is so beyond anything that is practical or reasonable,
|
||||
// so just panic instead of returning an error.
|
||||
panic("crypto/pbkdf2: derived key too long")
|
||||
case:
|
||||
}
|
||||
|
||||
// 2. Let l be the number of hLen-octet blocks in the derived key,
|
||||
// rounding up, and let r be the number of octets in the last block.
|
||||
|
||||
l := dk_len / h_len // Don't need to round up.
|
||||
r := dk_len % h_len
|
||||
|
||||
// 3. For each block of the derived key apply the function F defined
|
||||
// below to the password P, the salt S, the iteration count c, and
|
||||
// the block index to compute the block.
|
||||
//
|
||||
// 4. Concatenate the blocks and extract the first dkLen octets to
|
||||
// produce a derived key DK.
|
||||
//
|
||||
// 5. Output the derived key DK.
|
||||
|
||||
// Each iteration of F is always `PRF (P, ...)`, so instantiate the
|
||||
// PRF, and clone since memcpy is faster than having to re-initialize
|
||||
// HMAC repeatedly.
|
||||
|
||||
base: hmac.Context
|
||||
defer hmac.reset(&base)
|
||||
|
||||
hmac.init(&base, hmac_hash, password)
|
||||
|
||||
// Process all of the blocks that will be written directly to dst.
|
||||
dst_blk := dst
|
||||
for i in 1 ..= l { // F expects i starting at 1.
|
||||
_F(&base, salt, iterations, u32(i), dst_blk[:h_len])
|
||||
dst_blk = dst_blk[h_len:]
|
||||
}
|
||||
|
||||
// Instead of rounding l up, just proceass the one extra block iff
|
||||
// r != 0.
|
||||
if r > 0 {
|
||||
tmp: [hash.MAX_DIGEST_SIZE]byte
|
||||
blk := tmp[:h_len]
|
||||
defer mem.zero_explicit(raw_data(blk), h_len)
|
||||
|
||||
_F(&base, salt, iterations, u32(l + 1), blk)
|
||||
copy(dst_blk, blk)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
_F :: proc(base: ^hmac.Context, salt: []byte, c: u32, i: u32, dst_blk: []byte) {
|
||||
h_len := len(dst_blk)
|
||||
|
||||
tmp: [hash.MAX_DIGEST_SIZE]byte
|
||||
u := tmp[:h_len]
|
||||
defer mem.zero_explicit(raw_data(u), h_len)
|
||||
|
||||
// F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c
|
||||
//
|
||||
// where
|
||||
//
|
||||
// U_1 = PRF (P, S || INT (i)) ,
|
||||
// U_2 = PRF (P, U_1) ,
|
||||
// ...
|
||||
// U_c = PRF (P, U_{c-1}) .
|
||||
//
|
||||
// Here, INT (i) is a four-octet encoding of the integer i, most
|
||||
// significant octet first.
|
||||
|
||||
prf: hmac.Context
|
||||
|
||||
// U_1: PRF (P, S || INT (i))
|
||||
hmac.clone(&prf, base)
|
||||
hmac.update(&prf, salt)
|
||||
endian.unchecked_put_u32be(u, i) // Use u as scratch space.
|
||||
hmac.update(&prf, u[:4])
|
||||
hmac.final(&prf, u)
|
||||
copy(dst_blk, u)
|
||||
|
||||
// U_2 ... U_c: U_n = PRF (P, U_(n-1))
|
||||
for _ in 1 ..< c {
|
||||
hmac.clone(&prf, base)
|
||||
hmac.update(&prf, u)
|
||||
hmac.final(&prf, u)
|
||||
|
||||
// XOR dst_blk and u.
|
||||
for v, i in u {
|
||||
dst_blk[i] ~= v
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import hmac "core:crypto/hmac"
|
||||
import keccak "core:crypto/legacy/keccak"
|
||||
import md5 "core:crypto/legacy/md5"
|
||||
import sha1 "core:crypto/legacy/sha1"
|
||||
import pbkdf2 "core:crypto/pbkdf2"
|
||||
import poly1305 "core:crypto/poly1305"
|
||||
import sha2 "core:crypto/sha2"
|
||||
import sha3 "core:crypto/sha3"
|
||||
@@ -149,6 +150,7 @@ _ :: chacha20poly1305
|
||||
_ :: hmac
|
||||
_ :: keccak
|
||||
_ :: md5
|
||||
_ :: pbkdf2
|
||||
_ :: poly1305
|
||||
_ :: sha1
|
||||
_ :: sha2
|
||||
|
||||
@@ -53,6 +53,7 @@ main :: proc() {
|
||||
|
||||
test_hash(&t)
|
||||
test_mac(&t)
|
||||
test_kdf(&t) // After hash/mac tests because those should pass first.
|
||||
|
||||
test_chacha20(&t)
|
||||
test_chacha20poly1305(&t)
|
||||
|
||||
119
tests/core/crypto/test_core_crypto_kdf.odin
Normal file
119
tests/core/crypto/test_core_crypto_kdf.odin
Normal file
@@ -0,0 +1,119 @@
|
||||
package test_core_crypto
|
||||
|
||||
import "core:encoding/hex"
|
||||
import "core:fmt"
|
||||
import "core:testing"
|
||||
|
||||
import "core:crypto/hash"
|
||||
import "core:crypto/pbkdf2"
|
||||
|
||||
@(test)
|
||||
test_kdf :: proc(t: ^testing.T) {
|
||||
log(t, "Testing KDFs")
|
||||
|
||||
test_pbkdf2(t)
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_pbkdf2 :: proc(t: ^testing.T) {
|
||||
log(t, "Testing PBKDF2")
|
||||
|
||||
tmp: [64]byte // 512-bits is enough for every output for now.
|
||||
|
||||
test_vectors := []struct {
|
||||
algo: hash.Algorithm,
|
||||
password: string,
|
||||
salt: string,
|
||||
iterations: u32,
|
||||
dk: string,
|
||||
} {
|
||||
// SHA-1
|
||||
// - https://www.rfc-editor.org/rfc/rfc2898
|
||||
{
|
||||
hash.Algorithm.Insecure_SHA1,
|
||||
"password",
|
||||
"salt",
|
||||
1,
|
||||
"0c60c80f961f0e71f3a9b524af6012062fe037a6",
|
||||
},
|
||||
{
|
||||
hash.Algorithm.Insecure_SHA1,
|
||||
"password",
|
||||
"salt",
|
||||
2,
|
||||
"ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957",
|
||||
},
|
||||
{
|
||||
hash.Algorithm.Insecure_SHA1,
|
||||
"password",
|
||||
"salt",
|
||||
4096,
|
||||
"4b007901b765489abead49d926f721d065a429c1",
|
||||
},
|
||||
// This passes but takes a about 8 seconds on a modern-ish system.
|
||||
//
|
||||
// {
|
||||
// hash.Algorithm.Insecure_SHA1,
|
||||
// "password",
|
||||
// "salt",
|
||||
// 16777216,
|
||||
// "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984",
|
||||
// },
|
||||
{
|
||||
hash.Algorithm.Insecure_SHA1,
|
||||
"passwordPASSWORDpassword",
|
||||
"saltSALTsaltSALTsaltSALTsaltSALTsalt",
|
||||
4096,
|
||||
"3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038",
|
||||
},
|
||||
{
|
||||
hash.Algorithm.Insecure_SHA1,
|
||||
"pass\x00word",
|
||||
"sa\x00lt",
|
||||
4096,
|
||||
"56fa6aa75548099dcc37d7f03425e0c3",
|
||||
},
|
||||
|
||||
// SHA-256
|
||||
// - https://www.rfc-editor.org/rfc/rfc7914
|
||||
{
|
||||
hash.Algorithm.SHA256,
|
||||
"passwd",
|
||||
"salt",
|
||||
1,
|
||||
"55ac046e56e3089fec1691c22544b605f94185216dde0465e68b9d57c20dacbc49ca9cccf179b645991664b39d77ef317c71b845b1e30bd509112041d3a19783",
|
||||
},
|
||||
{
|
||||
hash.Algorithm.SHA256,
|
||||
"Password",
|
||||
"NaCl",
|
||||
80000,
|
||||
"4ddcd8f60b98be21830cee5ef22701f9641a4418d04c0414aeff08876b34ab56a1d425a1225833549adb841b51c9b3176a272bdebba1d078478f62b397f33c8d",
|
||||
},
|
||||
}
|
||||
for v, _ in test_vectors {
|
||||
algo_name := hash.ALGORITHM_NAMES[v.algo]
|
||||
dst := tmp[:len(v.dk) / 2]
|
||||
|
||||
password := transmute([]byte)(v.password)
|
||||
salt := transmute([]byte)(v.salt)
|
||||
|
||||
pbkdf2.derive(v.algo, password, salt, v.iterations, dst)
|
||||
|
||||
dst_str := string(hex.encode(dst, context.temp_allocator))
|
||||
|
||||
expect(
|
||||
t,
|
||||
dst_str == v.dk,
|
||||
fmt.tprintf(
|
||||
"HMAC-%s: Expected: %s for input of (%s, %s, %d), but got %s instead",
|
||||
algo_name,
|
||||
v.dk,
|
||||
v.password,
|
||||
v.salt,
|
||||
v.iterations,
|
||||
dst_str,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user