/*
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.nio.ByteBuffer;
public class AMCTRL {
private static KIRK kirk;
private static final byte[] amseed = {
(byte) 'A', (byte) 'M', (byte) 'C', (byte) 'T', (byte) 'R',
(byte) 'L', (byte) 'S', (byte) 'E', (byte) 'E', (byte) 'D',
(byte) 'J', (byte) 'P', (byte) 'C', (byte) 'S', (byte) 'P',
(byte) '0', (byte) '0', (byte) '0', (byte) '0', (byte) '0'
};
public AMCTRL() {
// Start the KIRK engine with a dummy seed and fuseID.
kirk = new KIRK(amseed, 0x14, 0xDEADC0DE, 0x12345678);
}
// AMCTRL context structs.
public static class BBCipher_Ctx {
private int mode;
private int seed;
private byte[] buf = new byte[16];
public BBCipher_Ctx() {
mode = 0;
seed = 0;
}
}
public static class BBMac_Ctx {
private int mode;
private byte[] key = new byte[16];
private byte[] pad = new byte[16];
private int padSize;
public BBMac_Ctx() {
mode = 0;
padSize = 0;
}
}
private static boolean isNullKey(byte[] key) {
if (key != null) {
for (int i = 0; i < key.length; i++) {
if (key[i] != (byte) 0) {
return false;
}
}
}
return true;
}
private byte[] xorHash(byte[] dest, int dest_offset, int[] src, int src_offset, int size) {
for (int i = 0; i < size; i++) {
dest[dest_offset + i] = (byte) (dest[dest_offset + i] ^ src[src_offset + i]);
}
return dest;
}
private byte[] xorKey(byte[] dest, int dest_offset, byte[] src, int src_offset, int size) {
for (int i = 0; i < size; i++) {
dest[dest_offset + i] = (byte) (dest[dest_offset + i] ^ src[src_offset + i]);
}
return dest;
}
private int getModeSeed(int mode) {
int seed;
switch (mode) {
case 0x2:
seed = 0x3A;
break;
default:
seed = 0x38;
break;
}
return seed;
}
private void ScrambleBB(byte[] buf, int size, int seed, int cbc, int kirk_code) {
// Set CBC mode.
buf[0] = 0;
buf[1] = 0;
buf[2] = 0;
buf[3] = (byte) cbc;
// Set unkown parameters to 0.
buf[4] = 0;
buf[5] = 0;
buf[6] = 0;
buf[7] = 0;
buf[8] = 0;
buf[9] = 0;
buf[10] = 0;
buf[11] = 0;
// Set the the key seed to seed.
buf[12] = 0;
buf[13] = 0;
buf[14] = 0;
buf[15] = (byte) seed;
// Set the the data size to size.
buf[16] = (byte) ((size >> 24) & 0xFF);
buf[17] = (byte) ((size >> 16) & 0xFF);
buf[18] = (byte) ((size >> 8) & 0xFF);
buf[19] = (byte) (size & 0xFF);
ByteBuffer bBuf = ByteBuffer.wrap(buf);
kirk.hleUtilsBufferCopyWithRange(bBuf, size, bBuf, size, kirk_code);
}
private void cipherMember(BBCipher_Ctx ctx, byte[] data, int data_offset, int length) {
byte[] dataBuf = new byte[length + 0x14];
byte[] keyBuf1 = new byte[0x10];
byte[] keyBuf2 = new byte[0x10];
byte[] hashBuf = new byte[0x10];
// Copy the hash stored by hleDrmBBCipherInit.
System.arraycopy(ctx.buf, 0, dataBuf, 0x14, 0x10);
if (ctx.mode == 0x2) {
// Decryption mode 0x02: XOR the hash with AMCTRL keys and decrypt with KIRK CMD8.
dataBuf = xorHash(dataBuf, 0x14, KeyVault.amHashKey5, 0, 0x10);
ScrambleBB(dataBuf, 0x10, 0x100, 5, KIRK.PSP_KIRK_CMD_DECRYPT_FUSE);
dataBuf = xorHash(dataBuf, 0, KeyVault.amHashKey4, 0, 0x10);
} else {
// Decryption mode 0x01: XOR the hash with AMCTRL keys and decrypt with KIRK CMD7.
dataBuf = xorHash(dataBuf, 0x14, KeyVault.amHashKey5, 0, 0x10);
ScrambleBB(dataBuf, 0x10, 0x39, 5, KIRK.PSP_KIRK_CMD_DECRYPT);
dataBuf = xorHash(dataBuf, 0, KeyVault.amHashKey4, 0, 0x10);
}
// Store the calculated key.
System.arraycopy(dataBuf, 0, keyBuf2, 0, 0x10);
// Apply extra padding if ctx.seed is not 1.
if (ctx.seed != 0x1) {
System.arraycopy(keyBuf2, 0, keyBuf1, 0, 0xC);
keyBuf1[0xC] = (byte) ((ctx.seed - 1) & 0xFF);
keyBuf1[0xD] = (byte) (((ctx.seed - 1) >> 8) & 0xFF);
keyBuf1[0xE] = (byte) (((ctx.seed - 1) >> 16) & 0xFF);
keyBuf1[0xF] = (byte) (((ctx.seed - 1) >> 24) & 0xFF);
}
// Copy the first 0xC bytes of the obtained key and replicate them
// across a new list buffer. As a terminator, add the ctx.seed parameter's
// 4 bytes (endian swapped) to achieve a full numbered list.
for (int i = 0x14; i < (length + 0x14); i += 0x10) {
System.arraycopy(keyBuf2, 0, dataBuf, i, 0xC);
dataBuf[i + 0xC] = (byte) (ctx.seed & 0xFF);
dataBuf[i + 0xD] = (byte) ((ctx.seed >> 8) & 0xFF);
dataBuf[i + 0xE] = (byte) ((ctx.seed >> 16) & 0xFF);
dataBuf[i + 0xF] = (byte) ((ctx.seed >> 24) & 0xFF);
ctx.seed++;
}
// Copy the generated hash to hashBuf.
System.arraycopy(dataBuf, length + 0x04, hashBuf, 0, 0x10);
// Decrypt the hash with KIRK CMD7 and seed 0x63.
ScrambleBB(dataBuf, length, 0x63, 5, KIRK.PSP_KIRK_CMD_DECRYPT);
// XOR the first 16-bytes of data with the saved key to generate a new hash.
dataBuf = xorKey(dataBuf, 0, keyBuf1, 0, 0x10);
// Copy back the last hash from the list to the first keyBuf.
System.arraycopy(hashBuf, 0, keyBuf1, 0, 0x10);
// Finally, XOR the full list with the given data.
xorKey(data, data_offset, dataBuf, 0, length);
}
/*
* sceDrmBB - amctrl.prx
*/
public int hleDrmBBMacInit(BBMac_Ctx ctx, int encMode) {
// Set all parameters to 0 and assign the encMode.
ctx.mode = encMode;
ctx.padSize = 0;
for (int i = 0; i < 0x10; i++) {
ctx.pad[i] = 0;
}
for (int i = 0; i < 0x10; i++) {
ctx.key[i] = 0;
}
return 0;
}
public int hleDrmBBMacUpdate(BBMac_Ctx ctx, byte[] data, int length) {
if (ctx.padSize > 0x10 || (length < 0)) {
// Invalid key or length.
return -1;
} else if (((ctx.padSize + length) <= 0x10)) {
// The key hasn't been set yet.
// Extract the hash from the data and set it as the key.
System.arraycopy(data, 0, ctx.pad, ctx.padSize, length);
ctx.padSize += length;
return 0;
} else {
// Calculate the seed.
int seed = getModeSeed(ctx.mode);
// Setup the buffer.
byte[] scrambleBuf = new byte[0x800 + 0x14];
// Copy the previous pad key to the buffer.
System.arraycopy(ctx.pad, 0, scrambleBuf, 0x14, ctx.padSize);
// Calculate new key length.
int kLen = ((ctx.padSize + length) & 0x0F);
if (kLen == 0) {
kLen = 0x10;
}
// Calculate new data length.
int nLen = ctx.padSize;
ctx.padSize = kLen;
// Copy data's footer to make a new key.
int remaining = length - kLen;
System.arraycopy(data, remaining, ctx.pad, 0, kLen);
// Process the encryption in 0x800 blocks.
int blockSize = 0x800;
for (int i = 0; i < remaining; i++) {
if (nLen == blockSize) {
// XOR with result and encrypt with KIRK CMD 4.
scrambleBuf = xorKey(scrambleBuf, 0x14, ctx.key, 0, 0x10);
ScrambleBB(scrambleBuf, blockSize, seed, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT);
System.arraycopy(scrambleBuf, (blockSize + 0x4) - 0x14, ctx.key, 0, 0x10);
// Reset length.
nLen = 0;
}
// Keep copying data.
scrambleBuf[0x14 + nLen] = data[i];
nLen++;
}
// Process any leftover data.
if (nLen > 0) {
scrambleBuf = xorKey(scrambleBuf, 0x14, ctx.key, 0, 0x10);
ScrambleBB(scrambleBuf, nLen, seed, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT);
System.arraycopy(scrambleBuf, (nLen + 0x4) - 0x14, ctx.key, 0, 0x10);
}
return 0;
}
}
public int hleDrmBBMacFinal(BBMac_Ctx ctx, byte[] hash, byte[] key) {
if (ctx.padSize > 0x10) {
// Invalid key length.
return -1;
}
// Calculate the seed.
int seed = getModeSeed(ctx.mode);
// Set up the buffer.
byte[] scrambleBuf = new byte[0x800 + 0x14];
// Set up necessary buffers.
byte[] keyBuf = new byte[0x10];
byte[] resultBuf = new byte[0x10];
// Encrypt the buffer with KIRK CMD 4.
ScrambleBB(scrambleBuf, 0x10, seed, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT);
// Store the generated key.
System.arraycopy(scrambleBuf, 0, keyBuf, 0, 0x10);
// Apply custom padding management to the stored key.
byte b = ((keyBuf[0] & (byte) 0x80) != 0) ? (byte) 0x87 : 0;
for (int i = 0; i < 0xF; i++) {
int b1 = ((keyBuf[i] & 0xFF) << 1);
int b2 = ((keyBuf[i + 1] & 0xFF) >> 7);
keyBuf[i] = (byte) (b1 | b2);
}
byte t = (byte) ((keyBuf[0xF] & 0xFF) << 1);
keyBuf[0xF] = (byte) (t ^ b);
if (ctx.padSize < 0x10) {
byte bb = ((keyBuf[0] < 0)) ? (byte) 0x87 : 0;
for (int i = 0; i < 0xF; i++) {
int bb1 = ((keyBuf[i] & 0xFF) << 1);
int bb2 = ((keyBuf[i + 1] & 0xFF) >> 7);
keyBuf[i] = (byte) (bb1 | bb2);
}
byte tt = (byte) ((keyBuf[0xF] & 0xFF) << 1);
keyBuf[0xF] = (byte) (tt ^ bb);
ctx.pad[ctx.padSize] = (byte) 0x80;
if ((ctx.padSize + 1) < 0x10) {
for (int i = 0; i < (0x10 - ctx.padSize - 1); i++) {
ctx.pad[ctx.padSize + 1 + i] = 0;
}
}
}
// XOR previous pad key with new one and copy the result back to the buffer.
ctx.pad = xorKey(ctx.pad, 0, keyBuf, 0, 0x10);
System.arraycopy(ctx.pad, 0, scrambleBuf, 0x14, 0x10);
// Save the previous result key.
System.arraycopy(ctx.key, 0, resultBuf, 0, 0x10);
// XOR the decrypted key with the result key.
scrambleBuf = xorKey(scrambleBuf, 0x14, resultBuf, 0, 0x10);
// Encrypt the key with KIRK CMD 4.
ScrambleBB(scrambleBuf, 0x10, seed, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT);
// Copy back the key into the result buffer.
System.arraycopy(scrambleBuf, 0, resultBuf, 0, 0x10);
// XOR with amHashKey3.
resultBuf = xorHash(resultBuf, 0, KeyVault.amHashKey3, 0, 0x10);
// If mode is 2, encrypt again with KIRK CMD 5 and then KIRK CMD 4.
if (ctx.mode == 0x2) {
// Copy the result buffer into the data buffer.
System.arraycopy(resultBuf, 0, scrambleBuf, 0x14, 0x10);
// Encrypt with KIRK CMD 5 (seed is always 0x100).
ScrambleBB(scrambleBuf, 0x10, 0x100, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT_FUSE);
// Copy the encrypted key to the data area of the buffer.
System.arraycopy(scrambleBuf, 0, scrambleBuf, 0x14, 0x10);
// Encrypt again with KIRK CMD 4.
ScrambleBB(scrambleBuf, 0x10, seed, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT);
// Copy back into result buffer.
System.arraycopy(scrambleBuf, 0, resultBuf, 0, 0x10);
}
// XOR with the supplied key and encrypt with KIRK CMD 4.
if (key != null) {
// XOR result buffer with user key.
resultBuf = xorKey(resultBuf, 0, key, 0, 0x10);
// Copy the result buffer into the data buffer.
System.arraycopy(resultBuf, 0, scrambleBuf, 0x14, 0x10);
// Encrypt with KIRK CMD 4.
ScrambleBB(scrambleBuf, 0x10, seed, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT);
// Copy back into the result buffer.
System.arraycopy(scrambleBuf, 0, resultBuf, 0, 0x10);
}
// Copy back the generated hash.
System.arraycopy(resultBuf, 0, hash, 0, 0x10);
// Clear the context fields.
ctx.mode = 0;
ctx.padSize = 0;
for (int i = 0; i < 0x10; i++) {
ctx.pad[i] = 0;
}
for (int i = 0; i < 0x10; i++) {
ctx.key[i] = 0;
}
return 0;
}
public int hleDrmBBMacFinal2(BBMac_Ctx ctx, byte[] hash, byte[] key) {
byte[] resBuf = new byte[0x10];
byte[] hashBuf = new byte[0x10];
int mode = ctx.mode;
// Call hleDrmBBMacFinal on an empty buffer.
hleDrmBBMacFinal(ctx, resBuf, key);
// If mode is 3, decrypt the hash first.
if ((mode & 0x3) == 0x3) {
hashBuf = DecryptBBMacKey(hash, 0x63);
} else {
hashBuf = hash;
}
// Compare the hashes.
for (int i = 0; i < 0x10; i++) {
if (hashBuf[i] != resBuf[i]) {
return -1;
}
}
return 0;
}
public int hleDrmBBCipherInit(BBCipher_Ctx ctx, int encMode, int genMode, byte[] data, byte[] key) {
return hleDrmBBCipherInit(ctx, encMode, genMode, data, key, 0);
}
public int hleDrmBBCipherInit(BBCipher_Ctx ctx, int encMode, int genMode, byte[] data, byte[] key, int seed) {
// If the key is not a 16-byte key, return an error.
if (key.length < 0x10) {
return -1;
}
// Set the mode and the unknown parameters.
ctx.mode = encMode;
ctx.seed = seed + 0x1;
// Key generator mode 0x1 (encryption): use an encrypted pseudo random number before XORing the data with the given key.
if (genMode == 0x1) {
byte[] header = new byte[0x24];
byte[] rseed = new byte[0x14];
// Generate SHA-1 to act as seed for encryption.
ByteBuffer bSeed = ByteBuffer.wrap(rseed);
kirk.hleUtilsBufferCopyWithRange(bSeed, 0x14, null, 0, KIRK.PSP_KIRK_CMD_PRNG);
// Propagate SHA-1 in kirk header.
System.arraycopy(bSeed.array(), 0, header, 0, 0x14);
System.arraycopy(bSeed.array(), 0, header, 0x14, 0x10);
header[0x20] = 0;
header[0x21] = 0;
header[0x22] = 0;
header[0x23] = 0;
if (ctx.mode == 0x2) { // Encryption mode 0x2: XOR with AMCTRL keys, encrypt with KIRK CMD5 and XOR with the given key.
header = xorHash(header, 0x14, KeyVault.amHashKey4, 0, 0x10);
ScrambleBB(header, 0x10, 0x100, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT_FUSE);
header = xorHash(header, 0, KeyVault.amHashKey5, 0, 0x10);
System.arraycopy(header, 0, ctx.buf, 0, 0x10);
System.arraycopy(header, 0, data, 0, 0x10);
// If the key is not null, XOR the hash with it.
if (!isNullKey(key)) {
ctx.buf = xorKey(ctx.buf, 0, key, 0, 0x10);
}
} else { // Encryption mode 0x1: XOR with AMCTRL keys, encrypt with KIRK CMD4 and XOR with the given key.
header = xorHash(header, 0x14, KeyVault.amHashKey4, 0, 0x10);
ScrambleBB(header, 0x10, 0x39, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT);
header = xorHash(header, 0, KeyVault.amHashKey5, 0, 0x10);
System.arraycopy(header, 0, ctx.buf, 0, 0x10);
System.arraycopy(header, 0, data, 0, 0x10);
// If the key is not null, XOR the hash with it.
if (!isNullKey(key)) {
ctx.buf = xorKey(ctx.buf, 0, key, 0, 0x10);
}
}
} else if (genMode == 0x2) { // Key generator mode 0x02 (decryption): directly XOR the data with the given key.
// Grab the data hash (first 16-bytes).
System.arraycopy(data, 0, ctx.buf, 0, 0x10);
// If the key is not null, XOR the hash with it.
if (!isNullKey(key)) {
ctx.buf = xorKey(ctx.buf, 0, key, 0, 0x10);
}
} else {
// Invalid mode.
return -1;
}
return 0;
}
public int hleDrmBBCipherUpdate(BBCipher_Ctx ctx, byte[] data, int length) {
if (length == 0) {
return 0;
}
if ((length & 0xF) != 0) {
return -1;
}
// Parse the data in 0x800 blocks first.
int index = 0;
if (length >= 0x800) {
for (index = 0; length >= 0x800; index += 0x800) {
cipherMember(ctx, data, index, 0x800);
length -= 0x800;
}
}
// Finally parse the rest of the data.
if (length >= 0x10) {
cipherMember(ctx, data, index, length);
}
return 0;
}
public int hleDrmBBCipherFinal(BBCipher_Ctx ctx) {
ctx.mode = 0;
ctx.seed = 0;
for (int i = 0; i < 0x10; i++) {
ctx.buf[i] = 0;
}
return 0;
}
public byte[] DecryptBBMacKey(byte[] key, int seed) {
byte[] scrambleBuf = new byte[0x10 + 0x14];
byte[] decKey = new byte[0x10];
System.arraycopy(key, 0, scrambleBuf, 0x14, 0x10);
ScrambleBB(scrambleBuf, 0x10, seed, 0x5, KIRK.PSP_KIRK_CMD_DECRYPT);
System.arraycopy(scrambleBuf, 0, decKey, 0, 0x10);
return decKey;
}
public byte[] GetKeyFromBBMac(BBMac_Ctx ctx, byte[] bbmac) {
byte[] key = new byte[0x10];
byte[] decKey = new byte[0x10];
byte[] macKey = new byte[0x10];
byte[] finalKey = new byte[0x10];
int mode = ctx.mode; // This will be reset to 0 by hleDrmBBMacFinal
hleDrmBBMacFinal(ctx, macKey, null);
if ((mode & 0x3) == 0x3) {
decKey = DecryptBBMacKey(bbmac, 0x63);
} else {
System.arraycopy(bbmac, 0, decKey, 0, 0x10);
}
int seed = getModeSeed(mode);
finalKey = DecryptBBMacKey(decKey, seed);
key = xorKey(macKey, 0, finalKey, 0, 0x10);
return key;
}
}