Difference between revisions of "MUX"

From Mania Tech Wiki
Jump to navigation Jump to search
m
(Add link to a library for dealing with mux files)
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
'''.mux files''' are encrypted .ogg files and are typically used for custom track music. They use a simple, custom stream cipher based on a 16-byte key: a very long key is generated from the 16-byte key, and then every byte of the input file is xor'd with the corresponding byte in the long key.
+
'''.mux files''' are encrypted .ogg files and are typically used for custom track music. They use a simple, custom stream cipher based on a 16-byte key (an MD5 hash): a very long key (the key stream) is generated from the 16-byte key, and then every byte of the input file is xor'd with the corresponding byte in the key stream. Details can be found at [https://en.wikipedia.org/wiki/Stream_cipher Wikipedia].
  
 
== File layout ==
 
== File layout ==
Line 8: Line 8:
 
  byte data[]
 
  byte data[]
  
== Decryption ==
+
== Decryption and Encryption ==
  
 
First, the 16-byte decryption key is determined as follows:
 
First, the 16-byte decryption key is determined as follows:
Line 14: Line 14:
 
  key = md5(pack(keySalt) + "Hello,hack3r!")    // Hello, d3veloper :)
 
  key = md5(pack(keySalt) + "Hello,hack3r!")    // Hello, d3veloper :)
  
where pack() returns the bytes of its argument in little endian order, and the string is encoded as ASCII.
+
where pack() returns the bytes of its argument in little endian order, and the string (''Hello,hack3r!'') is encoded as ASCII. Note that on little endian systems you won't need to implement pack() as the bytes will already be in correct order.
Then, the data is decrypted as follows:
 
  
  void Decrypt(byte[] data)
+
=== Pseudo Code ===
  {
+
// keySalt and data variables are as specified under File Layout
 +
 +
  void mux2ogg() {
 +
    byte[] key = generateKey(keySalt);
 +
    dataXORkey(key, data);
 +
    // decrypted data is now in data (pass by reference)
 +
}
 +
 +
  void ogg2mux() {
 +
    byte[] keySalt = ...; // generate random 4 bytes
 +
    byte[] key = generateKey(keySalt);
 +
    dataXORkey(key, data); // data is the full ogg content here
 +
    // mux file can now be created by building the mux header as described in File Layout
 +
}
 +
 +
byte[] generateKey(byte[] keySalt) {
 +
    return = md5(pack(keySalt) + "Hello,hack3r!");
 +
}
 +
 +
void dataXORkey(byte[] key, byte[] data) {
 
     for (int i = 0; i < data.Length; i++)
 
     for (int i = 0; i < data.Length; i++)
 
     {
 
     {
         data[i] ^= GetKeyStreamByte(i);
+
         data[i] ^= GetKeyStreamByte(key, i);
 
     }
 
     }
 
  }
 
  }
 
   
 
   
  byte GetKeyStreamByte(int pos)
+
  byte GetKeyStreamByte(byte[] key, int pos) {
{
 
 
     return rol(key[pos % 16], (pos / 17) % 8);
 
     return rol(key[pos % 16], (pos / 17) % 8);
 
  }
 
  }
 
   
 
   
  byte rol(byte input, int amount)
+
  byte rol(byte input, int amount) {
{
 
 
     return (byte)((input << amount) | (input >> (8 - amount)));
 
     return (byte)((input << amount) | (input >> (8 - amount)));
 
  }
 
  }
 +
 +
=== PHP Implementation ===
 +
 +
<?php
 +
/**
 +
  * Left rotate the bits of a 1 byte integer
 +
  *
 +
  * @param int $input integer to rotate
 +
  * @param int $amount how many steps to rotate
 +
  * @return int the rotated integer
 +
  */
 +
function rol(int $input, int $amount): int {
 +
    return bytify(($input << $amount) | ($input >> (8 - $amount)));
 +
}
 +
 +
/**
 +
  * Get the byte of the key in the key stream (very long key).
 +
  *
 +
  * @param int $pos Byte position in the data
 +
  * @return string Corresponding byte from key stream
 +
  */
 +
function GetKeyStreamByte(string $key, int $pos): string {
 +
    // Get the corresponding byte from the key and transform it into an integer.
 +
    // Wrap around on the key, which is only 16 bytes long -> do modulo.
 +
    // This would also work, but it's slower:
 +
    //      unpack('C', $key[$pos % 16])[1],
 +
    $input = ord($key[$pos % 16]);
 +
 +
    // What does this do and why is it necessary? legacy code by arc_
 +
    $amount = ($pos / 17) % 8;
 +
 +
    // alternatively, one could use pack('C', ...) but it' slower
 +
    // rol(...): why is this necessary? legacy code by arc_
 +
    $rotated = rol($input, $amount);
 +
 +
    // Convert integer back to char/byte.
 +
    // Could use pack('C', $rotated) here, but it's slower.
 +
    return chr($rotated);
 +
}
 +
 +
