Skip to content

Commit

Permalink
refactor z85 encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
snopf committed Feb 16, 2020
1 parent 4b9c503 commit 69fd5ec
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 73 deletions.
32 changes: 11 additions & 21 deletions src/avr/z85/z85.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,21 @@ static const char z85_map[85 + 1] PROGMEM = {
"*?&<>()[]{"
"}@%$#"};

static const uint32_t z85_divisor[5] PROGMEM = {
52200625, 614125, 7225, 85, 1
// 85*85*85*85, 85*85*85, 85*85, 85, 1
// Using 85*85*85*85 directly does not work as GCC interprets this
// as a int8_t multiplication
};

void z85_encode_chunk(char z85_encoded[5], const uint8_t sha256_hash[4])
void z85_encode_chunk(char z85_encoded[5], const uint8_t* chunk)
{
static uint32_t value;
static uint32_t divisor;

value = 0;
for (uint8_t i = 0; i < 4; i++) {
value = value * 256 + sha256_hash[i];
}

for (uint8_t i = 0; i < 5; i++) {
// Z85 uses big endian encoded uint32
uint32_t val = (uint32_t)chunk[0] << 24;
val |= (uint32_t)chunk[1] << 16;
val |= (uint16_t)chunk[2] << 8;
val |= chunk[3];

for (int8_t i = 4; i >= 0; i--) {
#ifdef TEST_NO_MCU
divisor = z85_divisor[i];
z85_encoded[i] = z85_map[value / divisor % 85];
z85_encoded[i] = z85_map[val % 85];
#else
divisor = pgm_read_dword(&z85_divisor[i]);
z85_encoded[i] =
pgm_read_byte(&(z85_map[(uint8_t)(value / divisor % 85)]));
pgm_read_byte(&(z85_map[(uint8_t)(val % 85)]));
#endif
val /= 85;
}
}
2 changes: 1 addition & 1 deletion src/avr/z85/z85.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
*/

// Z85 encode the sha256_hash chunk of 4 bytes length
void z85_encode_chunk(char z85_encoded[5], const uint8_t sha256_hash[4]);
void z85_encode_chunk(char z85_encoded[5], const uint8_t* chunk);

#endif
68 changes: 17 additions & 51 deletions src/avr/z85/z85_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,73 +4,39 @@
# License: GNU GPL v2 (see License.txt)

from ctypes import *
import numpy as np
import os
import builtins

# Python Z85 implementation
import zmq.utils.z85 as zmq_z85

# Z85 Reference C implementation
ref_z85 = CDLL("./z85_ref.so")
ref_z85 = CDLL('./z85_ref.so')
ref_z85_encode = ref_z85.Z85_encode
ref_z85_encode.restype = c_char_p

# Own Z85 implementation
z85 = CDLL("./z85.so")
z85 = CDLL('./z85.so')

def check_pw_reqs(pw, length=None):
"""Check if the password includes lower letters, capital letters,
numbers and special characters"""
z85_special_chars = ".-:+=^!/*?&<>()[]{}@%$#"
def test_z85_encoding(num):
'''
Test the Z85 encoding routine against the reference implementation in C
and the reference implementation in Python
'''
rand_inp = os.urandom(4 * num)

pw = pw[:length]
ref_py = zmq_z85.encode(rand_inp)
ref_c = ref_z85_encode(rand_inp, num * 4)

return (builtins.any(c.islower() for c in pw)
and builtins.any(c.isupper() for c in pw)
and builtins.any(c.isdigit() for c in pw)
and builtins.any(c in z85_special_chars for c in pw))

v_check_pw_reqs = np.vectorize(check_pw_reqs)

def test_passwords_lengths(num):
"""Create a number of random passwords and check how many fulfill
the requirements for a given password length."""
random_bytes = [os.urandom(40) for i in range(num)]
outp_buffer = (c_char * (num * 5))()

passwords = [zmq_z85.encode(h) for h in random_bytes]
[z85.z85_encode_chunk(byref(outp_buffer, k*5), rand_inp[4*k:4*k+4])
for k in range(num)]

# Count the number of passes for every password length
num_passes =[]
# Count the max number of rehashes needed to fulfill the requirements
indices = np.arange(num)
# max password length is 32/4 * 5 == 40 and minimum requirement is length
# 4 to include all kind of characters
for i in range(4, 41):
passes = v_check_pw_reqs(passwords, i)
num_passes.append(sum(passes))

return num_passes

def test_z85_encoding(num):
"""Test the Z85 encoding routine against the reference implementation in C
and the reference implementation in Python"""
for i in range(num):
num_4_byte_chunks = np.random.randint(1, 15)
z85_buffer = (c_char * (num_4_byte_chunks * 5))()
inp_vector = os.urandom(4 * num_4_byte_chunks)

ref_zmq = zmq_z85.encode(inp_vector)

ref_z85 = ref_z85_encode(inp_vector, num_4_byte_chunks * 4)

[z85.z85_encode_chunk(byref(z85_buffer, k*5), inp_vector[4*k:])
for k in range(num_4_byte_chunks)]

assert z85_buffer[:] == ref_zmq == ref_z85

print("Test passed")
assert outp_buffer[:] == ref_py == ref_c

print('Test passed')


if __name__ == "__main__":
if __name__ == '__main__':
test_z85_encoding(10000)

0 comments on commit 69fd5ec

Please sign in to comment.