Added savedata encryption (untested) and KIRK decryption/encryption

(untested too xD), also added game 16-byte gamekey.
This commit is contained in:
codestation 2011-03-01 22:20:07 +00:00
parent 83a07c8296
commit 2ef55f18a9
12 changed files with 2397 additions and 16 deletions

View file

@ -2,5 +2,6 @@
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="bcprov-jdk16-146.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

9
README
View file

@ -1,4 +1,4 @@
MH string tables unpacker/rebuilder/encrypter/decrypter.
MH utilities - codestation
encoder/decoder number meaning to use in binary files:
@ -49,4 +49,9 @@ To pack images into a TMH container
java -jar mhtrans.jar --rebuild /home/user/unpacked_countainer_dir 5
To unpack a .pak file
java -jar mhtrans.jar --extract file.pak 6
java -jar mhtrans.jar --extract file.pak 6
The KIRK related functions are untested and requires the Bouncy Castle Crypto API
that can be downloaded from http://www.bouncycastle.org/download/bcprov-jdk16-146.jar
The PSP Crypto engine and AES wrapper comes from the Jpcsp project.

View file

@ -1,4 +1,5 @@
Manifest-Version: 1.0
Sealed: true
Main-Class: base.Mhtrans
Class-Path: bcprov-jdk16-146.jar

View file