/**
 +
  * De- or encrypt the payload -> do XOR with the key.
 +
  *
 +
  * @param string $key 16 byte key as returned by md5()
 +
  * @param string &$data Input data, overwritten with output (pass by reference!)
 +
  * @return void
 +
  */
 +
function keyXORdata(string $key, string &$data): void {
 +
    $length = strlen($data);
 +
 +
    for ($i = 0; $i < $length; $i++) {
 +
        // XOR every byte of the input with the corresponding byte of the key
 +
        $data[$i] = $data[$i] ^ GetKeyStreamByte($key, $i);
 +
    }
 +
}
 +
 +
/**
 +
  * Make any integer fit into 1 byte by chopping off the bits above bit 7.
 +
  * This is a workaround because we don't have a "byte" type in PHP where
 +
  * every bit exceeding 7 bit would be dropped automatically during left shift.
 +
  *
 +
  * @param int $nobyte Int that we want to be cut down to 8 bit
 +
  * @return int
 +
  */
 +
function bytify(int $nobyte): int {
 +
    return 0x000000FF & $nobyte;
 +
}
 +
 +
/**
 +
  * Convert a .mux version 01 file to .ogg
 +
  *
 +
  * @param type $inputFile .mux file
 +
  * @param type $outputFile .ogg file
 +
  */
 +
function mux2ogg($inputFile, $outputFile) {
 +
    // open file and read its parts
 +
    $handle = fopen($inputFile, "rb");
 +
    $magicNumber = fread($handle, 9); // always "NadeoFile"
 +
    $version = fread($handle, 1); // should be 0x01
 +
    $keySalt = fread($handle, 4);
 +
    $data = fread($handle, 33554432); // read max 32MB (less if end of file comes first)
 +
    // Generate key. Note that we need the binary representation.
 +
    // By default, md5() returns the hexadecimal representation.
 +
    $key = md5($keySalt . "Hello,hack3r!", true);
 +
 +
    // decrypt mux to ogg
 +
    keyXORdata($key, $data);
 +
 +
    file_put_contents($outputFile, $data);
 +
 +
    fclose($handle);
 +
}
 +
 +
/**
 +
  * Convert a .ogg to a .mux file
 +
  *
 +
  * @param type $inputFile .ogg file
 +
  * @param type $outputFile .mux file
 +
  */
 +
function ogg2mux($inputFile, $outputFile) {
 +
    $magicNumber = "NadeoFile";
 +
    $version = "\x01";
 +
    $keySalt = "test"; // in reality, generate some random 4 bytes here...
 +
    $data = file_get_contents($inputFile);
 +
 +
    // Generate key. Note that we need the binary representation.
 +
    // By default, md5() returns the hexadecimal representation.
 +
    $key = md5($keySalt . "Hello,hack3r!", true);
 +
 +
    // encrypt ogg to mux
 +
    keyXORdata($key, $data);
 +
 +
    // now concatenate the header of the mux together with the encrypted ogg
 +
    $mux = $magicNumber . $version . $keySalt . $data;
 +
 +
    file_put_contents($outputFile, $mux);
 +
}
 +
 +
== Tools ==
 +
