Moved the IV creation into another function, the hash function don't really need the contentIndex.

This commit is contained in:
Maschell 2019-04-30 14:35:32 +02:00
parent 5ccc8c1763
commit 8dc4c8cf4d
2 changed files with 50 additions and 55 deletions

View File

@ -20,6 +20,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -30,6 +31,7 @@ import de.mas.wiiu.jnus.interfaces.FSTDataProvider;
import de.mas.wiiu.jnus.interfaces.HasNUSTitle; import de.mas.wiiu.jnus.interfaces.HasNUSTitle;
import de.mas.wiiu.jnus.interfaces.NUSDataProvider; import de.mas.wiiu.jnus.interfaces.NUSDataProvider;
import de.mas.wiiu.jnus.utils.CheckSumWrongException; import de.mas.wiiu.jnus.utils.CheckSumWrongException;
import de.mas.wiiu.jnus.utils.StreamUtils;
import de.mas.wiiu.jnus.utils.Utils; import de.mas.wiiu.jnus.utils.Utils;
import de.mas.wiiu.jnus.utils.cryptography.NUSDecryption; import de.mas.wiiu.jnus.utils.cryptography.NUSDecryption;
import lombok.Getter; import lombok.Getter;
@ -91,10 +93,11 @@ public class FSTDataProviderNUSTitle implements FSTDataProvider, HasNUSTitle {
Content c = title.getTMD().getContentByIndex(entry.getContentIndex()); Content c = title.getTMD().getContentByIndex(entry.getContentIndex());
long payloadOffset = entry.getFileOffset() + offset; long payloadOffset = entry.getFileOffset() + offset;
long streamOffset = payloadOffset; long streamOffset = payloadOffset;
long streamFilesize = c.getEncryptedFileSize(); long streamFilesize = c.getEncryptedFileSize();
Optional<byte[]> IV = Optional.empty();
if (c.isHashed()) { if (c.isHashed()) {
streamOffset = (payloadOffset / 0xFC00) * 0x10000; streamOffset = (payloadOffset / 0xFC00) * 0x10000;
long offsetInBlock = payloadOffset - ((streamOffset / 0x10000) * 0xFC00); long offsetInBlock = payloadOffset - ((streamOffset / 0x10000) * 0xFC00);
@ -113,16 +116,22 @@ public class FSTDataProviderNUSTitle implements FSTDataProvider, HasNUSTitle {
streamFilesize = curVal; streamFilesize = curVal;
} }
} else { } else {
if (size != entry.getFileSize()) { byte[] newIV = new byte[0x10];
newIV[0] = (byte) ((c.getIndex() >> 8) & 0xFF);
newIV[1] = (byte) (c.getIndex() & 0xFF);
IV = Optional.of(newIV);
// if we have an offset we can't calculate the hash anymore
// we need a new IV
if (streamOffset > 0) {
streamFilesize = size; streamFilesize = size;
// We need the previous IV if we don't start at the first block.
if (payloadOffset >= 16) {
streamOffset -= 16; streamOffset -= 16;
streamFilesize += 16; streamFilesize += 16;
}
}
// We need to get the current IV as soon as we get the InputStream.
IV = Optional.empty();
}
} }
NUSDataProvider dataProvider = title.getDataProvider(); NUSDataProvider dataProvider = title.getDataProvider();
@ -134,8 +143,27 @@ public class FSTDataProviderNUSTitle implements FSTDataProvider, HasNUSTitle {
Optional<byte[]> h3HashedOpt = Optional.empty(); Optional<byte[]> h3HashedOpt = Optional.empty();
if (c.isHashed()) { if (c.isHashed()) {
h3HashedOpt = dataProvider.getContentH3Hash(c); h3HashedOpt = dataProvider.getContentH3Hash(c);
} else {
if (!IV.isPresent()) {
// If we read with an offset > 16 we need the previous 16 bytes because they are the IV.
// The input stream has been prepared to start 16 bytes earlier on this case.
int toRead = 16;
byte[] data = new byte[toRead];
int readTotal = 0;
while (readTotal < toRead) {
int res = in.read(data, readTotal, toRead - readTotal);
StreamUtils.checkForException(in);
if (res < 0) {
// This should NEVER happen.
throw new IOException();
} }
return nusdecryption.decryptStreams(in, outputStream, payloadOffset, size, c, h3HashedOpt, size != entry.getFileSize()); readTotal += res;
}
IV = Optional.of(Arrays.copyOfRange(data, 0, toRead));
}
}
return nusdecryption.decryptStreams(in, outputStream, payloadOffset, size, c, IV, h3HashedOpt, size != entry.getFileSize());
} catch (CheckSumWrongException e) { } catch (CheckSumWrongException e) {
if (c.isUNKNWNFlag1Set()) { if (c.isUNKNWNFlag1Set()) {
log.info("Hash doesn't match. But file is optional. Don't worry."); log.info("Hash doesn't match. But file is optional. Don't worry.");

View File

@ -57,7 +57,7 @@ public class NUSDecryption extends AESDecryption {
return decrypt(blockBuffer, offset, BLOCKSIZE); return decrypt(blockBuffer, offset, BLOCKSIZE);
} }
public void decryptFileStream(InputStream inputStream, OutputStream outputStream, long fileOffset, long filesize, short contentIndex, byte[] h3hash, public void decryptFileStream(InputStream inputStream, OutputStream outputStream, long fileOffset, long filesize, byte[] IV, byte[] h3hash,
long expectedSizeForHash) throws IOException, CheckSumWrongException { long expectedSizeForHash) throws IOException, CheckSumWrongException {
MessageDigest sha1 = null; MessageDigest sha1 = null;
@ -70,15 +70,6 @@ public class NUSDecryption extends AESDecryption {
} }
int BLOCKSIZE = 0x8000; int BLOCKSIZE = 0x8000;
// long dlFileLength = filesize;
// if(dlFileLength > (dlFileLength/BLOCKSIZE)*BLOCKSIZE){
// dlFileLength = ((dlFileLength/BLOCKSIZE)*BLOCKSIZE) +BLOCKSIZE;
// }
byte[] IV = new byte[0x10];
IV[0] = (byte) ((contentIndex >> 8) & 0xFF);
IV[1] = (byte) (contentIndex);
byte[] blockBuffer = new byte[BLOCKSIZE]; byte[] blockBuffer = new byte[BLOCKSIZE];
@ -87,23 +78,6 @@ public class NUSDecryption extends AESDecryption {
long writtenHash = 0; long writtenHash = 0;
try { try {
// The input stream has been prepared to start 16 bytes earlier on this case.
if (fileOffset >= 16) {
int toRead = 16;
byte[] data = new byte[toRead];
int readTotal = 0;
while (readTotal < toRead) {
int res = inputStream.read(data, readTotal, toRead - readTotal);
StreamUtils.checkForException(inputStream);
if (res < 0) {
// This should NEVER happen.
throw new IOException();
}
readTotal += res;
}
IV = Arrays.copyOfRange(data, 0, toRead);
}
ByteArrayBuffer overflow = new ByteArrayBuffer(BLOCKSIZE); ByteArrayBuffer overflow = new ByteArrayBuffer(BLOCKSIZE);
// We can only decrypt multiples of 16. So we need to align it. // We can only decrypt multiples of 16. So we need to align it.
@ -171,7 +145,7 @@ public class NUSDecryption extends AESDecryption {
} }
} }
public void decryptFileStreamHashed(InputStream inputStream, OutputStream outputStream, long fileoffset, long filesize, short contentIndex, byte[] h3Hash) public void decryptFileStreamHashed(InputStream inputStream, OutputStream outputStream, long fileoffset, long filesize, byte[] h3Hash)
throws IOException, CheckSumWrongException, NoSuchAlgorithmException { throws IOException, CheckSumWrongException, NoSuchAlgorithmException {
int BLOCKSIZE = 0x10000; int BLOCKSIZE = 0x10000;
int HASHBLOCKSIZE = 0xFC00; int HASHBLOCKSIZE = 0xFC00;
@ -202,7 +176,7 @@ public class NUSDecryption extends AESDecryption {
byte[] output; byte[] output;
try { try {
output = decryptFileChunkHash(encryptedBlockBuffer, (int) block, contentIndex, h3Hash); output = decryptFileChunkHash(encryptedBlockBuffer, (int) block, h3Hash);
} catch (CheckSumWrongException | NoSuchAlgorithmException e) { } catch (CheckSumWrongException | NoSuchAlgorithmException e) {
throw e; throw e;
} }
@ -235,20 +209,15 @@ public class NUSDecryption extends AESDecryption {
} }
} }
private byte[] decryptFileChunkHash(byte[] blockBuffer, int block, int contentIndex, byte[] h3_hashes) private byte[] decryptFileChunkHash(byte[] blockBuffer, int block, byte[] h3_hashes) throws CheckSumWrongException, NoSuchAlgorithmException {
throws CheckSumWrongException, NoSuchAlgorithmException {
int hashSize = 0x400; int hashSize = 0x400;
int blocksize = 0xFC00; int blocksize = 0xFC00;
byte[] IV = ByteBuffer.allocate(16).putShort((short) contentIndex).array();
byte[] hashes = decryptFileChunk(blockBuffer, hashSize, IV); byte[] hashes = decryptFileChunk(blockBuffer, hashSize, new byte[16]);
hashes[0] ^= (byte) ((contentIndex >> 8) & 0xFF);
hashes[1] ^= (byte) (contentIndex & 0xFF);
int H0_start = (block % 16) * 20; int H0_start = (block % 16) * 20;
IV = Arrays.copyOfRange(hashes, H0_start, H0_start + 16); byte[] IV = Arrays.copyOfRange(hashes, H0_start, H0_start + 16);
byte[] output = decryptFileChunk(blockBuffer, hashSize, blocksize, IV); byte[] output = decryptFileChunk(blockBuffer, hashSize, blocksize, IV);
HashUtil.checkFileChunkHashes(hashes, h3_hashes, output, block); HashUtil.checkFileChunkHashes(hashes, h3_hashes, output, block);
@ -256,10 +225,8 @@ public class NUSDecryption extends AESDecryption {
return output; return output;
} }
public boolean decryptStreams(InputStream inputStream, OutputStream outputStream, long offset, long size, Content content, Optional<byte[]> h3HashHashed, public boolean decryptStreams(InputStream inputStream, OutputStream outputStream, long offset, long size, Content content, Optional<byte[]> IV,
boolean partial) throws IOException, CheckSumWrongException, NoSuchAlgorithmException { Optional<byte[]> h3HashHashed, boolean partial) throws IOException, CheckSumWrongException, NoSuchAlgorithmException {
short contentIndex = (short) content.getIndex();
long encryptedFileSize = content.getEncryptedFileSize(); long encryptedFileSize = content.getEncryptedFileSize();
@ -267,14 +234,14 @@ public class NUSDecryption extends AESDecryption {
if (content.isEncrypted()) { if (content.isEncrypted()) {
if (content.isHashed()) { if (content.isHashed()) {
byte[] h3 = h3HashHashed.orElseThrow(() -> new FileNotFoundException("h3 hash not found.")); byte[] h3 = h3HashHashed.orElseThrow(() -> new FileNotFoundException("h3 hash not found."));
decryptFileStreamHashed(inputStream, outputStream, offset, size, (short) contentIndex, h3); decryptFileStreamHashed(inputStream, outputStream, offset, size, h3);
} else { } else {
byte[] h3Hash = content.getSHA2Hash(); byte[] h3Hash = content.getSHA2Hash();
// Ignore the h3hash if we don't read the whole file. // Ignore the h3hash if we don't read the whole file.
if (partial) { if (partial) {
h3Hash = null; h3Hash = null;
} }
decryptFileStream(inputStream, outputStream, offset, size, (short) contentIndex, h3Hash, encryptedFileSize); decryptFileStream(inputStream, outputStream, offset, size, IV.orElseThrow(() -> new IOException("Missing IV")), h3Hash, encryptedFileSize);
} }
} else { } else {
StreamUtils.saveInputStreamToOutputStreamWithHash(inputStream, outputStream, size, content.getSHA2Hash(), encryptedFileSize); StreamUtils.saveInputStreamToOutputStreamWithHash(inputStream, outputStream, size, content.getSHA2Hash(), encryptedFileSize);