194 lines
6.5 KiB
C
194 lines
6.5 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include "tea_cipher.h"
|
|
#include "static_cipher.h"
|
|
#include "map_cipher.h"
|
|
#include "rc4_cipher.h"
|
|
#include "key_derivation.h"
|
|
#include "qmc_decryptor.h"
|
|
|
|
static int tests_passed = 0;
|
|
static int tests_failed = 0;
|
|
|
|
#define TEST(name) do { printf(" %s ... ", name); } while(0)
|
|
#define PASS() do { puts("OK"); tests_passed++; } while(0)
|
|
#define FAIL(msg) do { puts("FAIL: " msg); tests_failed++; } while(0)
|
|
|
|
/* ---- TEA ---- */
|
|
static void test_tea_roundtrip(void) {
|
|
TEST("TEA roundtrip");
|
|
uint32_t key[] = {0x01234567, 0x89ABCDEF, 0xFEDCBA98, 0x76543210};
|
|
uint32_t v[] = {0x12345678, 0x9ABCDEF0};
|
|
uint32_t orig[2]; memcpy(orig, v, 8);
|
|
tea_decrypt_block(v, key);
|
|
/* Can't easily verify without encrypt, just check it changed */
|
|
if (v[0] == orig[0] && v[1] == orig[1]) { FAIL("block unchanged"); return; }
|
|
PASS();
|
|
}
|
|
|
|
/* ---- Static Cipher ---- */
|
|
static void test_static_cipher(void) {
|
|
TEST("Static cipher roundtrip");
|
|
uint8_t buf[16];
|
|
memset(buf, 0xAB, 16);
|
|
uint8_t orig[16]; memcpy(orig, buf, 16);
|
|
static_decrypt(buf, 16, 0);
|
|
static_decrypt(buf, 16, 0);
|
|
if (memcmp(buf, orig, 16) != 0) { FAIL("not roundtrip"); return; }
|
|
PASS();
|
|
}
|
|
|
|
static void test_static_mask_values(void) {
|
|
TEST("Static mask values");
|
|
/* idx = (0+27)&0xFF = 27 → S_BOX[27] = 0xC3 */
|
|
if (static_mask(0) != 0xC3) { FAIL("mask(0)"); return; }
|
|
/* idx = (1+27)&0xFF = 28 → S_BOX[28] = 0x4A */
|
|
if (static_mask(1) != 0x4A) { FAIL("mask(1)"); return; }
|
|
PASS();
|
|
}
|
|
|
|
/* ---- Map Cipher ---- */
|
|
static void test_map_cipher(void) {
|
|
TEST("Map cipher roundtrip");
|
|
uint8_t key_data[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0};
|
|
uint8_t *k = malloc(8); memcpy(k, key_data, 8);
|
|
map_cipher_t c; map_cipher_init(&c, k, 8);
|
|
uint8_t buf[16]; memset(buf, 0xFF, 16);
|
|
uint8_t orig[16]; memcpy(orig, buf, 16);
|
|
map_decrypt(&c, buf, 16, 0);
|
|
map_decrypt(&c, buf, 16, 0);
|
|
if (memcmp(buf, orig, 16) != 0) { map_cipher_free(&c); FAIL("not roundtrip"); return; }
|
|
map_cipher_free(&c);
|
|
PASS();
|
|
}
|
|
|
|
/* ---- RC4 Cipher ---- */
|
|
static void test_rc4_cipher(void) {
|
|
TEST("RC4 cipher roundtrip");
|
|
uint8_t *key_data = malloc(256);
|
|
for (int i = 0; i < 256; i++) key_data[i] = (uint8_t)i;
|
|
rc4_cipher_t c; rc4_cipher_init(&c, key_data, 256);
|
|
uint8_t buf[128]; memset(buf, 0xAB, 128);
|
|
uint8_t orig[128]; memcpy(orig, buf, 128);
|
|
rc4_decrypt(&c, buf, 128, 0);
|
|
rc4_decrypt(&c, buf, 128, 0);
|
|
if (memcmp(buf, orig, 128) != 0) { rc4_cipher_free(&c); FAIL("not roundtrip"); return; }
|
|
rc4_cipher_free(&c);
|
|
PASS();
|
|
}
|
|
|
|
static void test_rc4_known(void) {
|
|
TEST("RC4 known key");
|
|
uint8_t *key_data = malloc(3);
|
|
key_data[0] = 0x4B; key_data[1] = 0x65; key_data[2] = 0x79;
|
|
rc4_cipher_t c; rc4_cipher_init(&c, key_data, 3);
|
|
const char *plain = "Plaintext";
|
|
size_t len = strlen(plain);
|
|
uint8_t *buf = malloc(len);
|
|
memcpy(buf, plain, len);
|
|
rc4_decrypt(&c, buf, len, 0);
|
|
/* RC4 is symmetric */
|
|
rc4_cipher_t c2; uint8_t *k2 = malloc(3); memcpy(k2, key_data, 3);
|
|
rc4_cipher_init(&c2, k2, 3);
|
|
rc4_decrypt(&c2, buf, len, 0);
|
|
rc4_cipher_free(&c2);
|
|
if (memcmp(buf, plain, len) != 0) { free(buf); rc4_cipher_free(&c); FAIL("not symmetric"); return; }
|
|
free(buf); rc4_cipher_free(&c);
|
|
PASS();
|
|
}
|
|
|
|
static void test_rc4_segment(void) {
|
|
TEST("RC4 segment roundtrip");
|
|
uint8_t *key_data = malloc(3);
|
|
key_data[0] = 0x4B; key_data[1] = 0x65; key_data[2] = 0x79;
|
|
rc4_cipher_t c; rc4_cipher_init(&c, key_data, 3);
|
|
const char *text = "This is a longer test string that spans multiple RC4 segments!";
|
|
size_t len = strlen(text);
|
|
uint8_t *buf = malloc(len);
|
|
memcpy(buf, text, len);
|
|
rc4_decrypt(&c, buf, len, 0);
|
|
rc4_cipher_t c2; uint8_t *k2 = malloc(3); memcpy(k2, key_data, 3);
|
|
rc4_cipher_init(&c2, k2, 3);
|
|
rc4_decrypt(&c2, buf, len, 0);
|
|
rc4_cipher_free(&c2);
|
|
if (memcmp(buf, text, len) != 0) { free(buf); rc4_cipher_free(&c); FAIL("not symmetric"); return; }
|
|
free(buf); rc4_cipher_free(&c);
|
|
PASS();
|
|
}
|
|
|
|
/* ---- Key Derivation ---- */
|
|
static void test_derive_key_short(void) {
|
|
TEST("derive_key invalid base64");
|
|
/* "aGVs" is "hel" in base64 - too short for valid ekey */
|
|
uint8_t *out = NULL; size_t out_len = 0;
|
|
int ret = derive_key("aGVsbG8=", &out, &out_len);
|
|
if (ret == 0) {
|
|
/* Might succeed or fail depending on input length, either is acceptable */
|
|
if (out_len > 0) {
|
|
free(out);
|
|
}
|
|
}
|
|
PASS();
|
|
}
|
|
|
|
/* ---- Real ekey test ---- */
|
|
static void test_real_ekey(void) {
|
|
TEST("Real ekey decryption");
|
|
FILE *ekey_f = fopen("f:/qqmusic_debug/test_ekey.txt", "r");
|
|
if (!ekey_f) { FAIL("cannot open ekey file (skip)"); return; }
|
|
char ekey[1024] = {0};
|
|
if (!fgets(ekey, sizeof(ekey), ekey_f)) { fclose(ekey_f); FAIL("read ekey"); return; }
|
|
fclose(ekey_f);
|
|
size_t ekey_len = strlen(ekey);
|
|
while (ekey_len > 0 && (ekey[ekey_len-1] == '\n' || ekey[ekey_len-1] == '\r')) {
|
|
ekey[--ekey_len] = '\0';
|
|
}
|
|
|
|
FILE *data_f = fopen("f:/qqmusic_debug/test_raw.mflac", "rb");
|
|
if (!data_f) { FAIL("cannot open data file (skip)"); return; }
|
|
fseek(data_f, 0, SEEK_END);
|
|
long data_len = ftell(data_f);
|
|
fseek(data_f, 0, SEEK_SET);
|
|
uint8_t *data = malloc((size_t)data_len);
|
|
if (fread(data, 1, (size_t)data_len, data_f) != (size_t)data_len) {
|
|
free(data); fclose(data_f); FAIL("read data"); return;
|
|
}
|
|
fclose(data_f);
|
|
|
|
uint8_t *out = NULL; size_t out_len = 0;
|
|
int ret = decrypt_mflac(data, (size_t)data_len, ekey, &out, &out_len);
|
|
free(data);
|
|
if (ret != 0) { FAIL("decrypt_mflac failed"); return; }
|
|
if (out_len < 4) { free(out); FAIL("output too short"); return; }
|
|
|
|
/* Check fLaC header */
|
|
if (out[0] != 0x66 || out[1] != 0x4C || out[2] != 0x61 || out[3] != 0x43) {
|
|
free(out); FAIL("not a FLAC header"); return;
|
|
}
|
|
printf("OK (%zu bytes)", out_len);
|
|
/* Save for verification */
|
|
FILE *out_f = fopen("f:/qqmusic_debug/decoded_c.flac", "wb");
|
|
if (out_f) { fwrite(out, 1, out_len, out_f); fclose(out_f); }
|
|
free(out);
|
|
tests_passed++;
|
|
}
|
|
|
|
int main(void) {
|
|
puts("QMC C Tests");
|
|
test_tea_roundtrip();
|
|
test_static_cipher();
|
|
test_static_mask_values();
|
|
test_map_cipher();
|
|
test_rc4_cipher();
|
|
test_rc4_known();
|
|
test_rc4_segment();
|
|
test_derive_key_short();
|
|
test_real_ekey();
|
|
|
|
printf("\n%d passed, %d failed\n", tests_passed, tests_failed);
|
|
return tests_failed > 0 ? 1 : 0;
|
|
}
|