* [http://web.archive.org/web/20101229224319/http://forum.mania-creative.com/thread-2423.html TMDemux]: Tool by arc_ for .mux to .ogg. The pseudo code on this page is derived from his tool.
 +
** [http://www.mediafire.com/file/3k6pccvmlc1nbg5/TMDemux.zip/file Executable]
 +
** [http://www.mediafire.com/?mkqssfcuu63m686 Source code]
  
 
[[Category:File formats]]
 
[[Category:File formats]]
 +
 +
== Libraries ==
 +
*PHP
 +
** [https://packagist.org/packages/askuri/muxconvert MuxConverter]

Latest revision as of 15:12, 12 July 2020

.mux files are encrypted .ogg files and are typically used for custom track music. They use a simple, custom stream cipher based on a 16-byte key (an MD5 hash): a very long key (the key stream) is generated from the 16-byte key, and then every byte of the input file is xor'd with the corresponding byte in the key stream. Details can be found at Wikipedia.

File layout

byte magic[9] = "NadeoFile"
byte version = 1
int32 keySalt
byte data[]

Decryption and Encryption

First, the 16-byte decryption key is determined as follows:

key = md5(pack(keySalt) + "Hello,hack3r!")    // Hello, d3veloper :)

where pack() returns the bytes of its argument in little endian order, and the string (Hello,hack3r!) is encoded as ASCII. Note that on little endian systems you won't need to implement pack() as the bytes will already be in correct order.

Pseudo Code

// keySalt and data variables are as specified under File Layout

void mux2ogg() {
    byte[] key = generateKey(keySalt);
    dataXORkey(key, data);
    // decrypted data is now in data (pass by reference)
}

void ogg2mux() {
    byte[] keySalt = ...; // generate random 4 bytes
    byte[] key = generateKey(keySalt);
    dataXORkey(key, data); // data is the full ogg content here
    // mux file can now be created by building the mux header as described in File Layout
}

byte[] generateKey(byte[] keySalt) {
    return = md5(pack(keySalt) + "Hello,hack3r!");
}

void dataXORkey(byte[] key, byte[] data) {
    for (int i = 0; i < data.Length; i++)
    {
        data[i] ^= GetKeyStreamByte(key, i);
    }
}

byte GetKeyStreamByte(byte[] key, int pos) {
    return rol(key[pos % 16], (pos / 17) % 8);
}

byte rol(byte input, int amount) {
    return (byte)((input << amount) | (input >> (8 - amount)));
}

PHP Implementation

<?php
/**
 * Left rotate the bits of a 1 byte integer
 * 
 * @param int $input integer to rotate
 * @param int $amount how many steps to rotate
 * @return int the rotated integer
 */
function rol(int $input, int $amount): int {
    return bytify(($input << $amount) | ($input >> (8 - $amount)));
}

/**
 * Get the byte of the key in the key stream (very long key).
 * 
 * @param int $pos Byte position in the data
 * @return string Corresponding byte from key stream
 */
function GetKeyStreamByte(string $key, int $pos): string {
    // Get the corresponding byte from the key and transform it into an integer.
    // Wrap around on the key, which is only 16 bytes long -> do modulo.
    // This would also work, but it's slower:
    //      unpack('C', $key[$pos % 16])[1],
    $input = ord($key[$pos % 16]);

    // What does this do and why is it necessary? legacy code by arc_
    $amount = ($pos / 17) % 8;

    // alternatively, one could use pack('C', ...) but it' slower
    // rol(...): why is this necessary? legacy code by arc_
    $rotated = rol($input, $amount);

    // Convert integer back to char/byte.
    // Could use pack('C', $rotated) here, but it's slower.
    return chr($rotated);
}

/**
 * De- or encrypt the payload -> do XOR with the key.
 * 
 * @param string $key 16 byte key as returned by md5()
 * @param string &$data Input data, overwritten with output (pass by reference!)
 * @return void
 */
function keyXORdata(string $key, string &$data): void {
    $length = strlen($data);

    for ($i = 0; $i < $length; $i++) {
        // XOR every byte of the input with the corresponding byte of the key
        $data[$i] = $data[$i] ^ GetKeyStreamByte($key, $i);
    }
}

/**
 * Make any integer fit into 1 byte by chopping off the bits above bit 7.
 * This is a workaround because we don't have a "byte" type in PHP where
 * every bit exceeding 7 bit would be dropped automatically during left shift.
 * 
 * @param int $nobyte Int that we want to be cut down to 8 bit
 * @return int
 */
function bytify(int $nobyte): int {
    return 0x000000FF & $nobyte;
}

/**
 * Convert a .mux version 01 file to .ogg
 * 
 * @param type $inputFile .mux file
 * @param type $outputFile .ogg file
 */
function mux2ogg($inputFile, $outputFile) {
    // open file and read its parts
    $handle = fopen($inputFile, "rb");
    $magicNumber = fread($handle, 9); // always "NadeoFile"
    $version = fread($handle, 1); // should be 0x01
    $keySalt = fread($handle, 4);
    $data = fread($handle, 33554432); // read max 32MB (less if end of file comes first)
    // Generate key. Note that we need the binary representation.
    // By default, md5() returns the hexadecimal representation.
    $key = md5($keySalt . "Hello,hack3r!", true);

    // decrypt mux to ogg
    keyXORdata($key, $data);

    file_put_contents($outputFile, $data);

    fclose($handle);
}

/**
 * Convert a .ogg to a .mux file
 * 
 * @param type $inputFile .ogg file
 * @param type $outputFile .mux file
 */
function ogg2mux($inputFile, $outputFile) {
    $magicNumber = "NadeoFile";
    $version = "\x01";
    $keySalt = "test"; // in reality, generate some random 4 bytes here...
    $data = file_get_contents($inputFile);

    // Generate key. Note that we need the binary representation.
    // By default, md5() returns the hexadecimal representation.
    $key = md5($keySalt . "Hello,hack3r!", true);

    // encrypt ogg to mux
    keyXORdata($key, $data);

    // now concatenate the header of the mux together with the encrypted ogg
    $mux = $magicNumber . $version . $keySalt . $data;

    file_put_contents($outputFile, $mux);
}

Tools

Libraries