AES CTR refactor, simplify, unify
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Dmitry Isaenko 2023-01-21 13:47:33 +03:00
parent 5bb6a3c3f4
commit 6e4e4dd3e7
32 changed files with 204 additions and 513 deletions

View file

@ -89,16 +89,13 @@ public class Converter {
}
public static byte[] hexStringToByteArray(String string){
if (string.length() % 2 != 0)
string = "0" + string;
int resultSize = string.length() / 2;
byte[] resultingArray = new byte[resultSize];
for (int i = 0; i < resultSize; i++){
resultingArray[i] = (byte) Integer.parseInt(string.substring(i*2, i*2+2), 16);
int len = string.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(string.charAt(i), 16) << 4)
+ Character.digit(string.charAt(i+1), 16));
}
return resultingArray;
return data;
}
public static byte[] flip(byte[] bytes){

View file

@ -18,7 +18,7 @@
*/
package libKonogonka.Tools;
import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.aesctr.InFileStreamProducer;
import java.io.File;

View file

@ -21,9 +21,9 @@ package libKonogonka.Tools.NCA;
import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader;
import libKonogonka.Tools.PFS0.PFS0Provider;
import libKonogonka.Tools.RomFs.RomFsProvider;
import libKonogonka.ctraes.AesCtrBufferedInputStream;
import libKonogonka.ctraes.AesCtrDecryptForMediaBlocks;
import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.aesctr.AesCtrBufferedInputStream;
import libKonogonka.aesctr.AesCtrDecryptForMediaBlocks;
import libKonogonka.aesctr.InFileStreamProducer;
import libKonogonka.exceptions.EmptySectionException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

View file

@ -18,6 +18,7 @@
*/
package libKonogonka.Tools.NCA.NCASectionTableBlock;
import libKonogonka.Converter;
import libKonogonka.RainbowDump;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -82,7 +83,7 @@ public class NcaFsHeader {
generation = Arrays.copyOfRange(tableBlockBytes, 0x140, 0x144);
secureValue = Arrays.copyOfRange(tableBlockBytes, 0x144, 0x148);
sectionCTR = Arrays.copyOfRange(tableBlockBytes, 0x140, 0x148);
sectionCTR = Converter.flip(Arrays.copyOfRange(tableBlockBytes, 0x140, 0x148));
sparseInfo = new SparseInfo(Arrays.copyOfRange(tableBlockBytes, 0x148, 0x178));
compressionInfo = new CompressionInfo(Arrays.copyOfRange(tableBlockBytes, 0x178, 0x1a0));

View file

@ -20,7 +20,7 @@ package libKonogonka.Tools.NPDM;
import libKonogonka.Tools.NPDM.ACI0.ACI0Provider;
import libKonogonka.Tools.NPDM.ACID.ACIDProvider;
import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.aesctr.InFileStreamProducer;
import java.io.BufferedInputStream;
import java.io.File;

View file

@ -18,7 +18,7 @@
*/
package libKonogonka.Tools.NSO;
import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.aesctr.InFileStreamProducer;
import java.io.BufferedInputStream;
import java.io.File;

View file

@ -18,7 +18,7 @@
*/
package libKonogonka.Tools.NSO;
import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.aesctr.InFileStreamProducer;
import net.jpountz.lz4.LZ4Factory;
import net.jpountz.lz4.LZ4SafeDecompressor;

View file

@ -22,13 +22,11 @@ import libKonogonka.RainbowDump;
import libKonogonka.Tools.ExportAble;
import libKonogonka.Tools.ISuperProvider;
import libKonogonka.Tools.NCA.NCASectionTableBlock.SuperBlockPFS0;
import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.aesctr.InFileStreamProducer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.LinkedList;
public class PFS0Provider extends ExportAble implements ISuperProvider {

View file

@ -19,7 +19,7 @@
package libKonogonka.Tools.RomFs;
import libKonogonka.Converter;
import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.aesctr.InFileStreamProducer;
import java.io.BufferedInputStream;

View file

@ -21,7 +21,7 @@ package libKonogonka.Tools.RomFs;
import libKonogonka.Tools.ExportAble;
import libKonogonka.Tools.RomFs.view.DirectoryMetaTablePlainView;
import libKonogonka.Tools.RomFs.view.FileMetaTablePlainView;
import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.aesctr.InFileStreamProducer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

View file

@ -20,14 +20,12 @@ package libKonogonka.Tools.XCI;
import libKonogonka.Tools.ExportAble;
import libKonogonka.Tools.ISuperProvider;
import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.aesctr.InFileStreamProducer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import static libKonogonka.Converter.*;

View file

@ -20,7 +20,7 @@ package libKonogonka.Tools.other.System2;
import libKonogonka.Converter;
import libKonogonka.RainbowDump;
import libKonogonka.ctraesclassic.AesCtrDecryptClassic;
import libKonogonka.aesctr.AesCtrDecryptClassic;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

View file

@ -21,8 +21,8 @@ package libKonogonka.Tools.other.System2;
import libKonogonka.KeyChainHolder;
import libKonogonka.Tools.ExportAble;
import libKonogonka.Tools.other.System2.ini1.Ini1Provider;
import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.ctraesclassic.InFileStreamClassicProducer;
import libKonogonka.aesctr.InFileStreamProducer;
import libKonogonka.aesctr.InFileStreamClassicProducer;
import java.io.*;
import java.nio.ByteBuffer;

View file

@ -20,7 +20,7 @@ package libKonogonka.Tools.other.System2.ini1;
import libKonogonka.Tools.ExportAble;
import libKonogonka.Tools.other.System2.System2Header;
import libKonogonka.ctraesclassic.InFileStreamClassicProducer;
import libKonogonka.aesctr.InFileStreamClassicProducer;
import java.io.IOException;
import java.nio.file.Files;

View file

@ -19,7 +19,7 @@
package libKonogonka.Tools.other.System2.ini1;
import libKonogonka.Tools.ExportAble;
import libKonogonka.ctraesclassic.InFileStreamClassicProducer;
import libKonogonka.aesctr.InFileStreamClassicProducer;
import java.nio.file.Paths;

View file

@ -20,7 +20,7 @@ package libKonogonka.Tools.other.System2.ini1;
import libKonogonka.Tools.NSO.SegmentHeader;
import libKonogonka.blz.BlzDecompress;
import libKonogonka.ctraesclassic.InFileStreamClassicProducer;
import libKonogonka.aesctr.InFileStreamClassicProducer;
import java.io.BufferedInputStream;
import java.io.File;

View file

@ -16,7 +16,7 @@
You should have received a copy of the GNU General Public License
along with libKonogonka. If not, see <https://www.gnu.org/licenses/>.
*/
package libKonogonka.ctraes;
package libKonogonka.aesctr;
import libKonogonka.RainbowDump;
import org.apache.logging.log4j.LogManager;
@ -27,15 +27,24 @@ import java.io.*;
public class AesCtrBufferedInputStream extends BufferedInputStream {
private final static Logger log = LogManager.getLogger(AesCtrBufferedInputStream.class);
private final AesCtrDecryptForMediaBlocks decryptor;
private final long mediaOffsetPositionStart;
private final long mediaOffsetPositionEnd;
private final AesCtrDecrypt decryptor;
private final long encryptedStartOffset;
private final long encryptedEndOffset;
private final long fileSize;
private byte[] decryptedBytes;
private long pseudoPos;
private int pointerInsideDecryptedSection;
/**
* AES CTR for 'Media Blocks'. Used in NCA.
* @param decryptor AesCtrDecryptForMediaBlocks
* @param ncaOffsetPosition NCA offset in file. If NCA is inside XCI, NSP. Otherwise, must be 0.
* @param mediaStartOffset 'Media Start Offset' in NCA representation. Small value, not bytes.
* @param mediaEndOffset 'Media End Offset' in NCA representation. Small value, not bytes.
* @param inputStream InputStream as it used in regular BufferedInputStream.
* @param fileSize File size or InputStream size.
*/
public AesCtrBufferedInputStream(AesCtrDecryptForMediaBlocks decryptor,
long ncaOffsetPosition,
long mediaStartOffset,
@ -44,13 +53,36 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
long fileSize){
super(inputStream, 0x200);
this.decryptor = decryptor;
this.mediaOffsetPositionStart = ncaOffsetPosition + (mediaStartOffset * 0x200);
this.mediaOffsetPositionEnd = ncaOffsetPosition + (mediaEndOffset * 0x200);
this.encryptedStartOffset = ncaOffsetPosition + (mediaStartOffset * 0x200);
this.encryptedEndOffset = ncaOffsetPosition + (mediaEndOffset * 0x200);
this.fileSize = fileSize;
log.trace("\n Offset Position "+ncaOffsetPosition+
"\n MediaOffsetPositionStart "+RainbowDump.formatDecHexString(mediaOffsetPositionStart)+
"\n MediaOffsetPositionEnd "+RainbowDump.formatDecHexString(mediaOffsetPositionEnd));
"\n MediaOffsetPositionStart "+RainbowDump.formatDecHexString(encryptedStartOffset)+
"\n MediaOffsetPositionEnd "+RainbowDump.formatDecHexString(encryptedEndOffset));
}
/**
* AES CTR 'classic' implementation. Used for system2 (PK21) decrypt.
* @param decryptor AesCtrDecryptClassic
* @param encryptedStartOffset Encrypted start position in bytes.
* @param encryptedEndOffset Encrypted start position in bytes.
* @param inputStream InputStream as it used in regular BufferedInputStream
* @param fileSize File size or InputStream size.
*/
public AesCtrBufferedInputStream(AesCtrDecryptClassic decryptor,
long encryptedStartOffset,
long encryptedEndOffset,
InputStream inputStream,
long fileSize){
super(inputStream, 0x200);
this.decryptor = decryptor;
this.encryptedStartOffset = encryptedStartOffset;
this.encryptedEndOffset = encryptedEndOffset;
this.fileSize = fileSize;
log.trace(" EncryptedStartOffset : "+RainbowDump.formatDecHexString(encryptedStartOffset)+
"\n EncryptedEndOffset : "+RainbowDump.formatDecHexString(encryptedEndOffset));
}
@Override
@ -90,7 +122,7 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
return b.length;
}
log.trace("1. Pointer Inside + End Position Outside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
int middleBlocksCount = (int) ((mediaOffsetPositionEnd - (pseudoPos+bytesFromFirstBlock)) / 0x200);
int middleBlocksCount = (int) ((encryptedEndOffset - (pseudoPos+bytesFromFirstBlock)) / 0x200);
int bytesFromEnd = len - bytesFromFirstBlock - middleBlocksCount * 0x200;
//1
System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesFromFirstBlock);
@ -108,7 +140,7 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
}
if (isEndPositionInsideEncryptedSection(len)) {
log.trace("2. End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
int bytesTillEncrypted = (int) (mediaOffsetPositionStart - pseudoPos);
int bytesTillEncrypted = (int) (encryptedStartOffset - pseudoPos);
int fullEncryptedBlocks = (len - bytesTillEncrypted) / 0x200;
int incompleteEncryptedBytes = (len - bytesTillEncrypted) % 0x200;
System.arraycopy(readChunk(bytesTillEncrypted), 0, b, 0, bytesTillEncrypted);
@ -133,34 +165,31 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
try{
decryptedBytes = decryptor.decryptNext(readChunk(0x200));
}
catch (Exception e){
throw new IOException(e);
}
catch (Exception e){ throw new IOException(e); }
}
private void resetAndSkip(long blockSum) throws IOException{
try{
decryptor.reset();
decryptor.skipNext(blockSum); // recalculate
}
catch (Exception e){
throw new IOException(e);
decryptor.resetAndSkip(blockSum);
}
catch (Exception e){ throw new IOException(e); }
}
private byte[] readChunk(int bytes) throws IOException{
byte[] chunkBytes = new byte[bytes];
long actuallyRead = super.read(chunkBytes, 0, bytes);
if (actuallyRead != bytes)
throw new IOException("Can't read. " + actuallyRead + "/"+ bytes);
throw new IOException("Can't read. "+ bytes +"/" + actuallyRead);
return chunkBytes;
}
private boolean isPointerInsideEncryptedSection(){
return (pseudoPos-pointerInsideDecryptedSection >= mediaOffsetPositionStart) &&
(pseudoPos-pointerInsideDecryptedSection < mediaOffsetPositionEnd);
return (pseudoPos-pointerInsideDecryptedSection >= encryptedStartOffset) &&
(pseudoPos-pointerInsideDecryptedSection < encryptedEndOffset);
}
private boolean isEndPositionInsideEncryptedSection(long requestedBytesCount){
return ((pseudoPos-pointerInsideDecryptedSection + requestedBytesCount) >= mediaOffsetPositionStart) &&
((pseudoPos-pointerInsideDecryptedSection + requestedBytesCount) < mediaOffsetPositionEnd);
return ((pseudoPos-pointerInsideDecryptedSection + requestedBytesCount) >= encryptedStartOffset) &&
((pseudoPos-pointerInsideDecryptedSection + requestedBytesCount) < encryptedEndOffset);
}
@Override
@ -175,7 +204,7 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
if (isEndPositionInsideEncryptedSection(n)){ // If we need to move somewhere out of the encrypted section
log.trace("4.1. Pointer Inside + End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+n)+")");
long blocksToSkipCountingFromStart = (pseudoPos+n - mediaOffsetPositionStart) / 0x200; // always positive
long blocksToSkipCountingFromStart = (pseudoPos+n - encryptedStartOffset) / 0x200; // always positive
resetAndSkip(blocksToSkipCountingFromStart);
long leftovers = realCountOfBytesToSkip % 0x200; // most likely will be 0; TODO: a lot of tests
@ -197,7 +226,7 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
if (isEndPositionInsideEncryptedSection(n)) { //pointer will be inside Encrypted Section, but now outside
log.trace("5. End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+n)+")");
//skip to start if the block we need
long bytesToSkipTillEncryptedBlock = mediaOffsetPositionStart - pseudoPos;
long bytesToSkipTillEncryptedBlock = encryptedStartOffset - pseudoPos;
long blocksToSkipCountingFromStart = (n - bytesToSkipTillEncryptedBlock) / 0x200; // always positive
long bytesToSkipTillRequiredBlock = bytesToSkipTillEncryptedBlock + blocksToSkipCountingFromStart * 0x200;
long leftovers = n - bytesToSkipTillRequiredBlock; // most likely will be 0;
@ -230,6 +259,7 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
log.trace("Skip loop: skipped: "+skipped+"\tmustSkip "+mustSkip);
}
}
@Override
public synchronized int read() throws IOException {
byte[] b = new byte[1];

View file

@ -0,0 +1,32 @@
package libKonogonka.aesctr;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;
public abstract class AesCtrDecrypt {
private static boolean shouldBeInitialized = true;
protected AesCtrDecrypt(){
if (shouldBeInitialized){
Security.addProvider(new BouncyCastleProvider());
shouldBeInitialized = false;
}
}
/**
* Decrypts next block of bytes. Usually 0x200.
* @param encryptedBlock Encrypted bytes
* @return Decrypted bytes
*/
abstract public byte[] decryptNext(byte[] encryptedBlock);
/**
* Initializes cipher again using updated IV (CTR)
* @param blockCount - how many blockCount from encrypted section start should be skipped. Block size = 0x200
* */
abstract public void resetAndSkip(long blockCount) throws Exception;
/**
* Initializes cipher again using initial IV (CTR)
* */
abstract public void reset() throws Exception;
}

View file

@ -16,51 +16,35 @@
You should have received a copy of the GNU General Public License
along with libKonogonka. If not, see <https://www.gnu.org/licenses/>.
*/
package libKonogonka.ctraesclassic;
package libKonogonka.aesctr;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import libKonogonka.Converter;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.Security;
public class AesCtrDecryptClassic {
private static boolean BCinitialized = false;
private void initBCProvider(){
Security.addProvider(new BouncyCastleProvider());
BCinitialized = true;
}
public class AesCtrDecryptClassic extends AesCtrDecrypt {
private final SecretKeySpec key;
private final byte[] ivArray;
private Cipher cipher;
public AesCtrDecryptClassic(String keyString, byte[] ivArray) throws Exception{
if ( ! BCinitialized)
initBCProvider();
byte[] keyArray = hexStrToByteArray(keyString);
super();
this.key = new SecretKeySpec(Converter.hexStringToByteArray(keyString), "AES");
this.ivArray = ivArray;
key = new SecretKeySpec(keyArray, "AES");
cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
IvParameterSpec iv = new IvParameterSpec(ivArray.clone());
cipher.init(Cipher.DECRYPT_MODE, key, iv);
reset();
}
@Override
public byte[] decryptNext(byte[] encryptedData) {
return cipher.update(encryptedData);
}
/**
* Initializes cipher again using updated IV
* @param blocks - how many blocks from encrypted section start should be skipped. Block size = 0x200
* */
public void resetAndSkip(long blocks) throws Exception{
reset(calculateCtr(blocks * 0x200));
@Override
public void resetAndSkip(long blockCount) throws Exception{
reset(calculateCtr(blockCount * 0x200));
}
private byte[] calculateCtr(long offset){
BigInteger ctr = new BigInteger(ivArray);
@ -75,9 +59,7 @@ public class AesCtrDecryptClassic {
return ctrCalculated;
}
/**
* Initializes cipher again using initial IV
* */
@Override
public void reset() throws Exception{
reset(ivArray.clone());
}
@ -87,14 +69,4 @@ public class AesCtrDecryptClassic {
IvParameterSpec iv = new IvParameterSpec(updatedIvArray);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
}
private byte[] hexStrToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
}

View file

@ -0,0 +1,66 @@
/*
Copyright 2019-2022 Dmitry Isaenko
This file is part of libKonogonka.
libKonogonka 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.
libKonogonka 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 libKonogonka. If not, see <https://www.gnu.org/licenses/>.
*/
package libKonogonka.aesctr;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Arrays;
/**
* Simplify decryption for NCA's AES CTR sections
*/
public class AesCtrDecryptForMediaBlocks extends AesCtrDecrypt {
private final SecretKeySpec key;
private final byte[] ivArray;
private Cipher cipher;
private final long initialOffset;
public AesCtrDecryptForMediaBlocks(byte[] key, byte[] sectionCTR, long realMediaOffset) throws Exception{
super();
this.key = new SecretKeySpec(key, "AES");
this.ivArray = Arrays.copyOf(sectionCTR, 0x10); // IV for CTR == 16 bytes; Populate first 4 bytes taken from Header's section Block CTR (aka SecureValue)
this.initialOffset = realMediaOffset;
reset();
}
@Override
public byte[] decryptNext(byte[] encryptedBlock){
return cipher.update(encryptedBlock);
}
@Override
public void resetAndSkip(long blockCount) throws Exception{
cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
long mediaOffset = initialOffset + (blockCount * 0x200L);
cipher.init(Cipher.DECRYPT_MODE, key, getIv(mediaOffset));
}
private IvParameterSpec getIv(long mediaOffset){ // Populate last 8 bytes calculated. Thanks hactool!
byte[] iv = ivArray.clone();
long offset = mediaOffset >> 4;
for (int i = 0; i < 8; i++){
iv[0x10-i-1] = (byte)(offset & 0xff);
offset >>= 8;
}
return new IvParameterSpec(iv);
}
@Override
public void reset() throws Exception{
resetAndSkip(0);
}
}

View file

@ -16,10 +16,9 @@
You should have received a copy of the GNU General Public License
along with libKonogonka. If not, see <https://www.gnu.org/licenses/>.
*/
package libKonogonka.ctraesclassic;
package libKonogonka.aesctr;
import libKonogonka.IProducer;
import libKonogonka.ctraes.InFileStreamProducer;
import java.io.BufferedInputStream;
import java.io.File;
@ -145,7 +144,7 @@ public class InFileStreamClassicProducer implements IProducer {
else
is = Files.newInputStream(filePath);
AesCtrClassicBufferedInputStream stream = new AesCtrClassicBufferedInputStream(
AesCtrBufferedInputStream stream = new AesCtrBufferedInputStream(
decryptor, encryptedStartOffset, encryptedEndOffset, is, fileSize);
if (offset != stream.skip(offset))

View file

@ -16,7 +16,7 @@
You should have received a copy of the GNU General Public License
along with libKonogonka. If not, see <https://www.gnu.org/licenses/>.
*/
package libKonogonka.ctraes;
package libKonogonka.aesctr;
import libKonogonka.IProducer;

View file

@ -1,93 +0,0 @@
/*
Copyright 2019-2022 Dmitry Isaenko
This file is part of libKonogonka.
libKonogonka 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.
libKonogonka 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 libKonogonka. If not, see <https://www.gnu.org/licenses/>.
*/
package libKonogonka.ctraes;
import libKonogonka.Converter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Security;
/**
* Simplify decryption of the CTR for NCA's AesCtr sections
*/
public class AesCtrDecryptForMediaBlocks {
private static boolean BCinitialized = false;
private Cipher cipher;
private final SecretKeySpec key;
private long realMediaOffset;
private byte[] ivArray;
private final byte[] initialSectionCTR;
private final long initialRealMediaOffset;
public AesCtrDecryptForMediaBlocks(byte[] key, byte[] sectionCTR, long realMediaOffset) throws Exception{
if ( ! BCinitialized)
initBCProvider();
this.key = new SecretKeySpec(key, "AES");
this.initialSectionCTR = sectionCTR;
this.initialRealMediaOffset = realMediaOffset;
reset();
}
private void initBCProvider(){
Security.addProvider(new BouncyCastleProvider());
BCinitialized = true;
}
public void skipNext(){
realMediaOffset += 0x200;
}
public void skipNext(long blocksNum){
realMediaOffset += blocksNum * 0x200;
}
public byte[] decryptNext(byte[] encryptedBlock) throws Exception{
updateIV();
byte[] decryptedBlock = decrypt(encryptedBlock);
realMediaOffset += 0x200;
return decryptedBlock;
}
// Populate last 8 bytes calculated. Thanks hactool project!
private void updateIV(){
long offset = realMediaOffset >> 4;
for (int i = 0; i < 0x8; i++){
ivArray[0x10-i-1] = (byte)(offset & 0xff); // Note: issues could be here
offset >>= 8;
}
}
private byte[] decrypt(byte[] encryptedData) throws Exception{
IvParameterSpec iv = new IvParameterSpec(ivArray);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
return cipher.doFinal(encryptedData);
}
public void reset() throws Exception{
realMediaOffset = initialRealMediaOffset;
cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
// IV for CTR == 16 bytes
ivArray = new byte[0x10];
// Populate first 4 bytes taken from Header's section Block CTR (aka SecureValue)
System.arraycopy(Converter.flip(initialSectionCTR), 0x0, ivArray, 0x0, 0x8);
}
}

View file

@ -1,248 +0,0 @@
/*
Copyright 2019-2023 Dmitry Isaenko
This file is part of libKonogonka.
libKonogonka 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.
libKonogonka 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 libKonogonka. If not, see <https://www.gnu.org/licenses/>.
*/
package libKonogonka.ctraesclassic;
import libKonogonka.RainbowDump;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.*;
public class AesCtrClassicBufferedInputStream extends BufferedInputStream {
private final static Logger log = LogManager.getLogger(AesCtrClassicBufferedInputStream.class);
private final AesCtrDecryptClassic decryptor;
private final long encryptedStartOffset;
private final long encryptedEndOffset;
private final long fileSize;
private byte[] decryptedBytes;
private long pseudoPos;
private int pointerInsideDecryptedSection;
public AesCtrClassicBufferedInputStream(AesCtrDecryptClassic decryptor,
long encryptedStartOffset,
long encryptedEndOffset,
InputStream inputStream,
long fileSize){
super(inputStream, 0x200);
this.decryptor = decryptor;
this.encryptedStartOffset = encryptedStartOffset;
this.encryptedEndOffset = encryptedEndOffset;
this.fileSize = fileSize;
log.trace(" EncryptedStartOffset : "+RainbowDump.formatDecHexString(encryptedStartOffset)+
"\n EncryptedEndOffset : "+RainbowDump.formatDecHexString(encryptedEndOffset));
}
@Override
public synchronized int read(byte[] b) throws IOException{
int bytesToRead = b.length;
if (isPointerInsideEncryptedSection()){
int bytesFromFirstBlock = 0x200 - pointerInsideDecryptedSection;
if (bytesFromFirstBlock > bytesToRead){
log.trace("1.2. Pointer Inside + End Position Inside (Decrypted) Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesToRead);
pseudoPos += bytesToRead;
pointerInsideDecryptedSection += bytesToRead;
return b.length;
}
if (isEndPositionInsideEncryptedSection(b.length)) {
log.trace("1.1. Pointer Inside + End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
int middleBlocksCount = (bytesToRead - bytesFromFirstBlock) / 0x200;
int bytesFromLastBlock = (bytesToRead - bytesFromFirstBlock) % 0x200;
//1
System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesFromFirstBlock);
//2
for (int i = 0; i < middleBlocksCount; i++) {
fillDecryptedCache();
System.arraycopy(decryptedBytes, 0, b, bytesFromFirstBlock+i*0x200, 0x200);
}
//3
if(fileSize > (pseudoPos+bytesToRead)) {
fillDecryptedCache();
System.arraycopy(decryptedBytes, 0, b, bytesFromFirstBlock + middleBlocksCount * 0x200, bytesFromLastBlock);
}
pseudoPos += bytesToRead;
pointerInsideDecryptedSection = bytesFromLastBlock;
return b.length;
}
log.trace("1. Pointer Inside + End Position Outside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
int middleBlocksCount = (int) ((encryptedEndOffset - (pseudoPos+bytesFromFirstBlock)) / 0x200);
int bytesFromEnd = bytesToRead - bytesFromFirstBlock - middleBlocksCount * 0x200;
//1
System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesFromFirstBlock);
//2
//log.debug("\n"+bytesFromFirstBlock+"\n"+ middleBlocksCount+" = "+(middleBlocksCount*0x200)+" bytes\n"+ bytesFromEnd+"\n");
for (int i = 0; i < middleBlocksCount; i++) {
fillDecryptedCache();
System.arraycopy(decryptedBytes, 0, b, bytesFromFirstBlock+i*0x200, 0x200);
}
//3 // TODO: if it's zero?
System.arraycopy(readChunk(bytesFromEnd), 0, b, bytesFromFirstBlock+middleBlocksCount*0x200, bytesFromEnd);
pseudoPos += bytesToRead;
pointerInsideDecryptedSection = 0;
return b.length;
}
if (isEndPositionInsideEncryptedSection(bytesToRead)) {
log.trace("2. End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
int bytesTillEncrypted = (int) (encryptedStartOffset - pseudoPos);
int fullEncryptedBlocks = (bytesToRead - bytesTillEncrypted) / 0x200;
int incompleteEncryptedBytes = (bytesToRead - bytesTillEncrypted) % 0x200;
System.arraycopy(readChunk(bytesTillEncrypted), 0, b, 0, bytesTillEncrypted);
//2
for (int i = 0; i < fullEncryptedBlocks; i++) {
fillDecryptedCache();
System.arraycopy(decryptedBytes, 0, b, fullEncryptedBlocks+i*0x200, 0x200);
}
//3
fillDecryptedCache();
System.arraycopy(decryptedBytes, 0, b, bytesTillEncrypted+fullEncryptedBlocks*0x200, incompleteEncryptedBytes);
pseudoPos += bytesToRead;
pointerInsideDecryptedSection = incompleteEncryptedBytes;
return b.length;
}
log.trace("3. Not encrypted ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
pseudoPos += bytesToRead;
pointerInsideDecryptedSection = 0;
return super.read(b);
}
private void fillDecryptedCache() throws IOException{
try{
decryptedBytes = decryptor.decryptNext(readChunk(0x200));
}
catch (Exception e){
throw new IOException(e);
}
}
private byte[] readChunk(int bytes) throws IOException{
byte[] chunkBytes = new byte[bytes];
long actuallyRead = super.read(chunkBytes);
if (actuallyRead != bytes)
throw new IOException("Can't read. "+ bytes +"/" + actuallyRead);
return chunkBytes;
}
private boolean isPointerInsideEncryptedSection(){
return (pseudoPos-pointerInsideDecryptedSection >= encryptedStartOffset) &&
(pseudoPos-pointerInsideDecryptedSection < encryptedEndOffset);
}
private boolean isEndPositionInsideEncryptedSection(long requestedBytesCount){
return ((pseudoPos-pointerInsideDecryptedSection + requestedBytesCount) >= encryptedStartOffset) &&
((pseudoPos-pointerInsideDecryptedSection + requestedBytesCount) < encryptedEndOffset);
}
@Override
public synchronized long skip(long n) throws IOException {
if (isPointerInsideEncryptedSection()){
long realCountOfBytesToSkip = n - (0x200 - pointerInsideDecryptedSection);
if (realCountOfBytesToSkip <= 0){
pseudoPos += n;
pointerInsideDecryptedSection += n;
return n;
}
if (isEndPositionInsideEncryptedSection(n)){ // If we need to move somewhere out of the encrypted section
log.trace("4.1. Pointer Inside + End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+n)+")");
long blocksToSkipCountingFromStart = (pseudoPos+n - encryptedStartOffset) / 0x200; // always positive
resetAndSkip(blocksToSkipCountingFromStart);
long leftovers = realCountOfBytesToSkip % 0x200; // most likely will be 0; TODO: a lot of tests
long bytesToSkipTillRequiredBlock = realCountOfBytesToSkip - leftovers;
skipLoop(bytesToSkipTillRequiredBlock);
fillDecryptedCache();
pseudoPos += n;
pointerInsideDecryptedSection = (int) leftovers;
return n;
}
log.trace("4. Pointer Inside + End Position Outside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+n)+")");
skipLoop(realCountOfBytesToSkip);
pseudoPos += n;
pointerInsideDecryptedSection = 0;
return n;
// just fast-forward to position we need and flush caches
}
if (isEndPositionInsideEncryptedSection(n)) { //pointer will be inside Encrypted Section, but now outside
log.trace("5. End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+n)+")");
//skip to start if the block we need
long bytesToSkipTillEncryptedBlock = encryptedStartOffset - pseudoPos;
long blocksToSkipCountingFromStart = (n - bytesToSkipTillEncryptedBlock) / 0x200; // always positive
long bytesToSkipTillRequiredBlock = bytesToSkipTillEncryptedBlock + blocksToSkipCountingFromStart * 0x200;
long leftovers = n - bytesToSkipTillRequiredBlock; // most likely will be 0;
long skipped = super.skip(bytesToSkipTillRequiredBlock);
if (bytesToSkipTillRequiredBlock != skipped)
throw new IOException("Can't skip bytes. To skip: " +
bytesToSkipTillEncryptedBlock +
".\nActually skipped: " + skipped +
".\nLeftovers inside encrypted section: " + leftovers);
log.trace("\tBlocks skipped "+blocksToSkipCountingFromStart);
resetAndSkip(blocksToSkipCountingFromStart);
fillDecryptedCache();
pseudoPos += n;
pointerInsideDecryptedSection = (int) leftovers;
return n;
}
log.trace("6. Not encrypted ("+pseudoPos+"-"+(pseudoPos+n)+")");
skipLoop(n);
pseudoPos += n;
pointerInsideDecryptedSection = 0;
return n;
}
private void skipLoop(long size) throws IOException{
long mustSkip = size;
long skipped = 0;
while (mustSkip > 0){
skipped += super.skip(mustSkip);
mustSkip = size - skipped;
log.trace("Skip loop: skipped: "+skipped+"\tmustSkip "+mustSkip);
}
}
private void resetAndSkip(long blockSum) throws IOException{
try {
decryptor.resetAndSkip(blockSum);
}
catch (Exception e){ throw new IOException(e); }
}
@Override
public synchronized int read() throws IOException {
byte[] b = new byte[1];
if (read(b) != -1)
return b[0];
return -1;
}
@Override
public boolean markSupported() {
return false;
}
@Override
public synchronized void mark(int readlimit) {}
@Override
public synchronized void reset() throws IOException {
throw new IOException("Not supported");
}
}

View file

@ -1,60 +0,0 @@
/*
Copyright 2019-2023 Dmitry Isaenko
This file is part of libKonogonka.
libKonogonka 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.
libKonogonka 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 libKonogonka. If not, see <https://www.gnu.org/licenses/>.
*/
package libKonogonka.ctraesclassic;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.InputStream;
import java.security.Security;
@Deprecated
public class AesCtrStream {
private static boolean BCinitialized = false;
private static void initBCProvider(){
Security.addProvider(new BouncyCastleProvider());
BCinitialized = true;
}
private AesCtrStream(){ }
public static CipherInputStream getStream(String keyString, byte[] IVarray, InputStream inputStream) throws Exception{
if ( ! BCinitialized)
initBCProvider();
byte[] keyArray = hexStrToByteArray(keyString);
SecretKeySpec key = new SecretKeySpec(keyArray, "AES");
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
IvParameterSpec iv = new IvParameterSpec(IVarray.clone());
cipher.init(Cipher.DECRYPT_MODE, key, iv);
return new CipherInputStream(inputStream, cipher);
}
private static byte[] hexStrToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
}

View file

@ -7,7 +7,7 @@ import libKonogonka.Tools.RomFs.FileSystemEntry;
import libKonogonka.Tools.RomFs.RomFsProvider;
import libKonogonka.Tools.other.System2.System2Provider;
import libKonogonka.Tools.other.System2.ini1.Ini1Provider;
import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.aesctr.InFileStreamProducer;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

View file

@ -6,14 +6,13 @@ import libKonogonka.Tools.NCA.NCAProvider;
import libKonogonka.Tools.RomFs.FileSystemEntry;
import libKonogonka.Tools.RomFs.RomFsProvider;
import libKonogonka.Tools.other.System2.System2Provider;
import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.aesctr.InFileStreamProducer;
import org.junit.jupiter.api.*;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

View file

@ -8,7 +8,7 @@ import libKonogonka.Tools.RomFs.RomFsProvider;
import libKonogonka.Tools.other.System2.System2Provider;
import libKonogonka.Tools.other.System2.ini1.Ini1Provider;
import libKonogonka.Tools.other.System2.ini1.KIP1Provider;
import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.aesctr.InFileStreamProducer;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

View file

@ -8,7 +8,7 @@ import libKonogonka.Tools.RomFs.RomFsProvider;
import libKonogonka.Tools.other.System2.System2Provider;
import libKonogonka.Tools.other.System2.ini1.Ini1Provider;
import libKonogonka.Tools.other.System2.ini1.KIP1Provider;
import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.aesctr.InFileStreamProducer;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

View file

@ -24,8 +24,8 @@ import libKonogonka.TitleKeyChainHolder;
import libKonogonka.Tools.NCA.NCAProvider;
import libKonogonka.Tools.PFS0.PFS0Provider;
import libKonogonka.Tools.PFS0.PFS0subFile;
import libKonogonka.ctraes.AesCtrBufferedInputStream;
import libKonogonka.ctraes.AesCtrDecryptForMediaBlocks;
import libKonogonka.aesctr.AesCtrBufferedInputStream;
import libKonogonka.aesctr.AesCtrDecryptForMediaBlocks;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

View file

@ -23,8 +23,8 @@ import libKonogonka.RainbowDump;
import libKonogonka.Tools.NCA.NCAProvider;
import libKonogonka.Tools.PFS0.PFS0Provider;
import libKonogonka.Tools.PFS0.PFS0subFile;
import libKonogonka.ctraes.AesCtrBufferedInputStream;
import libKonogonka.ctraes.AesCtrDecryptForMediaBlocks;
import libKonogonka.aesctr.AesCtrBufferedInputStream;
import libKonogonka.aesctr.AesCtrDecryptForMediaBlocks;
import org.junit.jupiter.api.*;
import java.io.*;

View file

@ -18,13 +18,13 @@
*/
package libKonogonka.unsorted;
import libKonogonka.ctraes.AesCtrBufferedInputStream;
import libKonogonka.aesctr.AesCtrBufferedInputStream;
import libKonogonka.KeyChainHolder;
import libKonogonka.RainbowDump;
import libKonogonka.Tools.NCA.NCAProvider;
import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader;
import libKonogonka.Tools.RomFs.FileSystemEntry;
import libKonogonka.ctraes.AesCtrDecryptForMediaBlocks;
import libKonogonka.aesctr.AesCtrDecryptForMediaBlocks;
import org.junit.jupiter.api.*;
import java.io.*;