diff --git a/src/utility.c b/src/utility.c index 53bc340d..28e91939 100644 --- a/src/utility.c +++ b/src/utility.c @@ -633,32 +633,27 @@ lxw_version_id(void) } /* - * Hash a worksheet password. Based on the algorithm provided by Daniel Rentz - * of OpenOffice. + * Hash a worksheet password. Based on the algorithm in ECMA-376-4:2016, + * Office Open XML File Formats - Transitional Migration Features, + * Additional attributes for workbookProtection element (Part 1, ยง18.2.29). */ uint16_t lxw_hash_password(const char *password) { - size_t count; - size_t i; - uint16_t hash = 0x0000; + uint16_t byte_count = strlen(password); + uint16_t hash = 0; + const char *p = &password[byte_count]; - count = strlen(password); + if (!byte_count) + return hash; - for (i = 0; i < (uint8_t) count; i++) { - uint32_t low_15; - uint32_t high_15; - uint32_t letter = password[i] << (i + 1); - - low_15 = letter & 0x7fff; - high_15 = letter & (0x7fff << 15); - high_15 = high_15 >> 15; - letter = low_15 | high_15; - - hash ^= letter; + while (p-- != password) { + hash = ((hash >> 14) & 0x01) | ((hash << 1) & 0x7fff); + hash ^= *p & 0xFF; } - hash ^= count; + hash = ((hash >> 14) & 0x01) | ((hash << 1) & 0x7fff); + hash ^= byte_count; hash ^= 0xCE4B; return hash; diff --git a/test/unit/utility/test_hash_password.c b/test/unit/utility/test_hash_password.c new file mode 100644 index 00000000..f6558042 --- /dev/null +++ b/test/unit/utility/test_hash_password.c @@ -0,0 +1,46 @@ +/* + * Tests for the libxlsxwriter library. + * + * Copyright 2014-2022, John McNamara, jmcnamara@cpan.org + * + */ + +#include "../ctest.h" +#include "../helper.h" + +#include "../../../include/xlsxwriter/utility.h" + + +// Test lxw_hash_password(). +CTEST(utility, lxw_hash_password) { + + ASSERT_EQUAL(0x83AF, lxw_hash_password("password")); + ASSERT_EQUAL(0xD14E, lxw_hash_password("This is a longer phrase")); + ASSERT_EQUAL(0xCE2A, lxw_hash_password("0")); + ASSERT_EQUAL(0xCEED, lxw_hash_password("01")); + ASSERT_EQUAL(0xCF7C, lxw_hash_password("012")); + ASSERT_EQUAL(0xCC4B, lxw_hash_password("0123")); + ASSERT_EQUAL(0xCACA, lxw_hash_password("01234")); + ASSERT_EQUAL(0xC789, lxw_hash_password("012345")); + ASSERT_EQUAL(0xDC88, lxw_hash_password("0123456")); + ASSERT_EQUAL(0xEB87, lxw_hash_password("01234567")); + ASSERT_EQUAL(0x9B86, lxw_hash_password("012345678")); + ASSERT_EQUAL(0xFF84, lxw_hash_password("0123456789")); + ASSERT_EQUAL(0xFF86, lxw_hash_password("01234567890")); + ASSERT_EQUAL(0xEF87, lxw_hash_password("012345678901")); + ASSERT_EQUAL(0xAF8A, lxw_hash_password("0123456789012")); + ASSERT_EQUAL(0xEF90, lxw_hash_password("01234567890123")); + ASSERT_EQUAL(0xEFA5, lxw_hash_password("012345678901234")); + ASSERT_EQUAL(0xEFD0, lxw_hash_password("0123456789012345")); + ASSERT_EQUAL(0xEF09, lxw_hash_password("01234567890123456")); + ASSERT_EQUAL(0xEEB2, lxw_hash_password("012345678901234567")); + ASSERT_EQUAL(0xED33, lxw_hash_password("0123456789012345678")); + ASSERT_EQUAL(0xEA14, lxw_hash_password("01234567890123456789")); + ASSERT_EQUAL(0xE615, lxw_hash_password("012345678901234567890")); + ASSERT_EQUAL(0xFE96, lxw_hash_password("0123456789012345678901")); + ASSERT_EQUAL(0xCC97, lxw_hash_password("01234567890123456789012")); + ASSERT_EQUAL(0xAA98, lxw_hash_password("012345678901234567890123")); + ASSERT_EQUAL(0xFA98, lxw_hash_password("0123456789012345678901234")); + ASSERT_EQUAL(0xD298, lxw_hash_password("01234567890123456789012345")); + ASSERT_EQUAL(0xD2D3, lxw_hash_password("0123456789012345678901234567890")); +}