Compare commits
No commits in common. "4c8194acd20d9c8380e7116bf47e05c3ca476a5c" and "8c7117fef1386cca36954ce48880b67fb0802e7a" have entirely different histories.
4c8194acd2
...
8c7117fef1
1 changed files with 42 additions and 104 deletions
146
puer.c
146
puer.c
|
@ -11,10 +11,8 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
// Adjusting this will render the file format incompatible
|
// Adjusting this will render the file format incompatible
|
||||||
// KDF_WORKFACTOR must be a power of two between 1 and 2^32
|
// The minimum possible buffer size is 64
|
||||||
#define KDF_BLOCKSIZE 1024
|
unsigned char workbuf[8 * 1024 * 1024];
|
||||||
#define KDF_WORKFACTOR (64 * 1024)
|
|
||||||
unsigned char workbuf[KDF_WORKFACTOR * KDF_BLOCKSIZE];
|
|
||||||
|
|
||||||
void xxtea128(uint32_t const key[4], uint32_t block[4]) {
|
void xxtea128(uint32_t const key[4], uint32_t block[4]) {
|
||||||
// Encryption half of the XXTEA algorithm, with block size limited
|
// Encryption half of the XXTEA algorithm, with block size limited
|
||||||
|
@ -68,7 +66,7 @@ void xxtea128(uint32_t const key[4], uint32_t block[4]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t bytes2word(unsigned char const bytes[4]) {
|
uint32_t bytes2word(unsigned char const bytes[4]) {
|
||||||
return (uint32_t)bytes[0] | (uint32_t)bytes[1]<<8 | (uint32_t)bytes[2]<<16 | (uint32_t)bytes[3]<<24;
|
return bytes[0] | bytes[1]<<8 | bytes[2]<<16 | bytes[3]<<24;
|
||||||
}
|
}
|
||||||
|
|
||||||
void word2bytes(unsigned char *bytes, uint32_t word) {
|
void word2bytes(unsigned char *bytes, uint32_t word) {
|
||||||
|
@ -231,36 +229,25 @@ void finalize_hash(struct hashstate *state, unsigned char hash[32]) {
|
||||||
explicit_bzero(state, sizeof(struct hashstate));
|
explicit_bzero(state, sizeof(struct hashstate));
|
||||||
}
|
}
|
||||||
|
|
||||||
void pbkdf2_1_block(unsigned char output[32], unsigned char passphrase[], size_t passphraselen, unsigned char salt[], size_t saltlen, uint32_t blockindex) {
|
void hmac(unsigned char output[32], unsigned char key[], size_t keylen, unsigned char message[], size_t messagelen) {
|
||||||
// NOTE: This implementation is hardcoded to one round, as required
|
// The blocksize of the underlying has function is 128 bits (16B)
|
||||||
// by the MFcrypt (see Stronger Key Derivation Via Sequential
|
|
||||||
// Memory-hard Functions by Colin Percival) algorithm. This is not
|
|
||||||
// suitable as a general purpose password-based KDF.
|
|
||||||
|
|
||||||
// This is equivalent to
|
|
||||||
// F(Password, Salt, 1, i)
|
|
||||||
// = U_1
|
|
||||||
// = PRF(Password, Salt + INT_32_BE(i))
|
|
||||||
// We use HMAC-MDC2-XXTEA128 as our PRF
|
|
||||||
|
|
||||||
// The blocksize of the underlying hash function is 128 bits (16B)
|
|
||||||
// but HMAC is specified assuming that the hash function output (in
|
// but HMAC is specified assuming that the hash function output (in
|
||||||
// our case 256 bits or 32B) fits in one block. As far as I can
|
// our case 256 bits or 32B) fits in one block. As far as I can
|
||||||
// tell extending the key to be two blocks long is not a problem.
|
// tell extending the key to be two blocks long is not a problem.
|
||||||
|
|
||||||
unsigned char padded_key[32];
|
unsigned char padded_key[32];
|
||||||
if (passphraselen > 16) {
|
if (keylen > 16) {
|
||||||
// We hash it even if it is shorter than our extended key
|
// We hash it even if it is shorter than our extended key
|
||||||
// length to avoid giving attacker any funny surfaces to
|
// length to avoid giving attacker any funny surfaces to
|
||||||
// play with at the interface of two blocks
|
// play with at the interface of two blocks
|
||||||
struct hashstate state;
|
struct hashstate state;
|
||||||
initialize_hash(&state);
|
initialize_hash(&state);
|
||||||
feed_hash(&state, passphrase, passphraselen);
|
feed_hash(&state, key, keylen);
|
||||||
finalize_hash(&state, padded_key);
|
finalize_hash(&state, padded_key);
|
||||||
} else {
|
} else {
|
||||||
// Copy the key and zero-pad if necessary
|
// Copy the key and zero-pad if necessary
|
||||||
memset(padded_key, 0, 32);
|
memset(padded_key, 0, 32);
|
||||||
memcpy(padded_key, passphrase, passphraselen);
|
memcpy(padded_key, key, keylen);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Outer and inner key derivation
|
// Outer and inner key derivation
|
||||||
|
@ -275,14 +262,7 @@ void pbkdf2_1_block(unsigned char output[32], unsigned char passphrase[], size_t
|
||||||
struct hashstate state;
|
struct hashstate state;
|
||||||
initialize_hash(&state);
|
initialize_hash(&state);
|
||||||
feed_hash(&state, inner_key, 32);
|
feed_hash(&state, inner_key, 32);
|
||||||
// Our message is salt plus big endian encoding of blockindex
|
feed_hash(&state, message, messagelen);
|
||||||
feed_hash(&state, salt, saltlen);
|
|
||||||
unsigned char be_blockindex[4];
|
|
||||||
be_blockindex[0] = blockindex >> 24;
|
|
||||||
be_blockindex[1] = blockindex >> 16;
|
|
||||||
be_blockindex[2] = blockindex >> 8;
|
|
||||||
be_blockindex[3] = blockindex;
|
|
||||||
feed_hash(&state, be_blockindex, 4);
|
|
||||||
finalize_hash(&state, inner_hash);
|
finalize_hash(&state, inner_hash);
|
||||||
|
|
||||||
// Outer hash
|
// Outer hash
|
||||||
|
@ -292,87 +272,45 @@ void pbkdf2_1_block(unsigned char output[32], unsigned char passphrase[], size_t
|
||||||
finalize_hash(&state, output);
|
finalize_hash(&state, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mfcrypt_hash(unsigned char chunk[16]) {
|
#define KDF_ROUNDS (sizeof(workbuf) / 32)
|
||||||
uint32_t key[4], words[4];
|
|
||||||
block2words(key, chunk);
|
|
||||||
block2words(words, chunk);
|
|
||||||
xxtea128(key, words);
|
|
||||||
words2block(chunk, words);
|
|
||||||
}
|
|
||||||
|
|
||||||
void blockmix(unsigned char block[KDF_BLOCKSIZE]) {
|
void kdf(unsigned char key[16], unsigned char salt[32], unsigned char passphrase[], size_t passphraselen) {
|
||||||
// r = KDF_BLOCKSIZE / 32, since block is 2r times the width of our
|
// This is based on the design of PBKDF2 but aims to be memory hard
|
||||||
// hash function (xxtea128)
|
// This is achieved by storing all the hashes in a buffer and the
|
||||||
const size_t r = KDF_BLOCKSIZE / 32;
|
// in the end hashing them together in reverse order, instead of
|
||||||
|
// just xoring together.
|
||||||
|
//
|
||||||
|
// The memory-hardness of this scheme rests of the assumption that
|
||||||
|
// it is not feasible to compute the final hash backwards, that is,
|
||||||
|
// starting with the first hash and working towards the final hash.
|
||||||
|
// While I cannot prove this to be the case, the fact that our hash
|
||||||
|
// is made out of a one-way compression function makes me
|
||||||
|
// relatively confident in it.
|
||||||
|
|
||||||
// accumulator (X) starts off as chunk 2r-1. Chunk k is at memory
|
// Place the hash of the salt at the top of the buffer. We do not
|
||||||
// location 16*k and is 16 bytes long. Substituting we get:
|
// include the counter i from PBKDF2 since we will ever only
|
||||||
// start = 16*(2*(KDF_BLOCKSIZE / 32) - 1)
|
// produce one block of output
|
||||||
// start = 16*(KDF_BLOCKSIZE / 16 - 1)
|
size_t index = KDF_ROUNDS*32 - 32;
|
||||||
// start = KDF_BLOCKSIZE - 16
|
hmac(&workbuf[index], passphrase, passphraselen, salt, 32);
|
||||||
unsigned char accumulator[16];
|
index -= 32;
|
||||||
memcpy(accumulator, &block[16 * (2*r - 1)], 16);
|
|
||||||
|
|
||||||
// Chunk i is at memory location 16*i. We go through chunks < 2r
|
// Walk back along the buffer, at each step hashing the previous
|
||||||
unsigned char hashedchunks[KDF_BLOCKSIZE];
|
// hashes
|
||||||
for (size_t i = 0; i < 2*r; i++) {
|
while (index > 0) {
|
||||||
// X = H(X xor B_i)
|
hmac(&workbuf[index], passphrase, passphraselen, &workbuf[index+32], 32);
|
||||||
for (size_t index = 0; index < 16; index++) {
|
index -= 32;
|
||||||
accumulator[index] ^= block[16 * i + index];
|
|
||||||
}
|
|
||||||
mfcrypt_hash(accumulator);
|
|
||||||
// Y_i = X
|
|
||||||
memcpy(&hashedchunks[16 * i], accumulator, 16);
|
|
||||||
}
|
}
|
||||||
|
hmac(workbuf, passphrase, passphraselen, &workbuf[32], 32);
|
||||||
|
|
||||||
// Interleave the blocks back into the buffer. We go through B's
|
// Perform the final hash
|
||||||
// chunks < r which corresponds to indices every 16 bytes smaller
|
unsigned char final_hash[32];
|
||||||
// than 16*(KDF_BLOCKSIZE / 32) = KDF_BLOCKSIZE / 2
|
hmac(final_hash, passphrase, passphraselen, workbuf, KDF_ROUNDS * 32);
|
||||||
size_t i = 0;
|
|
||||||
for (; i < r; i++) {
|
|
||||||
// B_i = Y_{2*i}
|
|
||||||
memcpy(&block[16*i], &hashedchunks[16*2*i], 16);
|
|
||||||
}
|
|
||||||
// Now we go through B's chunks < 2r but >= r
|
|
||||||
for (; i < 2*r; i++) {
|
|
||||||
// B_i = Y_{2*(i - r) + 1}
|
|
||||||
memcpy(&block[16*i], &hashedchunks[16*(2*(i - r) + 1)], 16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void romix(unsigned char block[KDF_BLOCKSIZE]) {
|
// Use first 128 bits of final hash as the key
|
||||||
// Block i starts at location KDF_BLOCKSIZE * i
|
memcpy(key, final_hash, 16);
|
||||||
for (size_t i = 0; i < KDF_WORKFACTOR; i++) {
|
|
||||||
// V_i = X
|
|
||||||
memcpy(&workbuf[KDF_BLOCKSIZE * i], block, KDF_BLOCKSIZE);
|
|
||||||
// X = H(X)
|
|
||||||
blockmix(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < sizeof(workbuf) / KDF_BLOCKSIZE; i++) {
|
// Empty the buffer
|
||||||
// j = Integrify(X) mod N
|
explicit_bzero(workbuf, sizeof(workbuf));
|
||||||
// N is a power of two
|
|
||||||
uint32_t j = bytes2word(&block[KDF_BLOCKSIZE - 4]) & (KDF_WORKFACTOR - 1);
|
|
||||||
// X = H(X xor V_j)
|
|
||||||
for (size_t index = 0; index < KDF_BLOCKSIZE; index++) {
|
|
||||||
block[index] ^= workbuf[KDF_BLOCKSIZE * j + index];
|
|
||||||
}
|
|
||||||
blockmix(block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void kdf(unsigned char key[16], unsigned char passphrase[], size_t passphraselen, unsigned char salt[32]) {
|
|
||||||
unsigned char block[KDF_BLOCKSIZE];
|
|
||||||
for (size_t i = 0; i < KDF_BLOCKSIZE / 32; i++) {
|
|
||||||
pbkdf2_1_block(&block[i * 32], passphrase, passphraselen, salt, 32, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
romix(block);
|
|
||||||
|
|
||||||
unsigned char result[32];
|
|
||||||
pbkdf2_1_block(result, passphrase, passphraselen, block, KDF_BLOCKSIZE, 0);
|
|
||||||
|
|
||||||
memcpy(key, result, 16);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 16 bit authentication tag
|
// 16 bit authentication tag
|
||||||
|
@ -790,7 +728,7 @@ int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
// Derive key
|
// Derive key
|
||||||
unsigned char key[16];
|
unsigned char key[16];
|
||||||
kdf(key, passphrase, passphrase_len, salt);
|
kdf(key, salt, passphrase, passphrase_len);
|
||||||
explicit_bzero(passphrase, sizeof(passphrase));
|
explicit_bzero(passphrase, sizeof(passphrase));
|
||||||
|
|
||||||
uint64_t messageindex = 0;
|
uint64_t messageindex = 0;
|
||||||
|
|
Loading…
Reference in a new issue