@ -25,6 +25,7 @@ import java.io.IOException;
import crypt.Decrypter;
import crypt.Encrypter;
import crypt.KirkCypher;
import crypt.QuestCypher;
import crypt.SavedataCypher;
import dec.ExtractPluginA;
@ -113,7 +114,7 @@ public class Mhtrans {
}
public static void main(String[] args) {
System.out.println("mhtrans v2.0 - MHP2G/MHFU/MHP3 xxxx.bin language table extractor/rebuilder");
System.out.println("mhtrans v2.0 - MHP2G/MHFU/MHP3 utils");
System.out.println();
if (args.length < 2) {
System.err.println("Usage: java -jar mhtrans.jar --extract <path to xxxx.bin> <decoder number>");
@ -127,9 +128,11 @@ public class Mhtrans {
System.err.println(" java -jar mhtrans.jar --create-patch <xxxx.bin.enc> [ ... <xxxx.bin.enc>] <output_file>");
System.err.println(" java -jar mhtrans.jar --decrypt-quest <mxxxxx.mib>");
System.err.println(" java -jar mhtrans.jar --encrypt-quest <mxxxxx.mib>");
//System.err.println(" java -jar mhtrans.jar --update-sha1 <mxxxxx.mib>");
System.err.println(" java -jar mhtrans.jar --update-sha1 <mxxxxx.mib>");
System.err.println(" java -jar mhtrans.jar --decrypt-save <xxxxx.bin>");
//System.err.println(" java -jar mhtrans.jar --encrypt-save <xxxxx.bin>");
System.err.println(" java -jar mhtrans.jar --encrypt-save <xxxxx.bin>");
System.err.println(" java -jar mhtrans.jar --decrypt-kirk <xxxxx.bin>");
System.err.println(" java -jar mhtrans.jar --encrypt-kirk <xxxxx.bin>");
System.exit(1);
} else {
if (args[0].equals("--extract")) {
@ -185,12 +188,16 @@ public class Mhtrans {
new QuestCypher().encrypt(args[1]);
} else if(args[0].equals("--decrypt-quest")) {
new QuestCypher().decrypt(args[1]);
//} else if(args[0].equals("--update-sha1")) {
// new QuestCypher().update_sha1(args[1]);
//} else if(args[0].equals("--encrypt-save")) {
// new SavedataCypher().encrypt(args[1]);
} else if(args[0].equals("--update-sha1")) {
new QuestCypher().update_sha1(args[1]);
} else if(args[0].equals("--encrypt-save")) {
new SavedataCypher().encrypt(args[1]);
} else if(args[0].equals("--decrypt-save")) {
new SavedataCypher().decrypt(args[1]);
} else if(args[0].equals("--encrypt-kirk")) {
new KirkCypher().encrypt(args[1]);
} else if(args[0].equals("--decrypt-kirk")) {
new KirkCypher().decrypt(args[1]);
} else {
System.err.println("Unknown parameter: " + args[0]);
System.exit(1);

64
src/crypt/KirkCypher.java Normal file
View file

@ -0,0 +1,64 @@
/* MHTrans - KIRK savedata decrypter/encrypter
Copyright (C) 2011 Codestation
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package crypt;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import jpcsp.crypto.CryptoEngine;
import keys.GameKeys;
public class KirkCypher implements GameKeys {
public void decrypt(String file) {
try {
RandomAccessFile fd = new RandomAccessFile(file, "rw");
byte byte_bt[] = new byte[(int)fd.length()];
fd.read(byte_bt);
fd.seek(0);
System.out.print("Decrypting savedata (KIRK engine)");
byte out[] = new CryptoEngine().DecryptSavedata(byte_bt, byte_bt.length, gamekey, 1);
fd.write(out);
fd.close();
System.out.println("Finished");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void encrypt(String file) {
try {
RandomAccessFile fd = new RandomAccessFile(file, "rw");
byte byte_bt[] = new byte[(int)fd.length()];
fd.read(byte_bt);
fd.seek(0);
System.out.println("Encrypting savedata (KIRK engine)");
byte out[] = new CryptoEngine().EncryptSavedata(byte_bt, byte_bt.length, gamekey, 0);
fd.write(out);
fd.close();
System.out.println("Finished");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

View file

@ -47,12 +47,14 @@ public class QuestCypher implements QuestKeys {
ShortBuffer sb = bt.asShortBuffer();
short short_bt[] = new short[byte_bt.length/2];
sb.get(short_bt);
System.out.println("Decrypting quest file");
decrypt_quest(short_bt);
sb.rewind();
sb.put(short_bt);
FileOutputStream out = new FileOutputStream(filein + ".dec");
out.write(byte_bt);
out.close();
System.out.println("finished.");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
@ -68,18 +70,21 @@ public class QuestCypher implements QuestKeys {
byte byte_bt[] = new byte[(int)fd.length()];
in.read(byte_bt);
in.close();
System.out.println("Updating sha1 hash");
update_sha1(byte_bt);
ByteBuffer bt = ByteBuffer.wrap(byte_bt);
bt.order(ByteOrder.LITTLE_ENDIAN);
ShortBuffer sb = bt.asShortBuffer();
short short_bt[] = new short[byte_bt.length/2];
sb.get(short_bt);
System.out.println("Encrypting quest file");
encrypt_quest(short_bt);
sb.rewind();
sb.put(short_bt);
FileOutputStream out = new FileOutputStream(filein + ".enc");
out.write(byte_bt);
out.close();
System.out.println("Finished");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
@ -122,10 +127,11 @@ public class QuestCypher implements QuestKeys {
byte byte_bt[] = new byte[(int)fd.length()];
fd.read(byte_bt);
fd.seek(0);
System.out.println("Updating sha1 hash");
update_sha1(byte_bt);
fd.write(byte_bt);
fd.close();
System.out.println("Finished");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
@ -135,14 +141,14 @@ public class QuestCypher implements QuestKeys {
private void update_sha1(byte buf[]) {
int len = ((buf[8+3] << 24) & 0xFFFFFFFF) + ((buf[8+2] << 16) & 0xFFFFFF) + ((buf[8+1] << 8) & 0xFFFF) + ((buf[8+0] << 0) & 0xFF);
len += 0x10;
len += quest_sha1_key.length();
byte buffer[] = new byte[len];
System.arraycopy(buf, 0x20, buffer, 0, len-0x10);
System.arraycopy(quest_sha1_key.getBytes(), 0, buffer, len-0x10, 0x10);
System.arraycopy(buf, 0x20, buffer, 0, len-quest_sha1_key.length());
System.arraycopy(quest_sha1_key.getBytes(), 0, buffer, len-quest_sha1_key.length(), quest_sha1_key.length());
try {
MessageDigest md = MessageDigest.getInstance("sha-1");
byte digest[] = md.digest(buffer);
System.arraycopy(digest, 0, buf, 12, 0x10);
System.arraycopy(digest, 0, buf, 12, digest.length);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}

View file

@ -20,6 +20,8 @@ package crypt;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import keys.SavedataKeys;
@ -50,21 +52,22 @@ public class SavedataCypher extends DecryptUtils implements SavedataKeys {
return mod_b;
}
public boolean decrypt(String file) {
public void decrypt(String file) {
try {
RandomAccessFile fd = new RandomAccessFile(file, "rw");
byte byte_bt[] = new byte[(int)fd.length()];
fd.read(byte_bt);
fd.seek(0);
System.out.println("Decrypting savedata");
decrypt_buffer(byte_bt);
fd.write(byte_bt);
fd.close();
System.out.println("Finished");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
private void decrypt_buffer(byte buffer[]) {
@ -84,4 +87,58 @@ public class SavedataCypher extends DecryptUtils implements SavedataKeys {
set_table_data(buffer, i);
}
}
public void encrypt(String file) {
try {
RandomAccessFile fd = new RandomAccessFile(file, "rw");
byte byte_bt[] = new byte[(int)fd.length()];
fd.read(byte_bt);
fd.seek(0);
System.out.println("Updating sha1 hash");
update_sha1(byte_bt);
System.out.println("Encrypting savedata");
encrypt_buffer(byte_bt);
fd.write(byte_bt);
fd.close();
System.out.println("Finished");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void update_sha1(byte buf[]) {
int len = buf.length - 24;
len += savedata_sha1_key.length();
byte buffer[] = new byte[len];
System.arraycopy(buf, 0, buffer, 0, len-savedata_sha1_key.length());
System.arraycopy(savedata_sha1_key.getBytes(), 0, buffer, len-savedata_sha1_key.length(), savedata_sha1_key.length());
try {
MessageDigest md = MessageDigest.getInstance("sha-1");
byte digest[] = md.digest(buffer);
System.arraycopy(digest, 0, buf, buf.length - 24, 0x10);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
private void encrypt_buffer(byte buffer[]) {
byte encrypt_table[] = new byte[256];
for (int i = 0; i < 256; i++) {
encrypt_table[decrypt_table[i] & 0xFF] = (byte) i;
}
int len = buffer.length - 4;
byte seed[] = new byte[4];
System.arraycopy(buffer, len, seed, 0, 4);
long alpha = get_table_value(seed, 0);
initSeed(alpha);
for (int i = 0; i < len; i += 4) {
long gamma = get_table_value(buffer, i);
long beta = getBeta();
alpha = beta ^ gamma;
set_table_value(buffer, i, alpha);
get_table_value(encrypt_table, buffer);
}
}
}

View file

@ -0,0 +1,250 @@
/*
This file is part of jpcsp.
Jpcsp is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jpcsp is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.crypto;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.security.Key;
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class AES128 {
private static byte[] const_Zero = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
private static byte[] const_Rb = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0x87};
private byte[] contentKey;
private ByteArrayOutputStream barros;
public AES128() {
Security.addProvider(new BouncyCastleProvider());
}
// Private encrypting method for CMAC (IV == 0).
private static byte[] encrypt(byte[] in, byte[] encKey) {
Key keySpec = new SecretKeySpec(encKey, "AES");
byte[] iv = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
IvParameterSpec ivec = new IvParameterSpec(iv);
try {
Cipher c = Cipher.getInstance("AES/CBC/NoPadding", "BC");
c.init(Cipher.ENCRYPT_MODE, keySpec, ivec);
ByteArrayInputStream inStream = new ByteArrayInputStream(in);
CipherInputStream cIn = new CipherInputStream(inStream, c);
DataInputStream dIn = new DataInputStream(cIn);
byte[] bytes = new byte[in.length];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) dIn.read();
}
return bytes;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
// Public encrypting/decrypting methods (for CryptoEngine calls).
public byte[] encryptCBC(byte[] in, byte[] encKey, byte[] iv) {
Key keySpec = new SecretKeySpec(encKey, "AES");
IvParameterSpec ivec = new IvParameterSpec(iv);
try {
Cipher c = Cipher.getInstance("AES/CBC/NoPadding", "BC");
c.init(Cipher.ENCRYPT_MODE, keySpec, ivec);
ByteArrayInputStream inStream = new ByteArrayInputStream(in);
CipherInputStream cIn = new CipherInputStream(inStream, c);
DataInputStream dIn = new DataInputStream(cIn);
byte[] bytes = new byte[in.length];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) dIn.read();
}
return bytes;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public byte[] decryptCBC(byte[] in, byte[] decKey, byte[] iv) {
Key keySpec = new SecretKeySpec(decKey, "AES");
IvParameterSpec ivec = new IvParameterSpec(iv);
try {
Cipher c = Cipher.getInstance("AES/CBC/NoPadding", "BC");
c.init(Cipher.DECRYPT_MODE, keySpec, ivec);
ByteArrayInputStream inStream = new ByteArrayInputStream(in);
CipherInputStream cIn = new CipherInputStream(inStream, c);
DataInputStream dIn = new DataInputStream(cIn);
byte[] bytes = new byte[in.length];
for (int i = 0; i < in.length; i++) {
bytes[i] = (byte) dIn.read();
}
return bytes;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public void doInitCMAC(byte[] contentKey) {
this.contentKey = contentKey;
barros = new ByteArrayOutputStream();
}
public void doUpdateCMAC(byte[] input, int offset, int len) {
barros.write(input, offset, len);
}
public void doUpdateCMAC(byte[] input) {
barros.write(input, 0, input.length);
}
public byte[] doFinalCMAC() {
Object[] keys = generateSubKey(contentKey);
byte[] K1 = (byte[]) keys[0];
byte[] K2 = (byte[]) keys[1];
byte[] input = barros.toByteArray();
int numberOfRounds = (input.length + 15) / 16;
boolean lastBlockComplete;
if (numberOfRounds == 0) {
numberOfRounds = 1;
lastBlockComplete = false;
} else {
if (input.length % 16 == 0) {
lastBlockComplete = true;
} else {
lastBlockComplete = false;
}
}
byte[] M_last;
int srcPos = 16 * (numberOfRounds - 1);
if (lastBlockComplete) {
byte[] partInput = new byte[16];
System.arraycopy(input, srcPos, partInput, 0, 16);
M_last = xor128(partInput, K1);
} else {
byte[] partInput = new byte[input.length % 16];
System.arraycopy(input, srcPos, partInput, 0, input.length % 16);
byte[] padded = doPaddingCMAC(partInput);
M_last = xor128(padded, K2);
}
byte[] X = const_Zero.clone();
byte[] partInput = new byte[16];
byte[] Y;
for (int i = 0; i < numberOfRounds - 1; i++) {
srcPos = 16 * i;
System.arraycopy(input, srcPos, partInput, 0, 16);
Y = xor128(partInput, X); /* Y := Mi (+) X */
X = encrypt(Y, contentKey);
}
Y = xor128(X, M_last);
X = encrypt(Y, contentKey);
return X;
}
public boolean doVerifyCMAC(byte[] verificationCMAC) {
byte[] cmac = doFinalCMAC();
if (verificationCMAC == null || verificationCMAC.length != cmac.length) {
return false;
}
for (int i = 0; i < cmac.length; i++) {
if (cmac[i] != verificationCMAC[i]) {
return false;
}
}
return true;
}
private byte[] doPaddingCMAC(byte[] input) {
byte[] padded = new byte[16];
for (int j = 0; j < 16; j++) {
if (j < input.length) {
padded[j] = input[j];
} else if (j == input.length) {
padded[j] = (byte) 0x80;
} else {
padded[j] = (byte) 0x00;
}
}
return padded;
}
public static Object[] generateSubKey(byte[] key) {
byte[] L = encrypt(const_Zero, key);
byte[] K1 = null;
if ((L[0] & 0x80) == 0) { /* If MSB(L) = 0, then K1 = L << 1 */
K1 = doLeftShiftOneBit(L);
} else { /* Else K1 = ( L << 1 ) (+) Rb */
byte[] tmp = doLeftShiftOneBit(L);
K1 = xor128(tmp, const_Rb);
}
byte[] K2 = null;
if ((K1[0] & 0x80) == 0) {
K2 = doLeftShiftOneBit(K1);
} else {
byte[] tmp = doLeftShiftOneBit(K1);
K2 = xor128(tmp, const_Rb);
}
Object[] result = new Object[2];
result[0] = K1;
result[1] = K2;
return result;
}
private static byte[] xor128(byte[] input1, byte[] input2) {
byte[] output = new byte[input1.length];
for (int i = 0; i < input1.length; i++) {
output[i] = (byte) (((int) input1[i] ^ (int) input2[i]) & 0xFF);
}
return output;
}
private static byte[] doLeftShiftOneBit(byte[] input) {
byte[] output = new byte[input.length];
byte overflow = 0;
for (int i = (input.length - 1); i >= 0; i--) {
output[i] = (byte) ((int) input[i] << 1 & 0xFF);
output[i] |= overflow;
overflow = ((input[i] & 0x80) != 0) ? (byte) 1 : (byte) 0;
}
return output;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,39 @@
/*
This file is part of jpcsp.
Jpcsp is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jpcsp is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.crypto;
import java.security.MessageDigest;
public class SHA1 {
public SHA1() {
}
public byte[] doSHA1(byte[] bytes, int lenght) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] sha1Hash = new byte[40];
md.update(bytes, 0, lenght);
sha1Hash = md.digest();
return sha1Hash;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

27
src/keys/GameKeys.java Normal file
View file

@ -0,0 +1,27 @@
/* MHTrans - MHP3 game keys
Copyright (C) 2011 Codestation
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package keys;
public interface GameKeys {
final byte gamekey[] = {
(byte)0xE3, (byte)0x05, (byte)0xCE, (byte)0xFA,
(byte)0xEB, (byte)0x46, (byte)0xB0, (byte)0x31,
(byte)0x85, (byte)0x9A, (byte)0x27, (byte)0x5B,
(byte)0xDF, (byte)0x32, (byte)0xD8, (byte)0x63
};
}

View file

@ -75,4 +75,6 @@ public interface SavedataKeys {
final long seed_b = 0xDFA3;
final long mod_a = 0xFF8F;
final long mod_b = 0xFFEF;
final String savedata_sha1_key = "VQ(DOdIO9?X3!2GmW#XF";
}