diff --git a/include/mgba-util/md5.h b/include/mgba-util/md5.h new file mode 100644 index 000000000..9a2c58145 --- /dev/null +++ b/include/mgba-util/md5.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2013-2014 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Based on https://github.com/Zunawe/md5-c + * Originally released under the Unlicense */ +#ifndef MD5_H +#define MD5_H + +#include + +CXX_GUARD_START + +struct MD5Context { + size_t size; // Size of input in bytes + uint32_t buffer[4]; // Current accumulation of hash + uint8_t input[0x40]; // Input to be used in the next step + uint8_t digest[0x10]; // Result of algorithm +}; + +void md5Init(struct MD5Context* ctx); +void md5Update(struct MD5Context* ctx, const void* input, size_t len); +void md5Finalize(struct MD5Context* ctx); + +void md5Buffer(const void* input, size_t len, uint8_t* result); + +struct VFile; +bool md5File(struct VFile* vf, uint8_t* result); + +CXX_GUARD_END + +#endif diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 66cd6ca47..2f4ba2dcd 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -6,6 +6,7 @@ set(BASE_SOURCE_FILES formatting.c gbk-table.c hash.c + md5.c string.c table.c vector.c @@ -41,6 +42,7 @@ set(TEST_FILES test/circle-buffer.c test/color.c test/geometry.c + test/hash.c test/image.c test/sfo.c test/string-parser.c diff --git a/src/util/md5.c b/src/util/md5.c new file mode 100644 index 000000000..08e06fb1d --- /dev/null +++ b/src/util/md5.c @@ -0,0 +1,228 @@ +/* Copyright (c) 2013-2014 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Based on https://github.com/Zunawe/md5-c + * Originally released under the Unlicense + * + * Derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm + */ +#include + +#include + +/* + * Constants defined by the MD5 algorithm + */ +#define A 0x67452301 +#define B 0xEFCDAB89 +#define C 0x98BADCFE +#define D 0x10325476 + +static const uint32_t S[] = { 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 }; + +static const uint32_t K[] = { 0xD76AA478, 0xE8C7B756, 0x242070DB, 0xC1BDCEEE, + 0xF57C0FAF, 0x4787C62A, 0xA8304613, 0xFD469501, + 0x698098D8, 0x8B44F7AF, 0xFFFF5BB1, 0x895CD7BE, + 0x6B901122, 0xFD987193, 0xA679438E, 0x49B40821, + 0xF61E2562, 0xC040B340, 0x265E5A51, 0xE9B6C7AA, + 0xD62F105D, 0x02441453, 0xD8A1E681, 0xE7D3FBC8, + 0x21E1CDE6, 0xC33707D6, 0xF4D50D87, 0x455A14ED, + 0xA9E3E905, 0xFCEFA3F8, 0x676F02D9, 0x8D2A4C8A, + 0xFFFA3942, 0x8771F681, 0x6D9D6122, 0xFDE5380C, + 0xA4BEEA44, 0x4BDECFA9, 0xF6BB4B60, 0xBEBFBC70, + 0x289B7EC6, 0xEAA127FA, 0xD4EF3085, 0x04881D05, + 0xD9D4D039, 0xE6DB99E5, 0x1FA27CF8, 0xC4AC5665, + 0xF4292244, 0x432AFF97, 0xAB9423A7, 0xFC93A039, + 0x655B59C3, 0x8F0CCC92, 0xFFEFF47D, 0x85845DD1, + 0x6FA87E4F, 0xFE2CE6E0, 0xA3014314, 0x4E0811A1, + 0xF7537E82, 0xBD3AF235, 0x2AD7D2BB, 0xEB86D391 }; + +/* + * Padding used to make the size (in bits) of the input congruent to 448 mod 512 + */ +static const uint8_t PADDING[] = { 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* + * Bit-manipulation functions defined by the MD5 algorithm + */ +#define F(X, Y, Z) (((X) & (Y)) | (~(X) & (Z))) +#define G(X, Y, Z) (((X) & (Z)) | ((Y) & ~(Z))) +#define H(X, Y, Z) ((X) ^ (Y) ^ (Z)) +#define I(X, Y, Z) ((Y) ^ ((X) | ~(Z))) + +/* + * Rotates a 32-bit word left by n bits + */ +static uint32_t rotateLeft(uint32_t x, uint32_t n) { + return (x << n) | (x >> (32 - n)); +} + +/* + * Step on 512 bits of input with the main MD5 algorithm. + */ +static void md5Step(uint32_t* buffer, const uint32_t* input) { + uint32_t AA = buffer[0]; + uint32_t BB = buffer[1]; + uint32_t CC = buffer[2]; + uint32_t DD = buffer[3]; + + uint32_t E; + + unsigned j; + + for (unsigned i = 0; i < 64; ++i) { + switch (i / 16) { + case 0: + E = F(BB, CC, DD); + j = i; + break; + case 1: + E = G(BB, CC, DD); + j = ((i * 5) + 1) & 0xF; + break; + case 2: + E = H(BB, CC, DD); + j = ((i * 3) + 5) & 0xF; + break; + default: + E = I(BB, CC, DD); + j = (i * 7) & 0xF; + break; + } + + uint32_t temp = DD; + DD = CC; + CC = BB; + BB += rotateLeft(AA + E + K[i] + input[j], S[i]); + AA = temp; + } + + buffer[0] += AA; + buffer[1] += BB; + buffer[2] += CC; + buffer[3] += DD; +} + +/* + * Initialize a context + */ +void md5Init(struct MD5Context* ctx) { + memset(ctx, 0, sizeof(*ctx)); + + ctx->buffer[0] = A; + ctx->buffer[1] = B; + ctx->buffer[2] = C; + ctx->buffer[3] = D; +} + +/* + * Add some amount of input to the context + * + * If the input fills out a block of 512 bits, apply the algorithm (md5Step) + * and save the result in the buffer. Also updates the overall size. + */ +void md5Update(struct MD5Context* ctx, const void* input, size_t len) { + uint32_t buffer[16]; + unsigned offset = ctx->size & 0x3F; + const uint8_t* inputBuffer = input; + ctx->size += len; + + // Copy each byte in input_buffer into the next space in our context input + unsigned i; + for (i = 0; i < len; ++i) { + ctx->input[offset] = inputBuffer[i]; + + // If we've filled our context input, copy it into our local array input + // then reset the offset to 0 and fill in a new buffer. + // Every time we fill out a chunk, we run it through the algorithm + // to enable some back and forth between cpu and i/o + if (offset < 0x3F) { + ++offset; + continue; + } + + unsigned j; + for (j = 0; j < 16; ++j) { + // Convert to little-endian + // The local variable `input` our 512-bit chunk separated into 32-bit words + // we can use in calculations + LOAD_32LE(buffer[j], j * 4, ctx->input); + } + md5Step(ctx->buffer, buffer); + offset = 0; + } +} + +/* + * Pad the current input to get to 448 bits, append the size in bits to the very end, + * and save the result of the final iteration into digest. + */ +void md5Finalize(struct MD5Context* ctx) { + uint32_t input[16]; + int offset = ctx->size & 0x3F; + unsigned paddingLength = offset < 56 ? 56 - offset : (56 + 64) - offset; + + // Fill in the padding and undo the changes to size that resulted from the update + md5Update(ctx, PADDING, paddingLength); + ctx->size -= paddingLength; + + // Do a final update (internal to this function) + // Last two 32-bit words are the two halves of the size (converted from bytes to bits) + unsigned j; + for (j = 0; j < 14; ++j) { + LOAD_32LE(input[j], j * 4, ctx->input); + } + input[14] = (uint32_t) (ctx->size * 8); + input[15] = (uint32_t) ((ctx->size * 8ULL) >> 32); + + md5Step(ctx->buffer, input); + + // Move the result into digest (convert from little-endian) + unsigned i; + for (i = 0; i < 4; ++i) { + STORE_32LE(ctx->buffer[i], i * 4, ctx->digest); + } +} + +void md5Buffer(const void* input, size_t len, uint8_t* result) { + struct MD5Context ctx; + md5Init(&ctx); + md5Update(&ctx, input, len); + md5Finalize(&ctx); + memcpy(result, ctx.digest, sizeof(ctx.digest)); +} + +bool md5File(struct VFile* vf, uint8_t* result) { + struct MD5Context ctx; + uint8_t buffer[2048]; + md5Init(&ctx); + + ssize_t read; + ssize_t position = vf->seek(vf, 0, SEEK_CUR); + if (vf->seek(vf, 0, SEEK_SET) < 0) { + return false; + } + while ((read = vf->read(vf, buffer, sizeof(buffer))) > 0) { + md5Update(&ctx, buffer, read); + } + vf->seek(vf, position, SEEK_SET); + if (read < 0) { + return false; + } + md5Finalize(&ctx); + memcpy(result, ctx.digest, sizeof(ctx.digest)); + return true; +} diff --git a/src/util/test/hash.c b/src/util/test/hash.c new file mode 100644 index 000000000..6d014435b --- /dev/null +++ b/src/util/test/hash.c @@ -0,0 +1,130 @@ +/* Copyright (c) 2013-2022 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/test/suite.h" + +#include +#include + +M_TEST_DEFINE(emptyCrc32) { + uint8_t buffer[1] = {0}; + assert_int_equal(doCrc32(buffer, 0), 0); +} + +M_TEST_DEFINE(newlineCrc32) { +uint8_t buffer[1] = { '\n' }; + assert_int_equal(doCrc32(buffer, 1), 0x32D70693); +} + +M_TEST_DEFINE(helloWorldCrc32) { + const char* buffer = "Hello, world!"; + assert_int_equal(doCrc32(buffer, strlen(buffer)), 0xEBE6C6E6); +} + +#ifndef HAVE_CRC32 +M_TEST_DEFINE(stagedCrc32) { + uint8_t buffer[1] = { '\n\n' }; + assert_int_equal(crc32(0, buffer, 1), 0x32D70693); + assert_int_equal(crc32(0, buffer, 2), 0x09304EBD); + assert_int_equal(crc32(0x32D70693, buffer, 1), 0x09304EBD); +} +#endif + +M_TEST_DEFINE(emptyMd5) { + uint8_t buffer[1] = {0}; + uint8_t digest[0x10] = {0}; + md5Buffer(buffer, 0, digest); + assert_memory_equal(digest, ((uint8_t[]) { + 0xD4, 0x1D, 0x8C, 0xD9, 0x8F, 0x00, 0xB2, 0x04, + 0xE9, 0x80, 0x09, 0x98, 0xEC, 0xF8, 0x42, 0x7E + }), 16); +} + +M_TEST_DEFINE(newlineMd5) { + uint8_t buffer[1] = { '\n' }; + uint8_t digest[0x10] = {0}; + md5Buffer(buffer, 1, digest); + assert_memory_equal(digest, ((uint8_t[]) { + 0x68, 0xB3, 0x29, 0xDA, 0x98, 0x93, 0xE3, 0x40, + 0x99, 0xC7, 0xD8, 0xAD, 0x5C, 0xB9, 0xC9, 0x40 + }), 16); +} + +M_TEST_DEFINE(fullBlockMd5) { + uint8_t buffer[56] = { + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + }; + uint8_t digest[0x10] = {0}; + md5Buffer(buffer, 56, digest); + assert_memory_equal(digest, ((uint8_t[]) { + 0xA3, 0x31, 0x42, 0x53, 0x78, 0x54, 0xFE, 0xE2, + 0xAF, 0xD6, 0xCF, 0xF4, 0xC5, 0xA1, 0xDD, 0x39 + }), 16); +} + +M_TEST_DEFINE(overflowBlockMd5) { + uint8_t buffer[57] = { + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x0a, + }; + uint8_t digest[0x10] = {0}; + md5Buffer(buffer, 57, digest); + assert_memory_equal(digest, ((uint8_t[]) { + 0xBA, 0x49, 0x77, 0x29, 0x15, 0x5B, 0x13, 0x5D, + 0xBA, 0x27, 0xF3, 0xD8, 0x53, 0xCF, 0xD2, 0x1A + }), 16); +} + +M_TEST_DEFINE(twoBlockMd5) { + uint8_t buffer[120] = { + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + }; + uint8_t digest[0x10] = {0}; + md5Buffer(buffer, 120, digest); + assert_memory_equal(digest, ((uint8_t[]) { + 0xB5, 0x68, 0xA7, 0x7E, 0xD4, 0xC2, 0x39, 0xFB, + 0x4B, 0x74, 0xD7, 0x5B, 0xDB, 0xFD, 0x94, 0x93 + }), 16); +} + +M_TEST_SUITE_DEFINE(Hashes, + cmocka_unit_test(emptyCrc32), + cmocka_unit_test(newlineCrc32), + cmocka_unit_test(helloWorldCrc32), +#ifndef HAVE_CRC32 + cmocka_unit_test(stagedCrc32), +#endif + cmocka_unit_test(emptyMd5), + cmocka_unit_test(newlineMd5), + cmocka_unit_test(fullBlockMd5), + cmocka_unit_test(overflowBlockMd5), + cmocka_unit_test(twoBlockMd5), +)