Browse Source
Reviewed-on: https://git.unlock-music.dev/um/web/pulls/29 Reviewed-by: jixunmoe <jixunmoe@noreply.unlock-music.dev>20230724
jixunmoe
2 years ago
33 changed files with 61 additions and 2569 deletions
@ -1,13 +0,0 @@ |
|||
#!/usr/bin/env bash |
|||
|
|||
set -ex |
|||
|
|||
cd "$(git rev-parse --show-toplevel)" |
|||
|
|||
pushd ./src/QmcWasm |
|||
bash build-wasm |
|||
popd |
|||
|
|||
pushd ./src/KgmWasm |
|||
bash build-wasm |
|||
popd |
@ -1,65 +0,0 @@ |
|||
# CMakeList.txt : CMake project for KgmWasm, include source and define |
|||
# project specific logic here. |
|||
# |
|||
cmake_minimum_required (VERSION 3.8) |
|||
|
|||
project ("KgmWasm") |
|||
|
|||
set(CMAKE_CXX_STANDARD 14) |
|||
|
|||
include_directories( |
|||
$<TARGET_PROPERTY:INTERFACE_INCLUDE_DIRECTORIES> |
|||
) |
|||
|
|||
# Add source to this project's executable. |
|||
set(RUNTIME_METHODS_LIST |
|||
getValue |
|||
writeArrayToMemory |
|||
UTF8ToString |
|||
) |
|||
list(JOIN RUNTIME_METHODS_LIST "," RUNTIME_METHODS) |
|||
|
|||
set(EMSCRIPTEN_FLAGS |
|||
"--bind" |
|||
"-s NO_DYNAMIC_EXECUTION=1" |
|||
"-s MODULARIZE=1" |
|||
"-s EXPORT_NAME=KgmCryptoModule" |
|||
"-s EXPORTED_RUNTIME_METHODS=${RUNTIME_METHODS}" |
|||
) |
|||
set(EMSCRIPTEN_LEGACY_FLAGS |
|||
${EMSCRIPTEN_FLAGS} |
|||
"-s WASM=0" |
|||
"--memory-init-file 0" |
|||
) |
|||
set(EMSCRIPTEN_WASM_BUNDLE_FLAGS |
|||
${EMSCRIPTEN_FLAGS} |
|||
"-s SINGLE_FILE=1" |
|||
) |
|||
|
|||
list(JOIN EMSCRIPTEN_FLAGS " " EMSCRIPTEN_FLAGS_STR) |
|||
list(JOIN EMSCRIPTEN_LEGACY_FLAGS " " EMSCRIPTEN_LEGACY_FLAGS_STR) |
|||
list(JOIN EMSCRIPTEN_WASM_BUNDLE_FLAGS " " EMSCRIPTEN_WASM_BUNDLE_FLAGS_STR) |
|||
|
|||
# Define projects config |
|||
set(WASM_SOURCES |
|||
"KgmWasm.cpp" |
|||
) |
|||
|
|||
add_executable(KgmWasm ${WASM_SOURCES}) |
|||
set_target_properties( |
|||
KgmWasm |
|||
PROPERTIES LINK_FLAGS ${EMSCRIPTEN_FLAGS_STR} |
|||
) |
|||
|
|||
add_executable(KgmWasmBundle ${WASM_SOURCES}) |
|||
set_target_properties( |
|||
KgmWasmBundle |
|||
PROPERTIES LINK_FLAGS ${EMSCRIPTEN_WASM_BUNDLE_FLAGS_STR} |
|||
) |
|||
|
|||
add_executable(KgmLegacy ${WASM_SOURCES}) |
|||
set_target_properties( |
|||
KgmLegacy |
|||
PROPERTIES LINK_FLAGS ${EMSCRIPTEN_LEGACY_FLAGS_STR} |
|||
) |
|||
|
@ -1,20 +0,0 @@ |
|||
// KgmWasm.cpp : Defines the entry point for the application.
|
|||
//
|
|||
|
|||
#include "KgmWasm.h" |
|||
|
|||
#include "kgm.hpp" |
|||
|
|||
#include <stddef.h> |
|||
#include <string.h> |
|||
|
|||
size_t preDec(uintptr_t blob, size_t blobSize, std::string ext) |
|||
{ |
|||
return PreDec((uint8_t*)blob, blobSize, ext == "vpr"); |
|||
} |
|||
|
|||
void decBlob(uintptr_t blob, size_t blobSize, size_t offset) |
|||
{ |
|||
Decrypt((uint8_t*)blob, blobSize, offset); |
|||
return; |
|||
} |
@ -1,18 +0,0 @@ |
|||
// KgmWasm.h : Include file for standard system include files,
|
|||
// or project specific include files.
|
|||
|
|||
#pragma once |
|||
|
|||
#include <emscripten/bind.h> |
|||
#include <string> |
|||
|
|||
namespace em = emscripten; |
|||
|
|||
size_t preDec(uintptr_t blob, size_t blobSize, std::string ext); |
|||
void decBlob(uintptr_t blob, size_t blobSize, size_t offset); |
|||
|
|||
EMSCRIPTEN_BINDINGS(QmcCrypto) |
|||
{ |
|||
em::function("preDec", &preDec, em::allow_raw_pointers()); |
|||
em::function("decBlob", &decBlob, em::allow_raw_pointers()); |
|||
} |
@ -1,9 +0,0 @@ |
|||
# KgmWasm |
|||
|
|||
## 构建 |
|||
|
|||
在 Linux 环境下执行 `bash build-wasm` 即可构建。 |
|||
|
|||
## Build |
|||
|
|||
Linux environment required. Build wasm binary by execute `bash build-wasm`. |
@ -1,41 +0,0 @@ |
|||
#!/usr/bin/env bash |
|||
|
|||
set -e |
|||
|
|||
pushd "$(realpath "$(dirname "$0")")" |
|||
|
|||
CURR_DIR="${PWD}" |
|||
|
|||
BUILD_TYPE="$1" |
|||
if [ -z "$BUILD_TYPE" ]; then |
|||
BUILD_TYPE=Release |
|||
fi |
|||
|
|||
# CI: already had emsdk installed. |
|||
if ! command -v emcc; then |
|||
if [ ! -d ../../build/emsdk ]; then |
|||
git clone https://github.com/emscripten-core/emsdk.git ../../build/emsdk |
|||
fi |
|||
|
|||
pushd ../../build/emsdk |
|||
./emsdk install 3.0.0 |
|||
./emsdk activate 3.0.0 |
|||
source ./emsdk_env.sh |
|||
popd # ../../build/emsdk |
|||
fi |
|||
|
|||
mkdir -p build/wasm |
|||
pushd build/wasm |
|||
emcmake cmake -DCMAKE_BUILD_TYPE="$BUILD_TYPE" ../.. |
|||
make -j |
|||
TARGET_FILES=" |
|||
KgmLegacy.js |
|||
KgmWasm.js |
|||
KgmWasm.wasm |
|||
KgmWasmBundle.js |
|||
" |
|||
|
|||
cp $TARGET_FILES "${CURR_DIR}/" |
|||
popd # build/wasm |
|||
|
|||
popd |
@ -1,112 +0,0 @@ |
|||
#include <vector> |
|||
|
|||
std::vector<uint8_t> VprHeader = { |
|||
0x05, 0x28, 0xBC, 0x96, 0xE9, 0xE4, 0x5A, 0x43, |
|||
0x91, 0xAA, 0xBD, 0xD0, 0x7A, 0xF5, 0x36, 0x31 }; |
|||
std::vector<uint8_t> KgmHeader = { |
|||
0x7C, 0xD5, 0x32, 0xEB, 0x86, 0x02, 0x7F, 0x4B, |
|||
0xA8, 0xAF, 0xA6, 0x8E, 0x0F, 0xFF, 0x99, 0x14 }; |
|||
std::vector<uint8_t> VprMaskDiff = { |
|||
0x25, 0xDF, 0xE8, 0xA6, 0x75, 0x1E, 0x75, 0x0E, |
|||
0x2F, 0x80, 0xF3, 0x2D, 0xB8, 0xB6, 0xE3, 0x11, 0x00 }; |
|||
|
|||
std::vector<uint8_t> MaskV2; |
|||
|
|||
std::vector<uint8_t> table1 = { |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x01, 0x21, 0x01, 0x61, 0x01, 0x21, 0x01, 0xe1, 0x01, 0x21, 0x01, 0x61, 0x01, 0x21, 0x01, |
|||
0xd2, 0x23, 0x02, 0x02, 0x42, 0x42, 0x02, 0x02, 0xc2, 0xc2, 0x02, 0x02, 0x42, 0x42, 0x02, 0x02, |
|||
0xd3, 0xd3, 0x02, 0x03, 0x63, 0x43, 0x63, 0x03, 0xe3, 0xc3, 0xe3, 0x03, 0x63, 0x43, 0x63, 0x03, |
|||
0x94, 0xb4, 0x94, 0x65, 0x04, 0x04, 0x04, 0x04, 0x84, 0x84, 0x84, 0x84, 0x04, 0x04, 0x04, 0x04, |
|||
0x95, 0x95, 0x95, 0x95, 0x04, 0x05, 0x25, 0x05, 0xe5, 0x85, 0xa5, 0x85, 0xe5, 0x05, 0x25, 0x05, |
|||
0xd6, 0xb6, 0x96, 0xb6, 0xd6, 0x27, 0x06, 0x06, 0xc6, 0xc6, 0x86, 0x86, 0xc6, 0xc6, 0x06, 0x06, |
|||
0xd7, 0xd7, 0x97, 0x97, 0xd7, 0xd7, 0x06, 0x07, 0xe7, 0xc7, 0xe7, 0x87, 0xe7, 0xc7, 0xe7, 0x07, |
|||
0x18, 0x38, 0x18, 0x78, 0x18, 0x38, 0x18, 0xe9, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, |
|||
0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x08, 0x09, 0x29, 0x09, 0x69, 0x09, 0x29, 0x09, |
|||
0xda, 0x3a, 0x1a, 0x3a, 0x5a, 0x3a, 0x1a, 0x3a, 0xda, 0x2b, 0x0a, 0x0a, 0x4a, 0x4a, 0x0a, 0x0a, |
|||
0xdb, 0xdb, 0x1b, 0x1b, 0x5b, 0x5b, 0x1b, 0x1b, 0xdb, 0xdb, 0x0a, 0x0b, 0x6b, 0x4b, 0x6b, 0x0b, |
|||
0x9c, 0xbc, 0x9c, 0x7c, 0x1c, 0x3c, 0x1c, 0x7c, 0x9c, 0xbc, 0x9c, 0x6d, 0x0c, 0x0c, 0x0c, 0x0c, |
|||
0x9d, 0x9d, 0x9d, 0x9d, 0x1d, 0x1d, 0x1d, 0x1d, 0x9d, 0x9d, 0x9d, 0x9d, 0x0c, 0x0d, 0x2d, 0x0d, |
|||
0xde, 0xbe, 0x9e, 0xbe, 0xde, 0x3e, 0x1e, 0x3e, 0xde, 0xbe, 0x9e, 0xbe, 0xde, 0x2f, 0x0e, 0x0e, |
|||
0xdf, 0xdf, 0x9f, 0x9f, 0xdf, 0xdf, 0x1f, 0x1f, 0xdf, 0xdf, 0x9f, 0x9f, 0xdf, 0xdf, 0x0e, 0x0f, |
|||
0x00, 0x20, 0x00, 0x60, 0x00, 0x20, 0x00, 0xe0, 0x00, 0x20, 0x00, 0x60, 0x00, 0x20, 0x00, 0xf1 |
|||
}; |
|||
|
|||
std::vector<uint8_t> table2 = { |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x01, 0x23, 0x01, 0x67, 0x01, 0x23, 0x01, 0xef, 0x01, 0x23, 0x01, 0x67, 0x01, 0x23, 0x01, |
|||
0xdf, 0x21, 0x02, 0x02, 0x46, 0x46, 0x02, 0x02, 0xce, 0xce, 0x02, 0x02, 0x46, 0x46, 0x02, 0x02, |
|||
0xde, 0xde, 0x02, 0x03, 0x65, 0x47, 0x65, 0x03, 0xed, 0xcf, 0xed, 0x03, 0x65, 0x47, 0x65, 0x03, |
|||
0x9d, 0xbf, 0x9d, 0x63, 0x04, 0x04, 0x04, 0x04, 0x8c, 0x8c, 0x8c, 0x8c, 0x04, 0x04, 0x04, 0x04, |
|||
0x9c, 0x9c, 0x9c, 0x9c, 0x04, 0x05, 0x27, 0x05, 0xeb, 0x8d, 0xaf, 0x8d, 0xeb, 0x05, 0x27, 0x05, |
|||
0xdb, 0xbd, 0x9f, 0xbd, 0xdb, 0x25, 0x06, 0x06, 0xca, 0xca, 0x8e, 0x8e, 0xca, 0xca, 0x06, 0x06, |
|||
0xda, 0xda, 0x9e, 0x9e, 0xda, 0xda, 0x06, 0x07, 0xe9, 0xcb, 0xe9, 0x8f, 0xe9, 0xcb, 0xe9, 0x07, |
|||
0x19, 0x3b, 0x19, 0x7f, 0x19, 0x3b, 0x19, 0xe7, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, |
|||
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x08, 0x09, 0x2b, 0x09, 0x6f, 0x09, 0x2b, 0x09, |
|||
0xd7, 0x39, 0x1b, 0x39, 0x5f, 0x39, 0x1b, 0x39, 0xd7, 0x29, 0x0a, 0x0a, 0x4e, 0x4e, 0x0a, 0x0a, |
|||
0xd6, 0xd6, 0x1a, 0x1a, 0x5e, 0x5e, 0x1a, 0x1a, 0xd6, 0xd6, 0x0a, 0x0b, 0x6d, 0x4f, 0x6d, 0x0b, |
|||
0x95, 0xb7, 0x95, 0x7b, 0x1d, 0x3f, 0x1d, 0x7b, 0x95, 0xb7, 0x95, 0x6b, 0x0c, 0x0c, 0x0c, 0x0c, |
|||
0x94, 0x94, 0x94, 0x94, 0x1c, 0x1c, 0x1c, 0x1c, 0x94, 0x94, 0x94, 0x94, 0x0c, 0x0d, 0x2f, 0x0d, |
|||
0xd3, 0xb5, 0x97, 0xb5, 0xd3, 0x3d, 0x1f, 0x3d, 0xd3, 0xb5, 0x97, 0xb5, 0xd3, 0x2d, 0x0e, 0x0e, |
|||
0xd2, 0xd2, 0x96, 0x96, 0xd2, 0xd2, 0x1e, 0x1e, 0xd2, 0xd2, 0x96, 0x96, 0xd2, 0xd2, 0x0e, 0x0f, |
|||
0x00, 0x22, 0x00, 0x66, 0x00, 0x22, 0x00, 0xee, 0x00, 0x22, 0x00, 0x66, 0x00, 0x22, 0x00, 0xfe |
|||
}; |
|||
|
|||
std::vector<uint8_t> MaskV2PreDef = { |
|||
0xB8, 0xD5, 0x3D, 0xB2, 0xE9, 0xAF, 0x78, 0x8C, 0x83, 0x33, 0x71, 0x51, 0x76, 0xA0, 0xCD, 0x37, |
|||
0x2F, 0x3E, 0x35, 0x8D, 0xA9, 0xBE, 0x98, 0xB7, 0xE7, 0x8C, 0x22, 0xCE, 0x5A, 0x61, 0xDF, 0x68, |
|||
0x69, 0x89, 0xFE, 0xA5, 0xB6, 0xDE, 0xA9, 0x77, 0xFC, 0xC8, 0xBD, 0xBD, 0xE5, 0x6D, 0x3E, 0x5A, |
|||
0x36, 0xEF, 0x69, 0x4E, 0xBE, 0xE1, 0xE9, 0x66, 0x1C, 0xF3, 0xD9, 0x02, 0xB6, 0xF2, 0x12, 0x9B, |
|||
0x44, 0xD0, 0x6F, 0xB9, 0x35, 0x89, 0xB6, 0x46, 0x6D, 0x73, 0x82, 0x06, 0x69, 0xC1, 0xED, 0xD7, |
|||
0x85, 0xC2, 0x30, 0xDF, 0xA2, 0x62, 0xBE, 0x79, 0x2D, 0x62, 0x62, 0x3D, 0x0D, 0x7E, 0xBE, 0x48, |
|||
0x89, 0x23, 0x02, 0xA0, 0xE4, 0xD5, 0x75, 0x51, 0x32, 0x02, 0x53, 0xFD, 0x16, 0x3A, 0x21, 0x3B, |
|||
0x16, 0x0F, 0xC3, 0xB2, 0xBB, 0xB3, 0xE2, 0xBA, 0x3A, 0x3D, 0x13, 0xEC, 0xF6, 0x01, 0x45, 0x84, |
|||
0xA5, 0x70, 0x0F, 0x93, 0x49, 0x0C, 0x64, 0xCD, 0x31, 0xD5, 0xCC, 0x4C, 0x07, 0x01, 0x9E, 0x00, |
|||
0x1A, 0x23, 0x90, 0xBF, 0x88, 0x1E, 0x3B, 0xAB, 0xA6, 0x3E, 0xC4, 0x73, 0x47, 0x10, 0x7E, 0x3B, |
|||
0x5E, 0xBC, 0xE3, 0x00, 0x84, 0xFF, 0x09, 0xD4, 0xE0, 0x89, 0x0F, 0x5B, 0x58, 0x70, 0x4F, 0xFB, |
|||
0x65, 0xD8, 0x5C, 0x53, 0x1B, 0xD3, 0xC8, 0xC6, 0xBF, 0xEF, 0x98, 0xB0, 0x50, 0x4F, 0x0F, 0xEA, |
|||
0xE5, 0x83, 0x58, 0x8C, 0x28, 0x2C, 0x84, 0x67, 0xCD, 0xD0, 0x9E, 0x47, 0xDB, 0x27, 0x50, 0xCA, |
|||
0xF4, 0x63, 0x63, 0xE8, 0x97, 0x7F, 0x1B, 0x4B, 0x0C, 0xC2, 0xC1, 0x21, 0x4C, 0xCC, 0x58, 0xF5, |
|||
0x94, 0x52, 0xA3, 0xF3, 0xD3, 0xE0, 0x68, 0xF4, 0x00, 0x23, 0xF3, 0x5E, 0x0A, 0x7B, 0x93, 0xDD, |
|||
0xAB, 0x12, 0xB2, 0x13, 0xE8, 0x84, 0xD7, 0xA7, 0x9F, 0x0F, 0x32, 0x4C, 0x55, 0x1D, 0x04, 0x36, |
|||
0x52, 0xDC, 0x03, 0xF3, 0xF9, 0x4E, 0x42, 0xE9, 0x3D, 0x61, 0xEF, 0x7C, 0xB6, 0xB3, 0x93, 0x50, |
|||
}; |
|||
|
|||
uint8_t getMask(size_t pos) { |
|||
size_t offset = pos >> 4; |
|||
uint8_t value = 0; |
|||
while (offset >= 0x11) { |
|||
value ^= table1[offset % 272]; |
|||
offset >>= 4; |
|||
value ^= table2[offset % 272]; |
|||
offset >>= 4; |
|||
} |
|||
|
|||
return MaskV2PreDef[pos % 272] ^ value; |
|||
} |
|||
|
|||
std::vector<uint8_t> key(17); |
|||
bool isVpr = false; |
|||
|
|||
size_t PreDec(uint8_t* fileData, size_t size, bool iV) { |
|||
uint32_t headerLen = *(uint32_t*)(fileData + 0x10); |
|||
memcpy(key.data(), (fileData + 0x1C), 0x10); |
|||
key[16] = 0; |
|||
isVpr = iV; |
|||
return headerLen; |
|||
} |
|||
|
|||
void Decrypt(uint8_t* fileData, size_t size, size_t offset) { |
|||
for (size_t i = 0; i < size; ++i) { |
|||
uint8_t med8 = key[(i + offset) % 17] ^ fileData[i]; |
|||
med8 ^= (med8 & 0xf) << 4; |
|||
|
|||
uint8_t msk8 = getMask(i + offset); |
|||
msk8 ^= (msk8 & 0xf) << 4; |
|||
fileData[i] = med8 ^ msk8; |
|||
|
|||
if (isVpr) { |
|||
fileData[i] ^= VprMaskDiff[(i + offset) % 17]; |
|||
} |
|||
} |
|||
} |
@ -1,65 +0,0 @@ |
|||
# CMakeList.txt : CMake project for QmcWasm, include source and define |
|||
# project specific logic here. |
|||
# |
|||
cmake_minimum_required (VERSION 3.8) |
|||
|
|||
project ("QmcWasm") |
|||
|
|||
set(CMAKE_CXX_STANDARD 14) |
|||
|
|||
include_directories( |
|||
$<TARGET_PROPERTY:INTERFACE_INCLUDE_DIRECTORIES> |
|||
) |
|||
|
|||
# Add source to this project's executable. |
|||
set(RUNTIME_METHODS_LIST |
|||
getValue |
|||
writeArrayToMemory |
|||
UTF8ToString |
|||
) |
|||
list(JOIN RUNTIME_METHODS_LIST "," RUNTIME_METHODS) |
|||
|
|||
set(EMSCRIPTEN_FLAGS |
|||
"--bind" |
|||
"-s NO_DYNAMIC_EXECUTION=1" |
|||
"-s MODULARIZE=1" |
|||
"-s EXPORT_NAME=QmcCryptoModule" |
|||
"-s EXPORTED_RUNTIME_METHODS=${RUNTIME_METHODS}" |
|||
) |
|||
set(EMSCRIPTEN_LEGACY_FLAGS |
|||
${EMSCRIPTEN_FLAGS} |
|||
"-s WASM=0" |
|||
"--memory-init-file 0" |
|||
) |
|||
set(EMSCRIPTEN_WASM_BUNDLE_FLAGS |
|||
${EMSCRIPTEN_FLAGS} |
|||
"-s SINGLE_FILE=1" |
|||
) |
|||
|
|||
list(JOIN EMSCRIPTEN_FLAGS " " EMSCRIPTEN_FLAGS_STR) |
|||
list(JOIN EMSCRIPTEN_LEGACY_FLAGS " " EMSCRIPTEN_LEGACY_FLAGS_STR) |
|||
list(JOIN EMSCRIPTEN_WASM_BUNDLE_FLAGS " " EMSCRIPTEN_WASM_BUNDLE_FLAGS_STR) |
|||
|
|||
# Define projects config |
|||
set(WASM_SOURCES |
|||
"QmcWasm.cpp" |
|||
) |
|||
|
|||
add_executable(QmcWasm ${WASM_SOURCES}) |
|||
set_target_properties( |
|||
QmcWasm |
|||
PROPERTIES LINK_FLAGS ${EMSCRIPTEN_FLAGS_STR} |
|||
) |
|||
|
|||
add_executable(QmcWasmBundle ${WASM_SOURCES}) |
|||
set_target_properties( |
|||
QmcWasmBundle |
|||
PROPERTIES LINK_FLAGS ${EMSCRIPTEN_WASM_BUNDLE_FLAGS_STR} |
|||
) |
|||
|
|||
add_executable(QmcLegacy ${WASM_SOURCES}) |
|||
set_target_properties( |
|||
QmcLegacy |
|||
PROPERTIES LINK_FLAGS ${EMSCRIPTEN_LEGACY_FLAGS_STR} |
|||
) |
|||
|
@ -1,57 +0,0 @@ |
|||
// QmcWasm.cpp : Defines the entry point for the application.
|
|||
//
|
|||
|
|||
#include "QmcWasm.h" |
|||
|
|||
#include "qmc.hpp" |
|||
|
|||
#include <stddef.h> |
|||
#include <string.h> |
|||
|
|||
std::string err = ""; |
|||
std::string sid = ""; |
|||
QmcDecode e; |
|||
|
|||
int preDec(uintptr_t blob, size_t blobSize, std::string ext) |
|||
{ |
|||
if (!e.SetBlob((uint8_t*)blob, blobSize)) |
|||
{ |
|||
err = "cannot allocate memory"; |
|||
return -1; |
|||
} |
|||
int tailSize = e.PreDecode(ext); |
|||
if (e.error != "") |
|||
{ |
|||
err = e.error; |
|||
return -1; |
|||
} |
|||
sid = e.songId; |
|||
return tailSize; |
|||
} |
|||
|
|||
size_t decBlob(uintptr_t blob, size_t blobSize, size_t offset) |
|||
{ |
|||
if (!e.SetBlob((uint8_t*)blob, blobSize)) |
|||
{ |
|||
err = "cannot allocate memory"; |
|||
return 0; |
|||
} |
|||
std::vector<uint8_t> decData = e.Decode(offset); |
|||
if (e.error != "") |
|||
{ |
|||
err = e.error; |
|||
return 0; |
|||
} |
|||
memcpy((uint8_t*)blob, decData.data(), decData.size()); |
|||
return decData.size(); |
|||
} |
|||
|
|||
std::string getErr() |
|||
{ |
|||
return err; |
|||
} |
|||
|
|||
std::string getSongId() |
|||
{ |
|||
return sid; |
|||
} |
@ -1,23 +0,0 @@ |
|||
// QmcWasm.h : Include file for standard system include files,
|
|||
// or project specific include files.
|
|||
|
|||
#pragma once |
|||
|
|||
#include <emscripten/bind.h> |
|||
#include <string> |
|||
|
|||
namespace em = emscripten; |
|||
|
|||
int preDec(uintptr_t blob, size_t blobSize, std::string ext); |
|||
size_t decBlob(uintptr_t blob, size_t blobSize, size_t offset); |
|||
std::string getErr(); |
|||
std::string getSongId(); |
|||
|
|||
EMSCRIPTEN_BINDINGS(QmcCrypto) |
|||
{ |
|||
em::function("getErr", &getErr); |
|||
em::function("getSongId", &getSongId); |
|||
|
|||
em::function("preDec", &preDec, em::allow_raw_pointers()); |
|||
em::function("decBlob", &decBlob, em::allow_raw_pointers()); |
|||
} |
@ -1,9 +0,0 @@ |
|||
# QmcWasm |
|||
|
|||
## 构建 |
|||
|
|||
在 Linux 环境下执行 `bash build-wasm` 即可构建。 |
|||
|
|||
## Build |
|||
|
|||
Linux environment required. Build wasm binary by execute `bash build-wasm`. |
@ -1,289 +0,0 @@ |
|||
#ifndef QQMUSIC_CPP_TENCENTTEA_HPP |
|||
#define QQMUSIC_CPP_TENCENTTEA_HPP |
|||
|
|||
#include <cstdlib> |
|||
#include <cstdio> |
|||
#include <cstdint> |
|||
#include <vector> |
|||
#include <time.h> |
|||
#include <arpa/inet.h> |
|||
|
|||
const uint32_t DELTA = 0x9e3779b9; |
|||
|
|||
#define ROUNDS 32 |
|||
#define SALT_LEN 2 |
|||
#define ZERO_LEN 7 |
|||
|
|||
void TeaDecryptECB(uint8_t* src, uint8_t* dst, std::vector<uint8_t> key, size_t rounds = ROUNDS) { |
|||
if (key.size() != 16 || (rounds & 1) != 0) |
|||
{ |
|||
return; |
|||
} |
|||
uint32_t y, z, sum; |
|||
uint32_t k[4]; |
|||
int i; |
|||
|
|||
//now encrypted buf is TCP/IP-endian;
|
|||
//TCP/IP network byte order (which is big-endian).
|
|||
y = ntohl(*((uint32_t*)src)); |
|||
z = ntohl(*((uint32_t*)(src + 4))); |
|||
//std::cout << ntohl(0x0a3aea41);
|
|||
|
|||
for (i = 0; i < 4; i++) { |
|||
//key is TCP/IP-endian;
|
|||
k[i] = ntohl(*((uint32_t*)(key.data() + i * 4))); |
|||
} |
|||
|
|||
sum = (DELTA * rounds); |
|||
for (i = 0; i < rounds; i++) { |
|||
z -= ((y << 4) + k[2]) ^ (y + sum) ^ ((y >> 5) + k[3]); |
|||
y -= ((z << 4) + k[0]) ^ (z + sum) ^ ((z >> 5) + k[1]); |
|||
sum -= DELTA; |
|||
} |
|||
|
|||
*((uint32_t*)dst) = ntohl(y); |
|||
*((uint32_t*)(dst + 4)) = ntohl(z); |
|||
|
|||
//now plain-text is TCP/IP-endian;
|
|||
} |
|||
|
|||
void TeaEncryptECB(uint8_t* src, uint8_t* dst, std::vector<uint8_t> key, size_t rounds = ROUNDS) { |
|||
if (key.size() != 16 || (rounds & 1) != 0) |
|||
{ |
|||
return; |
|||
} |
|||
uint32_t y, z, sum; |
|||
uint32_t k[4]; |
|||
int i; |
|||
|
|||
//now encrypted buf is TCP/IP-endian;
|
|||
//TCP/IP network byte order (which is big-endian).
|
|||
y = ntohl(*((uint32_t*)src)); |
|||
z = ntohl(*((uint32_t*)(src + 4))); |
|||
//std::cout << ntohl(0x0a3aea41);
|
|||
|
|||
for (i = 0; i < 4; i++) { |
|||
//key is TCP/IP-endian;
|
|||
k[i] = ntohl(*((uint32_t*)(key.data() + i * 4))); |
|||
} |
|||
|
|||
sum = 0; |
|||
for (i = 0; i < rounds; i++) { |
|||
sum += DELTA; |
|||
y += ((z << 4) + k[0]) ^ (z + sum) ^ ((z >> 5) + k[1]); |
|||
z += ((y << 4) + k[2]) ^ (y + sum) ^ ((y >> 5) + k[3]); |
|||
} |
|||
|
|||
*((uint32_t*)dst) = ntohl(y); |
|||
*((uint32_t*)(dst + 4)) = ntohl(z); |
|||
|
|||
//now plain-text is TCP/IP-endian;
|
|||
} |
|||
|
|||
/*pKey为16byte*/ |
|||
/*
|
|||
输入:nInBufLen为需加密的明文部分(Body)长度; |
|||
输出:返回为加密后的长度(是8byte的倍数); |
|||
*/ |
|||
/*TEA加密算法,CBC模式*/ |
|||
/*密文格式:PadLen(1byte)+Padding(var,0-7byte)+Salt(2byte)+Body(var byte)+Zero(7byte)*/ |
|||
int encryptTencentTeaLen(int nInBufLen) |
|||
{ |
|||
|
|||
int nPadSaltBodyZeroLen/*PadLen(1byte)+Salt+Body+Zero的长度*/; |
|||
int nPadlen; |
|||
|
|||
/*根据Body长度计算PadLen,最小必需长度必需为8byte的整数倍*/ |
|||
nPadSaltBodyZeroLen = nInBufLen/*Body长度*/ + 1 + SALT_LEN + ZERO_LEN/*PadLen(1byte)+Salt(2byte)+Zero(7byte)*/; |
|||
if ((nPadlen = nPadSaltBodyZeroLen % 8)) /*len=nSaltBodyZeroLen%8*/ |
|||
{ |
|||
/*模8余0需补0,余1补7,余2补6,...,余7补1*/ |
|||
nPadlen = 8 - nPadlen; |
|||
} |
|||
|
|||
return nPadlen; |
|||
} |
|||
|
|||
/*pKey为16byte*/ |
|||
/*
|
|||
输入:pInBuf为需加密的明文部分(Body),nInBufLen为pInBuf长度; |
|||
输出:pOutBuf为密文格式,pOutBufLen为pOutBuf的长度是8byte的倍数; |
|||
*/ |
|||
/*TEA加密算法,CBC模式*/ |
|||
/*密文格式:PadLen(1byte)+Padding(var,0-7byte)+Salt(2byte)+Body(var byte)+Zero(7byte)*/ |
|||
bool encryptTencentTea(std::vector<uint8_t> inBuf, std::vector<uint8_t> key, std::vector<uint8_t> &outBuf) |
|||
{ |
|||
srand(time(0)); |
|||
int nPadlen = encryptTencentTeaLen(inBuf.size()); |
|||
size_t ivCrypt; |
|||
std::vector<uint8_t> srcBuf; |
|||
srcBuf.resize(8); |
|||
std::vector<uint8_t> ivPlain; |
|||
ivPlain.resize(8); |
|||
int tmpIdx, i, j; |
|||
|
|||
/*加密第一块数据(8byte),取前面10byte*/ |
|||
srcBuf[0] = (((char)rand()) & 0x0f8)/*最低三位存PadLen,清零*/ | (char)nPadlen; |
|||
tmpIdx = 1; /*tmpIdx指向srcBuf下一个位置*/ |
|||
|
|||
while (nPadlen--) srcBuf[tmpIdx++] = (char)rand(); /*Padding*/ |
|||
|
|||
/*come here, tmpIdx must <= 8*/ |
|||
|
|||
for (i = 0; i < 8; i++) ivPlain[i] = 0; |
|||
ivCrypt = 0;//ivPlain /*make zero iv*/
|
|||
|
|||
auto outBufPos = 0; /*init outBufPos*/ |
|||
|
|||
#define cryptBlock {\ |
|||
/*tmpIdx==8*/\ |
|||
outBuf.resize(outBuf.size() + 8);\ |
|||
for (j = 0; j < 8; j++) /*加密前异或前8个byte的密文(iv_crypt指向的)*/\ |
|||
srcBuf[j] ^= outBuf[j + ivCrypt];\ |
|||
/*pOutBuffer、pInBuffer均为8byte, pKey为16byte*/\ |
|||
/*加密*/\ |
|||
TeaEncryptECB(srcBuf.data(), outBuf.data()+outBufPos, key, 16);\ |
|||
for (j = 0; j < 8; j++) /*加密后异或前8个byte的明文(iv_plain指向的)*/\ |
|||
outBuf[j + outBufPos] ^= ivPlain[j];\ |
|||
/*保存当前的iv_plain*/\ |
|||
for (j = 0; j < 8; j++) ivPlain[j] = srcBuf[j];\ |
|||
/*更新iv_crypt*/\ |
|||
tmpIdx = 0;\ |
|||
ivCrypt = outBufPos;\ |
|||
outBufPos += 8;\ |
|||
} |
|||
|
|||
|
|||
for (i = 1; i <= SALT_LEN;) /*Salt(2byte)*/ |
|||
{ |
|||
if (tmpIdx < 8) |
|||
{ |
|||
srcBuf[tmpIdx++] = (char)rand(); |
|||
i++; /*i inc in here*/ |
|||
} |
|||
if (tmpIdx == 8) |
|||
{ |
|||
cryptBlock |
|||
} |
|||
} |
|||
|
|||
/*tmpIdx指向srcBuf下一个位置*/ |
|||
|
|||
auto inBufPos = 0; |
|||
while (inBufPos < inBuf.size()) |
|||
{ |
|||
if (tmpIdx < 8) |
|||
{ |
|||
srcBuf[tmpIdx++] = inBuf[inBufPos]; |
|||
inBufPos++; |
|||
} |
|||
if (tmpIdx == 8) |
|||
{ |
|||
cryptBlock |
|||
} |
|||
} |
|||
|
|||
/*tmpIdx指向srcBuf下一个位置*/ |
|||
|
|||
for (i = 1; i <= ZERO_LEN;) |
|||
{ |
|||
if (tmpIdx < 8) |
|||
{ |
|||
srcBuf[tmpIdx++] = 0; |
|||
i++; //i inc in here
|
|||
} |
|||
if (tmpIdx == 8) |
|||
{ |
|||
cryptBlock |
|||
} |
|||
} |
|||
return true; |
|||
#undef cryptBlock |
|||
} |
|||
|
|||
bool decryptTencentTea(std::vector<uint8_t> inBuf, std::vector<uint8_t> key, std::vector<uint8_t> &out) { |
|||
if (inBuf.size() % 8 != 0) { |
|||
return false; |
|||
//inBuf size not a multiple of the block size
|
|||
} |
|||
if (inBuf.size() < 16) { |
|||
return false; |
|||
//inBuf size too small
|
|||
} |
|||
|
|||
std::vector<uint8_t> tmpBuf; |
|||
tmpBuf.resize(8); |
|||
|
|||
TeaDecryptECB(inBuf.data(), tmpBuf.data(), key, 16); |
|||
|
|||
auto nPadLen = tmpBuf[0] & 0x7; //只要最低三位
|
|||
/*密文格式:PadLen(1byte)+Padding(var,0-7byte)+Salt(2byte)+Body(var byte)+Zero(7byte)*/ |
|||
auto outLen = inBuf.size() - 1 /*PadLen*/ - nPadLen - SALT_LEN - ZERO_LEN; |
|||
std::vector<uint8_t> outBuf; |
|||
outBuf.resize(outLen); |
|||
|
|||
std::vector<uint8_t> ivPrev; |
|||
ivPrev.resize(8); |
|||
std::vector<uint8_t> ivCur; |
|||
ivCur.resize(8); |
|||
for (size_t i = 0; i < 8; i++) |
|||
{ |
|||
ivCur[i] = inBuf[i]; // init iv
|
|||
} |
|||
auto inBufPos = 8; |
|||
|
|||
// 跳过 Padding Len 和 Padding
|
|||
auto tmpIdx = 1 + nPadLen; |
|||
|
|||
// CBC IV 处理
|
|||
#define cryptBlock {\ |
|||
ivPrev = ivCur;\ |
|||
for (size_t k = inBufPos; k < inBufPos + 8; k++)\ |
|||
{\ |
|||
ivCur[k - inBufPos] = inBuf[k];\ |
|||
}\ |
|||
for (size_t j = 0; j < 8; j++) {\ |
|||
tmpBuf[j] ^= ivCur[j];\ |
|||
}\ |
|||
TeaDecryptECB(tmpBuf.data(), tmpBuf.data(), key, 16);\ |
|||
inBufPos += 8;\ |
|||
tmpIdx = 0;\ |
|||
} |
|||
|
|||
// 跳过 Salt
|
|||
for (size_t i = 1; i <= SALT_LEN; ) { |
|||
if (tmpIdx < 8) { |
|||
tmpIdx++; |
|||
i++; |
|||
} |
|||
else { |
|||
cryptBlock |
|||
} |
|||
} |
|||
|
|||
// 还原明文
|
|||
auto outBufPos = 0; |
|||
while (outBufPos < outLen) { |
|||
if (tmpIdx < 8) { |
|||
outBuf[outBufPos] = tmpBuf[tmpIdx] ^ ivPrev[tmpIdx]; |
|||
outBufPos++; |
|||
tmpIdx++; |
|||
} |
|||
else { |
|||
cryptBlock |
|||
} |
|||
} |
|||
|
|||
// 校验Zero
|
|||
for (size_t i = 1; i <= ZERO_LEN; i++) { |
|||
if (tmpBuf[i] != ivPrev[i]) { |
|||
return false; |
|||
//zero check failed
|
|||
} |
|||
} |
|||
out = outBuf; |
|||
return true; |
|||
#undef cryptBlock |
|||
} |
|||
|
|||
#endif //QQMUSIC_CPP_TENCENTTEA_HPP
|
@ -1,207 +0,0 @@ |
|||
//
|
|||
// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
|
|||
//
|
|||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|||
//
|
|||
// Official repository: https://github.com/boostorg/beast
|
|||
//
|
|||
|
|||
/*
|
|||
Portions from http://www.adp-gmbh.ch/cpp/common/base64.html
|
|||
Copyright notice: |
|||
|
|||
base64.cpp and base64.h |
|||
|
|||
Copyright (C) 2004-2008 Rene Nyffenegger |
|||
|
|||
This source code is provided 'as-is', without any express or implied |
|||
warranty. In no event will the author be held liable for any damages |
|||
arising from the use of this software. |
|||
|
|||
Permission is granted to anyone to use this software for any purpose, |
|||
including commercial applications, and to alter it and redistribute it |
|||
freely, subject to the following restrictions: |
|||
|
|||
1. The origin of this source code must not be misrepresented; you must not |
|||
claim that you wrote the original source code. If you use this source code |
|||
in a product, an acknowledgment in the product documentation would be |
|||
appreciated but is not required. |
|||
|
|||
2. Altered source versions must be plainly marked as such, and must not be |
|||
misrepresented as being the original source code. |
|||
|
|||
3. This notice may not be removed or altered from any source distribution. |
|||
|
|||
Rene Nyffenegger rene.nyffenegger@adp-gmbh.ch |
|||
*/ |
|||
|
|||
#ifndef BASE64_HPP |
|||
#define BASE64_HPP |
|||
|
|||
#include <cctype> |
|||
#include <string> |
|||
#include <utility> |
|||
|
|||
namespace base64 { |
|||
|
|||
/// Returns max chars needed to encode a base64 string
|
|||
std::size_t constexpr |
|||
encoded_size(std::size_t n) |
|||
{ |
|||
return 4 * ((n + 2) / 3); |
|||
} |
|||
|
|||
/// Returns max bytes needed to decode a base64 string
|
|||
inline |
|||
std::size_t constexpr |
|||
decoded_size(std::size_t n) |
|||
{ |
|||
return n / 4 * 3; // requires n&3==0, smaller
|
|||
} |
|||
|
|||
char const* |
|||
get_alphabet() |
|||
{ |
|||
static char constexpr tab[] = { |
|||
"ABCDEFGHIJKLMNOP" |
|||
"QRSTUVWXYZabcdef" |
|||
"ghijklmnopqrstuv" |
|||
"wxyz0123456789+/" |
|||
}; |
|||
return &tab[0]; |
|||
} |
|||
|
|||
signed char const* |
|||
get_inverse() |
|||
{ |
|||
static signed char constexpr tab[] = { |
|||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0-15
|
|||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16-31
|
|||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, // 32-47
|
|||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, // 48-63
|
|||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 64-79
|
|||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, // 80-95
|
|||
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96-111
|
|||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, // 112-127
|
|||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 128-143
|
|||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 144-159
|
|||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 160-175
|
|||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 176-191
|
|||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 192-207
|
|||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 208-223
|
|||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 224-239
|
|||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 240-255
|
|||
}; |
|||
return &tab[0]; |
|||
} |
|||
|
|||
/** Encode a series of octets as a padded, base64 string.
|
|||
|
|||
The resulting string will not be null terminated. |
|||
|
|||
@par Requires |
|||
|
|||
The memory pointed to by `out` points to valid memory |
|||
of at least `encoded_size(len)` bytes. |
|||
|
|||
@return The number of characters written to `out`. This |
|||
will exclude any null termination. |
|||
*/ |
|||
std::size_t |
|||
encode(void* dest, void const* src, std::size_t len) |
|||
{ |
|||
char* out = static_cast<char*>(dest); |
|||
char const* in = static_cast<char const*>(src); |
|||
auto const tab = base64::get_alphabet(); |
|||
|
|||
for (auto n = len / 3; n--;) |
|||
{ |
|||
*out++ = tab[(in[0] & 0xfc) >> 2]; |
|||
*out++ = tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)]; |
|||
*out++ = tab[((in[2] & 0xc0) >> 6) + ((in[1] & 0x0f) << 2)]; |
|||
*out++ = tab[in[2] & 0x3f]; |
|||
in += 3; |
|||
} |
|||
|
|||
switch (len % 3) |
|||
{ |
|||
case 2: |
|||
*out++ = tab[(in[0] & 0xfc) >> 2]; |
|||
*out++ = tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)]; |
|||
*out++ = tab[(in[1] & 0x0f) << 2]; |
|||
*out++ = '='; |
|||
break; |
|||
|
|||
case 1: |
|||
*out++ = tab[(in[0] & 0xfc) >> 2]; |
|||
*out++ = tab[((in[0] & 0x03) << 4)]; |
|||
*out++ = '='; |
|||
*out++ = '='; |
|||
break; |
|||
|
|||
case 0: |
|||
break; |
|||
} |
|||
|
|||
return out - static_cast<char*>(dest); |
|||
} |
|||
|
|||
/** Decode a padded base64 string into a series of octets.
|
|||
|
|||
@par Requires |
|||
|
|||
The memory pointed to by `out` points to valid memory |
|||
of at least `decoded_size(len)` bytes. |
|||
|
|||
@return The number of octets written to `out`, and |
|||
the number of characters read from the input string, |
|||
expressed as a pair. |
|||
*/ |
|||
std::pair<std::size_t, std::size_t> |
|||
decode(void* dest, char const* src, std::size_t len) |
|||
{ |
|||
char* out = static_cast<char*>(dest); |
|||
auto in = reinterpret_cast<unsigned char const*>(src); |
|||
unsigned char c3[3], c4[4]; |
|||
int i = 0; |
|||
int j = 0; |
|||
|
|||
auto const inverse = base64::get_inverse(); |
|||
|
|||
while (len-- && *in != '=') |
|||
{ |
|||
auto const v = inverse[*in]; |
|||
if (v == -1) |
|||
break; |
|||
++in; |
|||
c4[i] = v; |
|||
if (++i == 4) |
|||
{ |
|||
c3[0] = (c4[0] << 2) + ((c4[1] & 0x30) >> 4); |
|||
c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2); |
|||
c3[2] = ((c4[2] & 0x3) << 6) + c4[3]; |
|||
|
|||
for (i = 0; i < 3; i++) |
|||
*out++ = c3[i]; |
|||
i = 0; |
|||
} |
|||
} |
|||
|
|||
if (i) |
|||
{ |
|||
c3[0] = (c4[0] << 2) + ((c4[1] & 0x30) >> 4); |
|||
c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2); |
|||
c3[2] = ((c4[2] & 0x3) << 6) + c4[3]; |
|||
|
|||
for (j = 0; j < i - 1; j++) |
|||
*out++ = c3[j]; |
|||
} |
|||
|
|||
return { out - static_cast<char*>(dest), |
|||
in - reinterpret_cast<unsigned char const*>(src) }; |
|||
} |
|||
|
|||
} // base64
|
|||
|
|||
#endif |
@ -1,41 +0,0 @@ |
|||
#!/usr/bin/env bash |
|||
|
|||
set -e |
|||
|
|||
pushd "$(realpath "$(dirname "$0")")" |
|||
|
|||
CURR_DIR="${PWD}" |
|||
|
|||
BUILD_TYPE="$1" |
|||
if [ -z "$BUILD_TYPE" ]; then |
|||
BUILD_TYPE=Release |
|||
fi |
|||
|
|||
# CI: already had emsdk installed. |
|||
if ! command -v emcc; then |
|||
if [ ! -d ../../build/emsdk ]; then |
|||
git clone https://github.com/emscripten-core/emsdk.git ../../build/emsdk |
|||
fi |
|||
|
|||
pushd ../../build/emsdk |
|||
./emsdk install 3.0.0 |
|||
./emsdk activate 3.0.0 |
|||
source ./emsdk_env.sh |
|||
popd # ../../build/emsdk |
|||
fi |
|||
|
|||
mkdir -p build/wasm |
|||
pushd build/wasm |
|||
emcmake cmake -DCMAKE_BUILD_TYPE="$BUILD_TYPE" ../.. |
|||
make -j |
|||
TARGET_FILES=" |
|||
QmcLegacy.js |
|||
QmcWasm.js |
|||
QmcWasm.wasm |
|||
QmcWasmBundle.js |
|||
" |
|||
|
|||
cp $TARGET_FILES "${CURR_DIR}/" |
|||
popd # build/wasm |
|||
|
|||
popd |
@ -1,230 +0,0 @@ |
|||
#include <string.h> |
|||
#include <cmath> |
|||
#include <vector> |
|||
#include <arpa/inet.h> |
|||
#include "qmc_key.hpp" |
|||
#include "qmc_cipher.hpp" |
|||
|
|||
class QmcDecode { |
|||
private: |
|||
std::vector<uint8_t> blobData; |
|||
|
|||
std::vector<uint8_t> rawKeyBuf; |
|||
std::string cipherType = ""; |
|||
|
|||
size_t dataOffset = 0; |
|||
size_t keySize = 0; |
|||
int mediaVer = 0; |
|||
|
|||
std::string checkType(std::string fn) { |
|||
if (fn.find(".qmc") < fn.size() || fn.find(".m") < fn.size()) |
|||
{ |
|||
std::string buf_tag = ""; |
|||
for (int i = 4; i > 0; --i) |
|||
{ |
|||
buf_tag += *((char*)blobData.data() + blobData.size() - i); |
|||
} |
|||
if (buf_tag == "QTag") |
|||
{ |
|||
keySize = ntohl(*(uint32_t*)(blobData.data() + blobData.size() - 8)); |
|||
return "QTag"; |
|||
} |
|||
else if (buf_tag == "STag") |
|||
{ |
|||
return "STag"; |
|||
} |
|||
else |
|||
{ |
|||
keySize = (*(uint32_t*)(blobData.data() + blobData.size() - 4)); |
|||
if (keySize < 0x400) |
|||
{ |
|||
return "Map/RC4"; |
|||
} |
|||
else |
|||
{ |
|||
keySize = 0; |
|||
return "Static"; |
|||
} |
|||
} |
|||
} |
|||
else if (fn.find(".cache") < fn.size()) |
|||
{ |
|||
return "cache"; |
|||
} |
|||
else if (fn.find(".tm") < fn.size()) |
|||
{ |
|||
return "ios"; |
|||
} |
|||
else |
|||
{ |
|||
return "invalid"; |
|||
} |
|||
} |
|||
|
|||
bool parseRawKeyQTag() { |
|||
std::string ketStr = ""; |
|||
std::string::size_type index = 0; |
|||
ketStr.append((char*)rawKeyBuf.data(), rawKeyBuf.size()); |
|||
index = ketStr.find(",", 0); |
|||
if (index != std::string::npos) |
|||
{ |
|||
rawKeyBuf.resize(index); |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
ketStr = ketStr.substr(index + 1); |
|||
index = ketStr.find(",", 0); |
|||
if (index != std::string::npos) |
|||
{ |
|||
this->songId = ketStr.substr(0, index); |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
ketStr = ketStr.substr(index + 1); |
|||
index = ketStr.find(",", 0); |
|||
if (index == std::string::npos) |
|||
{ |
|||
this->mediaVer = std::stoi(ketStr); |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
bool readRawKey(size_t tailSize) { |
|||
// get raw key data length
|
|||
rawKeyBuf.resize(keySize); |
|||
if (rawKeyBuf.size() != keySize) { |
|||
return false; |
|||
} |
|||
for (size_t i = 0; i < keySize; i++) |
|||
{ |
|||
rawKeyBuf[i] = blobData[i + blobData.size() - (tailSize + keySize)]; |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
void DecodeStatic(); |
|||
|
|||
void DecodeMapRC4(); |
|||
|
|||
void DecodeCache(); |
|||
|
|||
void DecodeTm(); |
|||
|
|||
public: |
|||
bool SetBlob(uint8_t* blob, size_t blobSize) { |
|||
blobData.resize(blobSize); |
|||
if (blobData.size() != blobSize) { |
|||
return false; |
|||
} |
|||
memcpy(blobData.data(), blob, blobSize); |
|||
return true; |
|||
} |
|||
|
|||
int PreDecode(std::string ext) { |
|||
cipherType = checkType(ext); |
|||
size_t tailSize = 0; |
|||
if (cipherType == "invalid" || cipherType == "STag") { |
|||
error = "file is invalid or not supported (Please downgrade your app)."; |
|||
return -1; |
|||
} |
|||
if (cipherType == "QTag") { |
|||
tailSize = 8; |
|||
} |
|||
else if (cipherType == "Map/RC4") { |
|||
tailSize = 4; |
|||
} |
|||
if (keySize > 0) { |
|||
if (!readRawKey(tailSize)) { |
|||
error = "cannot read embedded key from file"; |
|||
return -1; |
|||
} |
|||
if (tailSize == 8) { |
|||
cipherType = "Map/RC4"; |
|||
if (!parseRawKeyQTag()) { |
|||
error = "cannot parse embedded key"; |
|||
return -1; |
|||
} |
|||
} |
|||
std::vector<uint8_t> tmp; |
|||
if (!QmcDecryptKey(rawKeyBuf, tmp)) { |
|||
error = "cannot decrypt embedded key"; |
|||
return -1; |
|||
} |
|||
rawKeyBuf = tmp; |
|||
} |
|||
return keySize + tailSize; |
|||
} |
|||
|
|||
std::vector<uint8_t> Decode(size_t offset); |
|||
|
|||
std::string songId = ""; |
|||
std::string error = ""; |
|||
}; |
|||
|
|||
void QmcDecode::DecodeStatic() |
|||
{ |
|||
QmcStaticCipher sc; |
|||
sc.proc(blobData, dataOffset); |
|||
} |
|||
|
|||
void QmcDecode::DecodeMapRC4() { |
|||
if (rawKeyBuf.size() > 300) |
|||
{ |
|||
QmcRC4Cipher c(rawKeyBuf, 2); |
|||
c.proc(blobData, dataOffset); |
|||
} |
|||
else |
|||
{ |
|||
QmcMapCipher c(rawKeyBuf, 2); |
|||
c.proc(blobData, dataOffset); |
|||
} |
|||
} |
|||
|
|||
void QmcDecode::DecodeCache() |
|||
{ |
|||
for (size_t i = 0; i < blobData.size(); i++) { |
|||
blobData[i] ^= 0xf4; |
|||
blobData[i] = ((blobData[i] & 0b00111111) << 2) | (blobData[i] >> 6); // rol 2
|
|||
} |
|||
} |
|||
|
|||
void QmcDecode::DecodeTm() |
|||
{ |
|||
uint8_t const TM_HEADER[] = { 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70 }; |
|||
for (size_t cur = dataOffset, i = 0; cur < 8 && i < blobData.size(); ++cur, ++i) { |
|||
blobData[i] = TM_HEADER[dataOffset]; |
|||
} |
|||
} |
|||
|
|||
std::vector<uint8_t> QmcDecode::Decode(size_t offset) |
|||
{ |
|||
dataOffset = offset; |
|||
if (cipherType == "Map/RC4") |
|||
{ |
|||
DecodeMapRC4(); |
|||
} |
|||
else if (cipherType == "Static") |
|||
{ |
|||
DecodeStatic(); |
|||
} |
|||
else if (cipherType == "cache") |
|||
{ |
|||
DecodeCache(); |
|||
} |
|||
else if (cipherType == "ios") |
|||
{ |
|||
DecodeTm(); |
|||
} |
|||
else { |
|||
error = "File is invalid or encryption type is not supported."; |
|||
} |
|||
return blobData; |
|||
} |
@ -1,290 +0,0 @@ |
|||
#include <cstdint> |
|||
#include <vector> |
|||
class QmcStaticCipher { |
|||
private: |
|||
uint8_t staticCipherBox[256] = { |
|||
0x77, 0x48, 0x32, 0x73, 0xDE, 0xF2, 0xC0, 0xC8, //0x00
|
|||
0x95, 0xEC, 0x30, 0xB2, 0x51, 0xC3, 0xE1, 0xA0, //0x08
|
|||
0x9E, 0xE6, 0x9D, 0xCF, 0xFA, 0x7F, 0x14, 0xD1, //0x10
|
|||
0xCE, 0xB8, 0xDC, 0xC3, 0x4A, 0x67, 0x93, 0xD6, //0x18
|
|||
0x28, 0xC2, 0x91, 0x70, 0xCA, 0x8D, 0xA2, 0xA4, //0x20
|
|||
0xF0, 0x08, 0x61, 0x90, 0x7E, 0x6F, 0xA2, 0xE0, //0x28
|
|||
0xEB, 0xAE, 0x3E, 0xB6, 0x67, 0xC7, 0x92, 0xF4, //0x30
|
|||
0x91, 0xB5, 0xF6, 0x6C, 0x5E, 0x84, 0x40, 0xF7, //0x38
|
|||
0xF3, 0x1B, 0x02, 0x7F, 0xD5, 0xAB, 0x41, 0x89, //0x40
|
|||
0x28, 0xF4, 0x25, 0xCC, 0x52, 0x11, 0xAD, 0x43, //0x48
|
|||
0x68, 0xA6, 0x41, 0x8B, 0x84, 0xB5, 0xFF, 0x2C, //0x50
|
|||
0x92, 0x4A, 0x26, 0xD8, 0x47, 0x6A, 0x7C, 0x95, //0x58
|
|||
0x61, 0xCC, 0xE6, 0xCB, 0xBB, 0x3F, 0x47, 0x58, //0x60
|
|||
0x89, 0x75, 0xC3, 0x75, 0xA1, 0xD9, 0xAF, 0xCC, //0x68
|
|||
0x08, 0x73, 0x17, 0xDC, 0xAA, 0x9A, 0xA2, 0x16, //0x70
|
|||
0x41, 0xD8, 0xA2, 0x06, 0xC6, 0x8B, 0xFC, 0x66, //0x78
|
|||
0x34, 0x9F, 0xCF, 0x18, 0x23, 0xA0, 0x0A, 0x74, //0x80
|
|||
0xE7, 0x2B, 0x27, 0x70, 0x92, 0xE9, 0xAF, 0x37, //0x88
|
|||
0xE6, 0x8C, 0xA7, 0xBC, 0x62, 0x65, 0x9C, 0xC2, //0x90
|
|||
0x08, 0xC9, 0x88, 0xB3, 0xF3, 0x43, 0xAC, 0x74, //0x98
|
|||
0x2C, 0x0F, 0xD4, 0xAF, 0xA1, 0xC3, 0x01, 0x64, //0xA0
|
|||
0x95, 0x4E, 0x48, 0x9F, 0xF4, 0x35, 0x78, 0x95, //0xA8
|
|||
0x7A, 0x39, 0xD6, 0x6A, 0xA0, 0x6D, 0x40, 0xE8, //0xB0
|
|||
0x4F, 0xA8, 0xEF, 0x11, 0x1D, 0xF3, 0x1B, 0x3F, //0xB8
|
|||
0x3F, 0x07, 0xDD, 0x6F, 0x5B, 0x19, 0x30, 0x19, //0xC0
|
|||
0xFB, 0xEF, 0x0E, 0x37, 0xF0, 0x0E, 0xCD, 0x16, //0xC8
|
|||
0x49, 0xFE, 0x53, 0x47, 0x13, 0x1A, 0xBD, 0xA4, //0xD0
|
|||
0xF1, 0x40, 0x19, 0x60, 0x0E, 0xED, 0x68, 0x09, //0xD8
|
|||
0x06, 0x5F, 0x4D, 0xCF, 0x3D, 0x1A, 0xFE, 0x20, //0xE0
|
|||
0x77, 0xE4, 0xD9, 0xDA, 0xF9, 0xA4, 0x2B, 0x76, //0xE8
|
|||
0x1C, 0x71, 0xDB, 0x00, 0xBC, 0xFD, 0x0C, 0x6C, //0xF0
|
|||
0xA5, 0x47, 0xF7, 0xF6, 0x00, 0x79, 0x4A, 0x11 //0xF8
|
|||
}; |
|||
|
|||
uint8_t getMask(size_t offset) { |
|||
if (offset > 0x7fff) offset %= 0x7fff; |
|||
return staticCipherBox[(offset * offset + 27) & 0xff]; |
|||
} |
|||
|
|||
public: |
|||
void proc(std::vector<uint8_t>& buf, size_t offset) { |
|||
for (size_t i = 0; i < buf.size(); i++) { |
|||
buf[i] ^= getMask(offset + i); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
class QmcMapCipher { |
|||
private: |
|||
std::vector<uint8_t> key; |
|||
|
|||
uint8_t rotate(uint8_t value, size_t bits) { |
|||
auto rotate = (bits + 4) % 8; |
|||
auto left = value << rotate; |
|||
auto right = value >> rotate; |
|||
return (left | right) & 0xff; |
|||
} |
|||
|
|||
uint8_t getMask(size_t offset) { |
|||
if (offset > 0x7fff) offset %= 0x7fff; |
|||
|
|||
const auto idx = (offset * offset + 71214) % key.size(); |
|||
return rotate(key[idx], idx & 0x7); |
|||
} |
|||
|
|||
public: |
|||
QmcMapCipher(std::vector<uint8_t> &argKey, short operation) { |
|||
if (operation == 2) |
|||
{ |
|||
if (argKey.size() == 0) { |
|||
return; |
|||
} |
|||
} |
|||
else if (operation == 1) |
|||
{ |
|||
const char WordList[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; |
|||
srand(time(0)); |
|||
uint32_t number = 0; |
|||
while (number > 300 || number == 0) |
|||
{ |
|||
number = rand(); |
|||
} |
|||
argKey.resize(number); |
|||
for (int i = 0; i < argKey.size(); i++) { |
|||
number = rand(); |
|||
argKey[i] = WordList[number % 62]; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
key = argKey; |
|||
} |
|||
|
|||
void proc(std::vector<uint8_t>& buf, size_t offset) { |
|||
for (size_t i = 0; i < buf.size(); i++) { |
|||
buf[i] ^= getMask(offset + i); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
class QmcRC4Cipher { |
|||
public: |
|||
void proc(std::vector<uint8_t>& buf, size_t offset) { |
|||
// Macro: common code after each process
|
|||
#define postProcess(len) \ |
|||
{ \ |
|||
toProcess -= len; \ |
|||
processed += len; \ |
|||
offset += len; \ |
|||
/* no more data */ \ |
|||
if (toProcess == 0) { \ |
|||
return; \ |
|||
} \ |
|||
} |
|||
|
|||
size_t toProcess = buf.size(); |
|||
size_t processed = 0; |
|||
std::vector<uint8_t> tmpbuf; |
|||
|
|||
// 前 128 字节使用不同的解密方案
|
|||
if (offset < FIRST_SEGMENT_SIZE) { |
|||
size_t len_segment = std::min(FIRST_SEGMENT_SIZE - offset, buf.size()); |
|||
tmpbuf.resize(len_segment); |
|||
for (size_t i = 0; i < len_segment; i++) |
|||
{ |
|||
tmpbuf[i] = buf[processed + i]; |
|||
} |
|||
procFirstSegment(tmpbuf, offset); |
|||
for (size_t i = 0; i < len_segment; i++) |
|||
{ |
|||
buf[processed + i] = tmpbuf[i]; |
|||
} |
|||
postProcess(len_segment); |
|||
} |
|||
|
|||
|
|||
// 区块对齐
|
|||
if (offset % SEGMENT_SIZE != 0) { |
|||
size_t len_segment = std::min(SEGMENT_SIZE - (offset % SEGMENT_SIZE), toProcess); |
|||
tmpbuf.resize(len_segment); |
|||
for (size_t i = 0; i < len_segment; i++) |
|||
{ |
|||
tmpbuf[i] = buf[processed + i]; |
|||
} |
|||
procASegment(tmpbuf, offset); |
|||
for (size_t i = 0; i < len_segment; i++) |
|||
{ |
|||
buf[processed + i] = tmpbuf[i]; |
|||
} |
|||
postProcess(len_segment); |
|||
} |
|||
|
|||
// 对每个区块逐一进行解密
|
|||
while (toProcess > SEGMENT_SIZE) { |
|||
tmpbuf.resize(SEGMENT_SIZE); |
|||
for (size_t i = 0; i < SEGMENT_SIZE; i++) |
|||
{ |
|||
tmpbuf[i] = buf[processed + i]; |
|||
} |
|||
procASegment(tmpbuf, offset); |
|||
for (size_t i = 0; i < SEGMENT_SIZE; i++) |
|||
{ |
|||
buf[processed + i] = tmpbuf[i]; |
|||
} |
|||
postProcess(SEGMENT_SIZE); |
|||
} |
|||
|
|||
if (toProcess > 0) { |
|||
tmpbuf.resize(toProcess); |
|||
for (size_t i = 0; i < toProcess; i++) |
|||
{ |
|||
tmpbuf[i] = buf[processed + i]; |
|||
} |
|||
procASegment(tmpbuf, offset); |
|||
for (size_t i = 0; i < toProcess; i++) |
|||
{ |
|||
buf[processed + i] = tmpbuf[i]; |
|||
} |
|||
} |
|||
|
|||
#undef postProcess |
|||
} |
|||
|
|||
QmcRC4Cipher(std::vector<uint8_t>& argKey, short operation) { |
|||
if (operation == 2) |
|||
{ |
|||
if (argKey.size() == 0) { |
|||
return; |
|||
} |
|||
} |
|||
else if (operation == 1) |
|||
{ |
|||
const char WordList[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; |
|||
srand(time(0)); |
|||
uint32_t number = 0; |
|||
while (number <= 300 || number >= 512) |
|||
{ |
|||
number = rand(); |
|||
} |
|||
argKey.resize(number); |
|||
for (int i = 0; i < argKey.size(); i++) { |
|||
number = rand(); |
|||
argKey[i] = WordList[number % 62]; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
key = argKey; |
|||
|
|||
// init seed box
|
|||
S.resize(key.size()); |
|||
for (size_t i = 0; i < key.size(); ++i) { |
|||
S[i] = i & 0xff; |
|||
} |
|||
size_t j = 0; |
|||
for (size_t i = 0; i < key.size(); ++i) { |
|||
j = (S[i] + j + key[i % key.size()]) % key.size(); |
|||
std::swap(S[i], S[j]); |
|||
} |
|||
|
|||
// init hash base
|
|||
hash = 1; |
|||
for (size_t i = 0; i < key.size(); i++) { |
|||
uint8_t value = key[i]; |
|||
|
|||
// ignore if key char is '\x00'
|
|||
if (!value) continue; |
|||
|
|||
auto next_hash = hash * value; |
|||
if (next_hash == 0 || next_hash <= hash) break; |
|||
|
|||
hash = next_hash; |
|||
} |
|||
} |
|||
|
|||
private: |
|||
const size_t FIRST_SEGMENT_SIZE = 0x80; |
|||
const size_t SEGMENT_SIZE = 5120; |
|||
|
|||
std::vector<uint8_t> S; |
|||
std::vector<uint8_t> key; |
|||
uint32_t hash = 1; |
|||
|
|||
void procFirstSegment(std::vector<uint8_t>& buf, size_t offset) { |
|||
for (size_t i = 0; i < buf.size(); i++) { |
|||
buf[i] ^= key[getSegmentKey(offset + i)]; |
|||
} |
|||
} |
|||
|
|||
void procASegment(std::vector<uint8_t>& buf, size_t offset) { |
|||
// Initialise a new seed box
|
|||
std::vector<uint8_t> nS; |
|||
nS = S; |
|||
|
|||
// Calculate the number of bytes to skip.
|
|||
// The initial "key" derived from segment id, plus the current offset.
|
|||
int64_t skipLen = (offset % SEGMENT_SIZE) + getSegmentKey(int(offset / SEGMENT_SIZE)); |
|||
|
|||
// decrypt the block
|
|||
size_t j = 0; |
|||
size_t k = 0; |
|||
int i = -skipLen; |
|||
for (; i < (int)buf.size(); i++) { |
|||
j = (j + 1) % key.size(); |
|||
k = (nS[j] + k) % key.size(); |
|||
std::swap(nS[k], nS[j]); |
|||
|
|||
if (i >= 0) { |
|||
buf[i] ^= nS[(nS[j] + nS[k]) % key.size()]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
uint64_t getSegmentKey(int id) { |
|||
auto seed = key[id % key.size()]; |
|||
uint64_t idx = ((double)hash / ((id + 1) * seed)) * 100.0; |
|||
return idx % key.size(); |
|||
} |
|||
}; |
@ -1,217 +0,0 @@ |
|||
#include"TencentTea.hpp" |
|||
#include "base64.hpp" |
|||
|
|||
void simpleMakeKey(uint8_t salt, int length, std::vector<uint8_t> &key_buf) { |
|||
for (size_t i = 0; i < length; ++i) { |
|||
double tmp = tan((float)salt + (double)i * 0.1); |
|||
key_buf[i] = 0xFF & (uint8_t)(fabs(tmp) * 100.0); |
|||
} |
|||
} |
|||
|
|||
std::vector<uint8_t> v2KeyPrefix = { 0x51, 0x51, 0x4D, 0x75, 0x73, 0x69, 0x63, 0x20, 0x45, 0x6E, 0x63, 0x56, 0x32, 0x2C, 0x4B, 0x65, 0x79, 0x3A }; |
|||
|
|||
bool decryptV2Key(std::vector<uint8_t> key, std::vector<uint8_t>& outVec) |
|||
{ |
|||
if (v2KeyPrefix.size() > key.size()) |
|||
{ |
|||
return true; |
|||
} |
|||
for (size_t i = 0; i < v2KeyPrefix.size(); i++) |
|||
{ |
|||
if (key[i] != v2KeyPrefix[i]) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
std::vector<uint8_t> mixKey1 = { 0x33, 0x38, 0x36, 0x5A, 0x4A, 0x59, 0x21, 0x40, 0x23, 0x2A, 0x24, 0x25, 0x5E, 0x26, 0x29, 0x28 }; |
|||
std::vector<uint8_t> mixKey2 = { 0x2A, 0x2A, 0x23, 0x21, 0x28, 0x23, 0x24, 0x25, 0x26, 0x5E, 0x61, 0x31, 0x63, 0x5A, 0x2C, 0x54 }; |
|||
|
|||
std::vector<uint8_t> out; |
|||
std::vector<uint8_t> tmpKey; |
|||
tmpKey.resize(key.size() - 18); |
|||
for (size_t i = 0; i < tmpKey.size(); i++) |
|||
{ |
|||
tmpKey[i] = key[18 + i]; |
|||
} |
|||
if (!decryptTencentTea(tmpKey, mixKey1, out)) |
|||
{ |
|||
outVec.resize(0); |
|||
//EncV2 key decode failed.
|
|||
return false; |
|||
} |
|||
|
|||
tmpKey.resize(out.size()); |
|||
for (size_t i = 0; i < tmpKey.size(); i++) |
|||
{ |
|||
tmpKey[i] = out[i]; |
|||
} |
|||
out.resize(0); |
|||
if (!decryptTencentTea(tmpKey, mixKey2, out)) |
|||
{ |
|||
outVec.resize(0); |
|||
//EncV2 key decode failed.
|
|||
return false; |
|||
} |
|||
|
|||
outVec.resize(base64::decoded_size(out.size())); |
|||
auto n = base64::decode(outVec.data(), (const char*)(out.data()), out.size()).first; |
|||
|
|||
if (n < 16) |
|||
{ |
|||
outVec.resize(0); |
|||
//EncV2 key size is too small.
|
|||
return false; |
|||
} |
|||
outVec.resize(n); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
bool encryptV2Key(std::vector<uint8_t> key, std::vector<uint8_t>& outVec) |
|||
{ |
|||
if (key.size() < 16) |
|||
{ |
|||
outVec.resize(0); |
|||
//EncV2 key size is too small.
|
|||
return false; |
|||
} |
|||
|
|||
std::vector<uint8_t> in; |
|||
in.resize(base64::encoded_size(key.size())); |
|||
auto n = base64::encode(in.data(), (const char*)(key.data()), key.size()); |
|||
in.resize(n); |
|||
|
|||
std::vector<uint8_t> mixKey1 = { 0x33, 0x38, 0x36, 0x5A, 0x4A, 0x59, 0x21, 0x40, 0x23, 0x2A, 0x24, 0x25, 0x5E, 0x26, 0x29, 0x28 }; |
|||
std::vector<uint8_t> mixKey2 = { 0x2A, 0x2A, 0x23, 0x21, 0x28, 0x23, 0x24, 0x25, 0x26, 0x5E, 0x61, 0x31, 0x63, 0x5A, 0x2C, 0x54 }; |
|||
|
|||
std::vector<uint8_t> tmpKey; |
|||
if (!encryptTencentTea(in, mixKey2, tmpKey)) |
|||
{ |
|||
outVec.resize(0); |
|||
//EncV2 key decode failed.
|
|||
return false; |
|||
} |
|||
in.resize(tmpKey.size()); |
|||
for (size_t i = 0; i < tmpKey.size(); i++) |
|||
{ |
|||
in[i] = tmpKey[i]; |
|||
} |
|||
tmpKey.resize(0); |
|||
|
|||
if (!encryptTencentTea(in, mixKey1, tmpKey)) |
|||
{ |
|||
outVec.resize(0); |
|||
//EncV2 key decode failed.
|
|||
return false; |
|||
} |
|||
outVec.resize(tmpKey.size() + 18); |
|||
for (size_t i = 0; i < tmpKey.size(); i++) |
|||
{ |
|||
outVec[18 + i] = tmpKey[i]; |
|||
} |
|||
|
|||
for (size_t i = 0; i < v2KeyPrefix.size(); i++) |
|||
{ |
|||
outVec[i] = v2KeyPrefix[i]; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
bool QmcDecryptKey(std::vector<uint8_t> raw, std::vector<uint8_t> &outVec) { |
|||
std::vector<uint8_t> rawDec; |
|||
rawDec.resize(base64::decoded_size(raw.size())); |
|||
auto n = base64::decode(rawDec.data(), (const char*)(raw.data()), raw.size()).first; |
|||
if (n < 16) { |
|||
return false; |
|||
//key length is too short
|
|||
} |
|||
rawDec.resize(n); |
|||
|
|||
std::vector<uint8_t> tmpIn = rawDec; |
|||
if (!decryptV2Key(tmpIn, rawDec)) |
|||
{ |
|||
//decrypt EncV2 failed.
|
|||
return false; |
|||
} |
|||
|
|||
std::vector<uint8_t> simpleKey; |
|||
simpleKey.resize(8); |
|||
simpleMakeKey(106, 8, simpleKey); |
|||
std::vector<uint8_t> teaKey; |
|||
teaKey.resize(16); |
|||
for (size_t i = 0; i < 8; i++) { |
|||
teaKey[i << 1] = simpleKey[i]; |
|||
teaKey[(i << 1) + 1] = rawDec[i]; |
|||
} |
|||
std::vector<uint8_t> out; |
|||
std::vector<uint8_t> tmpRaw; |
|||
tmpRaw.resize(rawDec.size() - 8); |
|||
for (size_t i = 0; i < tmpRaw.size(); i++) |
|||
{ |
|||
tmpRaw[i] = rawDec[8 + i]; |
|||
} |
|||
if (decryptTencentTea(tmpRaw, teaKey, out)) |
|||
{ |
|||
rawDec.resize(8 + out.size()); |
|||
for (size_t i = 0; i < out.size(); i++) |
|||
{ |
|||
rawDec[8 + i] = out[i]; |
|||
} |
|||
outVec = rawDec; |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
bool QmcEncryptKey(std::vector<uint8_t> raw, std::vector<uint8_t>& outVec, bool useEncV2 = true) { |
|||
std::vector<uint8_t> simpleKey; |
|||
simpleKey.resize(8); |
|||
simpleMakeKey(106, 8, simpleKey); |
|||
std::vector<uint8_t> teaKey; |
|||
teaKey.resize(16); |
|||
for (size_t i = 0; i < 8; i++) { |
|||
teaKey[i << 1] = simpleKey[i]; |
|||
teaKey[(i << 1) + 1] = raw[i]; |
|||
} |
|||
std::vector<uint8_t> out; |
|||
out.resize(raw.size() - 8); |
|||
for (size_t i = 0; i < out.size(); i++) |
|||
{ |
|||
out[i] = raw[8 + i]; |
|||
} |
|||
std::vector<uint8_t> tmpRaw; |
|||
if (encryptTencentTea(out, teaKey, tmpRaw)) |
|||
{ |
|||
raw.resize(tmpRaw.size() + 8); |
|||
for (size_t i = 0; i < tmpRaw.size(); i++) |
|||
{ |
|||
raw[i + 8] = tmpRaw[i]; |
|||
} |
|||
|
|||
if (useEncV2) |
|||
{ |
|||
std::vector<uint8_t> tmpIn = raw; |
|||
if (!encryptV2Key(tmpIn, raw)) |
|||
{ |
|||
//encrypt EncV2 failed.
|
|||
return false; |
|||
} |
|||
} |
|||
|
|||
std::vector<uint8_t> rawEnc; |
|||
rawEnc.resize(base64::encoded_size(raw.size())); |
|||
auto n = base64::encode(rawEnc.data(), (const char*)(raw.data()), raw.size()); |
|||
rawEnc.resize(n); |
|||
outVec = rawEnc; |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
} |
@ -1,29 +0,0 @@ |
|||
import fs from 'fs'; |
|||
import { QmcDecoder } from '@/decrypt/qmc'; |
|||
import { BytesEqual } from '@/decrypt/utils'; |
|||
|
|||
function loadTestDataDecoder(name: string): { |
|||
cipherText: Uint8Array; |
|||
clearText: Uint8Array; |
|||
} { |
|||
const cipherBody = fs.readFileSync(`./testdata/${name}_raw.bin`); |
|||
const cipherSuffix = fs.readFileSync(`./testdata/${name}_suffix.bin`); |
|||
const cipherText = new Uint8Array(cipherBody.length + cipherSuffix.length); |
|||
cipherText.set(cipherBody); |
|||
cipherText.set(cipherSuffix, cipherBody.length); |
|||
return { |
|||
cipherText, |
|||
clearText: fs.readFileSync(`testdata/${name}_target.bin`), |
|||
}; |
|||
} |
|||
|
|||
test('qmc: real file', async () => { |
|||
const cases = ['mflac0_rc4', 'mflac_rc4', 'mflac_map', 'mgg_map', 'qmc0_static']; |
|||
for (const name of cases) { |
|||
const { clearText, cipherText } = loadTestDataDecoder(name); |
|||
const c = new QmcDecoder(cipherText); |
|||
const buf = c.decrypt(); |
|||
|
|||
expect(BytesEqual(buf, clearText)).toBeTruthy(); |
|||
} |
|||
}); |
@ -1,117 +0,0 @@ |
|||
import { QmcMapCipher, QmcRC4Cipher, QmcStaticCipher } from '@/decrypt/qmc_cipher'; |
|||
import fs from 'fs'; |
|||
|
|||
test('static cipher [0x7ff8,0x8000) ', () => { |
|||
//prettier-ignore
|
|||
const expected = new Uint8Array([ |
|||
0xD8, 0x52, 0xF7, 0x67, 0x90, 0xCA, 0xD6, 0x4A, |
|||
0x4A, 0xD6, 0xCA, 0x90, 0x67, 0xF7, 0x52, 0xD8, |
|||
]) |
|||
|
|||
const c = new QmcStaticCipher(); |
|||
const buf = new Uint8Array(16); |
|||
c.decrypt(buf, 0x7ff8); |
|||
|
|||
expect(buf).toStrictEqual(expected); |
|||
}); |
|||
|
|||
test('static cipher [0,0x10) ', () => { |
|||
//prettier-ignore
|
|||
const expected = new Uint8Array([ |
|||
0xC3, 0x4A, 0xD6, 0xCA, 0x90, 0x67, 0xF7, 0x52, |
|||
0xD8, 0xA1, 0x66, 0x62, 0x9F, 0x5B, 0x09, 0x00, |
|||
]) |
|||
|
|||
const c = new QmcStaticCipher(); |
|||
const buf = new Uint8Array(16); |
|||
c.decrypt(buf, 0); |
|||
|
|||
expect(buf).toStrictEqual(expected); |
|||
}); |
|||
|
|||
test('map cipher: get mask', () => { |
|||
//prettier-ignore
|
|||
const expected = new Uint8Array([ |
|||
0xBB, 0x7D, 0x80, 0xBE, 0xFF, 0x38, 0x81, 0xFB, |
|||
0xBB, 0xFF, 0x82, 0x3C, 0xFF, 0xBA, 0x83, 0x79, |
|||
]) |
|||
const key = new Uint8Array(256); |
|||
for (let i = 0; i < 256; i++) key[i] = i; |
|||
const buf = new Uint8Array(16); |
|||
|
|||
const c = new QmcMapCipher(key); |
|||
c.decrypt(buf, 0); |
|||
expect(buf).toStrictEqual(expected); |
|||
}); |
|||
|
|||
function loadTestDataCipher(name: string): { |
|||
key: Uint8Array; |
|||
cipherText: Uint8Array; |
|||
clearText: Uint8Array; |
|||
} { |
|||
return { |
|||
key: fs.readFileSync(`testdata/${name}_key.bin`), |
|||
cipherText: fs.readFileSync(`testdata/${name}_raw.bin`), |
|||
clearText: fs.readFileSync(`testdata/${name}_target.bin`), |
|||
}; |
|||
} |
|||
|
|||
test('map cipher: real file', async () => { |
|||
const cases = ['mflac_map', 'mgg_map']; |
|||
for (const name of cases) { |
|||
const { key, clearText, cipherText } = loadTestDataCipher(name); |
|||
const c = new QmcMapCipher(key); |
|||
|
|||
c.decrypt(cipherText, 0); |
|||
|
|||
expect(cipherText).toStrictEqual(clearText); |
|||
} |
|||
}); |
|||
|
|||
test('rc4 cipher: real file', async () => { |
|||
const cases = ['mflac0_rc4', 'mflac_rc4']; |
|||
for (const name of cases) { |
|||
const { key, clearText, cipherText } = loadTestDataCipher(name); |
|||
const c = new QmcRC4Cipher(key); |
|||
|
|||
c.decrypt(cipherText, 0); |
|||
|
|||
expect(cipherText).toStrictEqual(clearText); |
|||
} |
|||
}); |
|||
|
|||
test('rc4 cipher: first segment', async () => { |
|||
const cases = ['mflac0_rc4', 'mflac_rc4']; |
|||
for (const name of cases) { |
|||
const { key, clearText, cipherText } = loadTestDataCipher(name); |
|||
const c = new QmcRC4Cipher(key); |
|||
|
|||
const buf = cipherText.slice(0, 128); |
|||
c.decrypt(buf, 0); |
|||
expect(buf).toStrictEqual(clearText.slice(0, 128)); |
|||
} |
|||
}); |
|||
|
|||
test('rc4 cipher: align block (128~5120)', async () => { |
|||
const cases = ['mflac0_rc4', 'mflac_rc4']; |
|||
for (const name of cases) { |
|||
const { key, clearText, cipherText } = loadTestDataCipher(name); |
|||
const c = new QmcRC4Cipher(key); |
|||
|
|||
const buf = cipherText.slice(128, 5120); |
|||
c.decrypt(buf, 128); |
|||
expect(buf).toStrictEqual(clearText.slice(128, 5120)); |
|||
} |
|||
}); |
|||
|
|||
test('rc4 cipher: simple block (5120~10240)', async () => { |
|||
const cases = ['mflac0_rc4', 'mflac_rc4']; |
|||
for (const name of cases) { |
|||
const { key, clearText, cipherText } = loadTestDataCipher(name); |
|||
const c = new QmcRC4Cipher(key); |
|||
|
|||
const buf = cipherText.slice(5120, 10240); |
|||
c.decrypt(buf, 5120); |
|||
expect(buf).toStrictEqual(clearText.slice(5120, 10240)); |
|||
} |
|||
}); |
@ -1,199 +0,0 @@ |
|||
export interface QmcStreamCipher { |
|||
decrypt(buf: Uint8Array, offset: number): void; |
|||
} |
|||
|
|||
export class QmcStaticCipher implements QmcStreamCipher { |
|||
//prettier-ignore
|
|||
private static readonly staticCipherBox: Uint8Array = new Uint8Array([ |
|||
0x77, 0x48, 0x32, 0x73, 0xDE, 0xF2, 0xC0, 0xC8, //0x00
|
|||
0x95, 0xEC, 0x30, 0xB2, 0x51, 0xC3, 0xE1, 0xA0, //0x08
|
|||
0x9E, 0xE6, 0x9D, 0xCF, 0xFA, 0x7F, 0x14, 0xD1, //0x10
|
|||
0xCE, 0xB8, 0xDC, 0xC3, 0x4A, 0x67, 0x93, 0xD6, //0x18
|
|||
0x28, 0xC2, 0x91, 0x70, 0xCA, 0x8D, 0xA2, 0xA4, //0x20
|
|||
0xF0, 0x08, 0x61, 0x90, 0x7E, 0x6F, 0xA2, 0xE0, //0x28
|
|||
0xEB, 0xAE, 0x3E, 0xB6, 0x67, 0xC7, 0x92, 0xF4, //0x30
|
|||
0x91, 0xB5, 0xF6, 0x6C, 0x5E, 0x84, 0x40, 0xF7, //0x38
|
|||
0xF3, 0x1B, 0x02, 0x7F, 0xD5, 0xAB, 0x41, 0x89, //0x40
|
|||
0x28, 0xF4, 0x25, 0xCC, 0x52, 0x11, 0xAD, 0x43, //0x48
|
|||
0x68, 0xA6, 0x41, 0x8B, 0x84, 0xB5, 0xFF, 0x2C, //0x50
|
|||
0x92, 0x4A, 0x26, 0xD8, 0x47, 0x6A, 0x7C, 0x95, //0x58
|
|||
0x61, 0xCC, 0xE6, 0xCB, 0xBB, 0x3F, 0x47, 0x58, //0x60
|
|||
0x89, 0x75, 0xC3, 0x75, 0xA1, 0xD9, 0xAF, 0xCC, //0x68
|
|||
0x08, 0x73, 0x17, 0xDC, 0xAA, 0x9A, 0xA2, 0x16, //0x70
|
|||
0x41, 0xD8, 0xA2, 0x06, 0xC6, 0x8B, 0xFC, 0x66, //0x78
|
|||
0x34, 0x9F, 0xCF, 0x18, 0x23, 0xA0, 0x0A, 0x74, //0x80
|
|||
0xE7, 0x2B, 0x27, 0x70, 0x92, 0xE9, 0xAF, 0x37, //0x88
|
|||
0xE6, 0x8C, 0xA7, 0xBC, 0x62, 0x65, 0x9C, 0xC2, //0x90
|
|||
0x08, 0xC9, 0x88, 0xB3, 0xF3, 0x43, 0xAC, 0x74, //0x98
|
|||
0x2C, 0x0F, 0xD4, 0xAF, 0xA1, 0xC3, 0x01, 0x64, //0xA0
|
|||
0x95, 0x4E, 0x48, 0x9F, 0xF4, 0x35, 0x78, 0x95, //0xA8
|
|||
0x7A, 0x39, 0xD6, 0x6A, 0xA0, 0x6D, 0x40, 0xE8, //0xB0
|
|||
0x4F, 0xA8, 0xEF, 0x11, 0x1D, 0xF3, 0x1B, 0x3F, //0xB8
|
|||
0x3F, 0x07, 0xDD, 0x6F, 0x5B, 0x19, 0x30, 0x19, //0xC0
|
|||
0xFB, 0xEF, 0x0E, 0x37, 0xF0, 0x0E, 0xCD, 0x16, //0xC8
|
|||
0x49, 0xFE, 0x53, 0x47, 0x13, 0x1A, 0xBD, 0xA4, //0xD0
|
|||
0xF1, 0x40, 0x19, 0x60, 0x0E, 0xED, 0x68, 0x09, //0xD8
|
|||
0x06, 0x5F, 0x4D, 0xCF, 0x3D, 0x1A, 0xFE, 0x20, //0xE0
|
|||
0x77, 0xE4, 0xD9, 0xDA, 0xF9, 0xA4, 0x2B, 0x76, //0xE8
|
|||
0x1C, 0x71, 0xDB, 0x00, 0xBC, 0xFD, 0x0C, 0x6C, //0xF0
|
|||
0xA5, 0x47, 0xF7, 0xF6, 0x00, 0x79, 0x4A, 0x11, //0xF8
|
|||
]) |
|||
|
|||
public getMask(offset: number) { |
|||
if (offset > 0x7fff) offset %= 0x7fff; |
|||
return QmcStaticCipher.staticCipherBox[(offset * offset + 27) & 0xff]; |
|||
} |
|||
|
|||
public decrypt(buf: Uint8Array, offset: number) { |
|||
for (let i = 0; i < buf.length; i++) { |
|||
buf[i] ^= this.getMask(offset + i); |
|||
} |
|||
} |
|||
} |
|||
|
|||
export class QmcMapCipher implements QmcStreamCipher { |
|||
key: Uint8Array; |
|||
n: number; |
|||
|
|||
constructor(key: Uint8Array) { |
|||
if (key.length == 0) throw Error('qmc/cipher_map: invalid key size'); |
|||
|
|||
this.key = key; |
|||
this.n = key.length; |
|||
} |
|||
|
|||
private static rotate(value: number, bits: number) { |
|||
let rotate = (bits + 4) % 8; |
|||
let left = value << rotate; |
|||
let right = value >> rotate; |
|||
return (left | right) & 0xff; |
|||
} |
|||
|
|||
decrypt(buf: Uint8Array, offset: number): void { |
|||
for (let i = 0; i < buf.length; i++) { |
|||
buf[i] ^= this.getMask(offset + i); |
|||
} |
|||
} |
|||
|
|||
private getMask(offset: number) { |
|||
if (offset > 0x7fff) offset %= 0x7fff; |
|||
|
|||
const idx = (offset * offset + 71214) % this.n; |
|||
return QmcMapCipher.rotate(this.key[idx], idx & 0x7); |
|||
} |
|||
} |
|||
|
|||
export class QmcRC4Cipher implements QmcStreamCipher { |
|||
private static readonly FIRST_SEGMENT_SIZE = 0x80; |
|||
private static readonly SEGMENT_SIZE = 5120; |
|||
|
|||
S: Uint8Array; |
|||
N: number; |
|||
key: Uint8Array; |
|||
hash: number; |
|||
|
|||
constructor(key: Uint8Array) { |
|||
if (key.length == 0) { |
|||
throw Error('invalid key size'); |
|||
} |
|||
|
|||
this.key = key; |
|||
this.N = key.length; |
|||
|
|||
// init seed box
|
|||
this.S = new Uint8Array(this.N); |
|||
for (let i = 0; i < this.N; ++i) { |
|||
this.S[i] = i & 0xff; |
|||
} |
|||
let j = 0; |
|||
for (let i = 0; i < this.N; ++i) { |
|||
j = (this.S[i] + j + this.key[i % this.N]) % this.N; |
|||
[this.S[i], this.S[j]] = [this.S[j], this.S[i]]; |
|||
} |
|||
|
|||
// init hash base
|
|||
this.hash = 1; |
|||
for (let i = 0; i < this.N; i++) { |
|||
let value = this.key[i]; |
|||
|
|||
// ignore if key char is '\x00'
|
|||
if (!value) continue; |
|||
|
|||
const next_hash = (this.hash * value) >>> 0; |
|||
if (next_hash == 0 || next_hash <= this.hash) break; |
|||
|
|||
this.hash = next_hash; |
|||
} |
|||
} |
|||
|
|||
decrypt(buf: Uint8Array, offset: number): void { |
|||
let toProcess = buf.length; |
|||
let processed = 0; |
|||
const postProcess = (len: number): boolean => { |
|||
toProcess -= len; |
|||
processed += len; |
|||
offset += len; |
|||
return toProcess == 0; |
|||
}; |
|||
|
|||
// Initial segment
|
|||
if (offset < QmcRC4Cipher.FIRST_SEGMENT_SIZE) { |
|||
const len_segment = Math.min(buf.length, QmcRC4Cipher.FIRST_SEGMENT_SIZE - offset); |
|||
this.encFirstSegment(buf.subarray(0, len_segment), offset); |
|||
if (postProcess(len_segment)) return; |
|||
} |
|||
|
|||
// align segment
|
|||
if (offset % QmcRC4Cipher.SEGMENT_SIZE != 0) { |
|||
const len_segment = Math.min(QmcRC4Cipher.SEGMENT_SIZE - (offset % QmcRC4Cipher.SEGMENT_SIZE), toProcess); |
|||
this.encASegment(buf.subarray(processed, processed + len_segment), offset); |
|||
if (postProcess(len_segment)) return; |
|||
} |
|||
|
|||
// Batch process segments
|
|||
while (toProcess > QmcRC4Cipher.SEGMENT_SIZE) { |
|||
this.encASegment(buf.subarray(processed, processed + QmcRC4Cipher.SEGMENT_SIZE), offset); |
|||
postProcess(QmcRC4Cipher.SEGMENT_SIZE); |
|||
} |
|||
|
|||
// Last segment (incomplete segment)
|
|||
if (toProcess > 0) { |
|||
this.encASegment(buf.subarray(processed), offset); |
|||
} |
|||
} |
|||
|
|||
private encFirstSegment(buf: Uint8Array, offset: number) { |
|||
for (let i = 0; i < buf.length; i++) { |
|||
buf[i] ^= this.key[this.getSegmentKey(offset + i)]; |
|||
} |
|||
} |
|||
|
|||
private encASegment(buf: Uint8Array, offset: number) { |
|||
// Initialise a new seed box
|
|||
const S = this.S.slice(0); |
|||
|
|||
// Calculate the number of bytes to skip.
|
|||
// The initial "key" derived from segment id, plus the current offset.
|
|||
const skipLen = |
|||
(offset % QmcRC4Cipher.SEGMENT_SIZE) + this.getSegmentKey(Math.floor(offset / QmcRC4Cipher.SEGMENT_SIZE)); |
|||
|
|||
// decrypt the block
|
|||
let j = 0; |
|||
let k = 0; |
|||
for (let i = -skipLen; i < buf.length; i++) { |
|||
j = (j + 1) % this.N; |
|||
k = (S[j] + k) % this.N; |
|||
[S[k], S[j]] = [S[j], S[k]]; |
|||
|
|||
if (i >= 0) { |
|||
buf[i] ^= S[(S[j] + S[k]) % this.N]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private getSegmentKey(id: number): number { |
|||
const seed = this.key[id % this.N]; |
|||
const idx = Math.floor((this.hash / ((id + 1) * seed)) * 100.0); |
|||
return idx % this.N; |
|||
} |
|||
} |
@ -1,26 +0,0 @@ |
|||
import { QmcDeriveKey, simpleMakeKey } from '@/decrypt/qmc_key'; |
|||
import fs from 'fs'; |
|||
|
|||
test('key dec: make simple key', () => { |
|||
expect(simpleMakeKey(106, 8)).toStrictEqual([0x69, 0x56, 0x46, 0x38, 0x2b, 0x20, 0x15, 0x0b]); |
|||
}); |
|||
|
|||
function loadTestDataKeyDecrypt(name: string): { |
|||
cipherText: Uint8Array; |
|||
clearText: Uint8Array; |
|||
} { |
|||
return { |
|||
cipherText: fs.readFileSync(`testdata/${name}_key_raw.bin`), |
|||
clearText: fs.readFileSync(`testdata/${name}_key.bin`), |
|||
}; |
|||
} |
|||
|
|||
test('key dec: real file', async () => { |
|||
const cases = ['mflac_map', 'mgg_map', 'mflac0_rc4', 'mflac_rc4']; |
|||
for (const name of cases) { |
|||
const { clearText, cipherText } = loadTestDataKeyDecrypt(name); |
|||
const buf = QmcDeriveKey(cipherText); |
|||
|
|||
expect(buf).toStrictEqual(clearText); |
|||
} |
|||
}); |
@ -1,127 +0,0 @@ |
|||
import { TeaCipher } from '@/utils/tea'; |
|||
|
|||
const SALT_LEN = 2; |
|||
const ZERO_LEN = 7; |
|||
|
|||
export function QmcDeriveKey(raw: Uint8Array): Uint8Array { |
|||
const textDec = new TextDecoder(); |
|||
let rawDec = Buffer.from(textDec.decode(raw), 'base64'); |
|||
let n = rawDec.length; |
|||
if (n < 16) { |
|||
throw Error('key length is too short'); |
|||
} |
|||
|
|||
rawDec = decryptV2Key(rawDec); |
|||
|
|||
const simpleKey = simpleMakeKey(106, 8); |
|||
let teaKey = new Uint8Array(16); |
|||
for (let i = 0; i < 8; i++) { |
|||
teaKey[i << 1] = simpleKey[i]; |
|||
teaKey[(i << 1) + 1] = rawDec[i]; |
|||
} |
|||
const sub = decryptTencentTea(rawDec.subarray(8), teaKey); |
|||
rawDec.set(sub, 8); |
|||
return rawDec.subarray(0, 8 + sub.length); |
|||
} |
|||
|
|||
// simpleMakeKey exported only for unit test
|
|||
export function simpleMakeKey(salt: number, length: number): number[] { |
|||
const keyBuf: number[] = []; |
|||
for (let i = 0; i < length; i++) { |
|||
const tmp = Math.tan(salt + i * 0.1); |
|||
keyBuf[i] = 0xff & (Math.abs(tmp) * 100.0); |
|||
} |
|||
return keyBuf; |
|||
} |
|||
|
|||
const mixKey1: Uint8Array = new Uint8Array([ 0x33, 0x38, 0x36, 0x5A, 0x4A, 0x59, 0x21, 0x40, 0x23, 0x2A, 0x24, 0x25, 0x5E, 0x26, 0x29, 0x28 ]) |
|||
const mixKey2: Uint8Array = new Uint8Array([ 0x2A, 0x2A, 0x23, 0x21, 0x28, 0x23, 0x24, 0x25, 0x26, 0x5E, 0x61, 0x31, 0x63, 0x5A, 0x2C, 0x54 ]) |
|||
|
|||
function decryptV2Key(key: Buffer): Buffer |
|||
{ |
|||
const textEnc = new TextDecoder(); |
|||
if (key.length < 18 || textEnc.decode(key.slice(0, 18)) !== 'QQMusic EncV2,Key:') { |
|||
return key; |
|||
} |
|||
|
|||
let out = decryptTencentTea(key.slice(18), mixKey1); |
|||
out = decryptTencentTea(out, mixKey2); |
|||
const textDec = new TextDecoder(); |
|||
const keyDec = Buffer.from(textDec.decode(out), 'base64'); |
|||
let n = keyDec.length; |
|||
if (n < 16) { |
|||
throw Error('EncV2 key decode failed'); |
|||
} |
|||
|
|||
return keyDec; |
|||
} |
|||
|
|||
function decryptTencentTea(inBuf: Uint8Array, key: Uint8Array): Uint8Array { |
|||
if (inBuf.length % 8 != 0) { |
|||
throw Error('inBuf size not a multiple of the block size'); |
|||
} |
|||
if (inBuf.length < 16) { |
|||
throw Error('inBuf size too small'); |
|||
} |
|||
|
|||
const blk = new TeaCipher(key, 32); |
|||
|
|||
const tmpBuf = new Uint8Array(8); |
|||
const tmpView = new DataView(tmpBuf.buffer); |
|||
|
|||
blk.decrypt(tmpView, new DataView(inBuf.buffer, inBuf.byteOffset, 8)); |
|||
|
|||
const nPadLen = tmpBuf[0] & 0x7; //只要最低三位
|
|||
/*密文格式:PadLen(1byte)+Padding(var,0-7byte)+Salt(2byte)+Body(var byte)+Zero(7byte)*/ |
|||
const outLen = inBuf.length - 1 /*PadLen*/ - nPadLen - SALT_LEN - ZERO_LEN; |
|||
const outBuf = new Uint8Array(outLen); |
|||
|
|||
let ivPrev = new Uint8Array(8); |
|||
let ivCur = inBuf.slice(0, 8); // init iv
|
|||
let inBufPos = 8; |
|||
|
|||
// 跳过 Padding Len 和 Padding
|
|||
let tmpIdx = 1 + nPadLen; |
|||
|
|||
// CBC IV 处理
|
|||
const cryptBlock = () => { |
|||
ivPrev = ivCur; |
|||
ivCur = inBuf.slice(inBufPos, inBufPos + 8); |
|||
for (let j = 0; j < 8; j++) { |
|||
tmpBuf[j] ^= ivCur[j]; |
|||
} |
|||
blk.decrypt(tmpView, tmpView); |
|||
inBufPos += 8; |
|||
tmpIdx = 0; |
|||
}; |
|||
|
|||
// 跳过 Salt
|
|||
for (let i = 1; i <= SALT_LEN; ) { |
|||
if (tmpIdx < 8) { |
|||
tmpIdx++; |
|||
i++; |
|||
} else { |
|||
cryptBlock(); |
|||
} |
|||
} |
|||
|
|||
// 还原明文
|
|||
let outBufPos = 0; |
|||
while (outBufPos < outLen) { |
|||
if (tmpIdx < 8) { |
|||
outBuf[outBufPos] = tmpBuf[tmpIdx] ^ ivPrev[tmpIdx]; |
|||
outBufPos++; |
|||
tmpIdx++; |
|||
} else { |
|||
cryptBlock(); |
|||
} |
|||
} |
|||
|
|||
// 校验Zero
|
|||
for (let i = 1; i <= ZERO_LEN; i++) { |
|||
if (tmpBuf[tmpIdx] != ivPrev[tmpIdx]) { |
|||
throw Error('zero check failed'); |
|||
} |
|||
} |
|||
return outBuf; |
|||
} |
@ -1,73 +0,0 @@ |
|||
// Copyright 2021 MengYX. All rights reserved.
|
|||
//
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
|||
// Use of this source code is governed by a BSD-style
|
|||
// license that can be found in https://go.dev/LICENSE.
|
|||
|
|||
import { TeaCipher } from '@/utils/tea'; |
|||
|
|||
test('key size', () => { |
|||
// prettier-ignore
|
|||
const testKey = new Uint8Array([ |
|||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, |
|||
0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, |
|||
0x00, |
|||
]) |
|||
expect(() => new TeaCipher(testKey.slice(0, 16))).not.toThrow(); |
|||
|
|||
expect(() => new TeaCipher(testKey)).toThrow(); |
|||
|
|||
expect(() => new TeaCipher(testKey.slice(0, 15))).toThrow(); |
|||
}); |
|||
|
|||
// prettier-ignore
|
|||
const teaTests = [ |
|||
// These were sourced from https://github.com/froydnj/ironclad/blob/master/testing/test-vectors/tea.testvec
|
|||
{ |
|||
rounds: TeaCipher.numRounds, |
|||
key: new Uint8Array([ |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
]), |
|||
plainText: new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), |
|||
cipherText: new Uint8Array([0x41, 0xea, 0x3a, 0x0a, 0x94, 0xba, 0xa9, 0x40]), |
|||
}, |
|||
{ |
|||
rounds: TeaCipher.numRounds, |
|||
key: new Uint8Array([ |
|||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
|||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
|||
]), |
|||
plainText: new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), |
|||
cipherText: new Uint8Array([0x31, 0x9b, 0xbe, 0xfb, 0x01, 0x6a, 0xbd, 0xb2]), |
|||
}, |
|||
{ |
|||
rounds: 16, |
|||
key: new Uint8Array([ |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
]), |
|||
plainText: new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), |
|||
cipherText: new Uint8Array([0xed, 0x28, 0x5d, 0xa1, 0x45, 0x5b, 0x33, 0xc1]), |
|||
}, |
|||
]; |
|||
|
|||
test('rounds', () => { |
|||
const tt = teaTests[0]; |
|||
expect(() => new TeaCipher(tt.key, tt.rounds - 1)).toThrow(); |
|||
}); |
|||
|
|||
test('encrypt & decrypt', () => { |
|||
for (const tt of teaTests) { |
|||
const c = new TeaCipher(tt.key, tt.rounds); |
|||
|
|||
const buf = new Uint8Array(8); |
|||
const bufView = new DataView(buf.buffer); |
|||
|
|||
c.encrypt(bufView, new DataView(tt.plainText.buffer)); |
|||
expect(buf).toStrictEqual(tt.cipherText); |
|||
|
|||
c.decrypt(bufView, new DataView(tt.cipherText.buffer)); |
|||
expect(buf).toStrictEqual(tt.plainText); |
|||
} |
|||
}); |
@ -1,80 +0,0 @@ |
|||
// Copyright 2021 MengYX. All rights reserved.
|
|||
//
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
|||
// Use of this source code is governed by a BSD-style
|
|||
// license that can be found in https://go.dev/LICENSE.
|
|||
|
|||
// TeaCipher is a typescript port to golang.org/x/crypto/tea
|
|||
|
|||
// Package tea implements the TEA algorithm, as defined in Needham and
|
|||
// Wheeler's 1994 technical report, “TEA, a Tiny Encryption Algorithm”. See
|
|||
// http://www.cix.co.uk/~klockstone/tea.pdf for details.
|
|||
//
|
|||
// TEA is a legacy cipher and its short block size makes it vulnerable to
|
|||
// birthday bound attacks (see https://sweet32.info). It should only be used
|
|||
// where compatibility with legacy systems, not security, is the goal.
|
|||
|
|||
export class TeaCipher { |
|||
// BlockSize is the size of a TEA block, in bytes.
|
|||
static readonly BlockSize = 8; |
|||
|
|||
// KeySize is the size of a TEA key, in bytes.
|
|||
static readonly KeySize = 16; |
|||
|
|||
// delta is the TEA key schedule constant.
|
|||
static readonly delta = 0x9e3779b9; |
|||
|
|||
// numRounds 64 is the standard number of rounds in TEA.
|
|||
static readonly numRounds = 64; |
|||
|
|||
k0: number; |
|||
k1: number; |
|||
k2: number; |
|||
k3: number; |
|||
rounds: number; |
|||
|
|||
constructor(key: Uint8Array, rounds: number = TeaCipher.numRounds) { |
|||
if (key.length != 16) { |
|||
throw Error('incorrect key size'); |
|||
} |
|||
if ((rounds & 1) != 0) { |
|||
throw Error('odd number of rounds specified'); |
|||
} |
|||
|
|||
const k = new DataView(key.buffer); |
|||
this.k0 = k.getUint32(0, false); |
|||
this.k1 = k.getUint32(4, false); |
|||
this.k2 = k.getUint32(8, false); |
|||
this.k3 = k.getUint32(12, false); |
|||
this.rounds = rounds; |
|||
} |
|||
|
|||
encrypt(dst: DataView, src: DataView) { |
|||
let v0 = src.getUint32(0, false); |
|||
let v1 = src.getUint32(4, false); |
|||
|
|||
let sum = 0; |
|||
for (let i = 0; i < this.rounds / 2; i++) { |
|||
sum = sum + TeaCipher.delta; |
|||
v0 += ((v1 << 4) + this.k0) ^ (v1 + sum) ^ ((v1 >>> 5) + this.k1); |
|||
v1 += ((v0 << 4) + this.k2) ^ (v0 + sum) ^ ((v0 >>> 5) + this.k3); |
|||
} |
|||
|
|||
dst.setUint32(0, v0, false); |
|||
dst.setUint32(4, v1, false); |
|||
} |
|||
|
|||
decrypt(dst: DataView, src: DataView) { |
|||
let v0 = src.getUint32(0, false); |
|||
let v1 = src.getUint32(4, false); |
|||
|
|||
let sum = (TeaCipher.delta * this.rounds) / 2; |
|||
for (let i = 0; i < this.rounds / 2; i++) { |
|||
v1 -= ((v0 << 4) + this.k2) ^ (v0 + sum) ^ ((v0 >>> 5) + this.k3); |
|||
v0 -= ((v1 << 4) + this.k0) ^ (v1 + sum) ^ ((v1 >>> 5) + this.k1); |
|||
sum -= TeaCipher.delta; |
|||
} |
|||
dst.setUint32(0, v0, false); |
|||
dst.setUint32(4, v1, false); |
|||
} |
|||
} |
Loading…
Reference in new issue