AES CTR refactor, simplify, unify
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
5bb6a3c3f4
commit
6e4e4dd3e7
32 changed files with 204 additions and 513 deletions
|
@ -89,16 +89,13 @@ public class Converter {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] hexStringToByteArray(String string){
|
public static byte[] hexStringToByteArray(String string){
|
||||||
if (string.length() % 2 != 0)
|
int len = string.length();
|
||||||
string = "0" + string;
|
byte[] data = new byte[len / 2];
|
||||||
|
for (int i = 0; i < len; i += 2) {
|
||||||
int resultSize = string.length() / 2;
|
data[i / 2] = (byte) ((Character.digit(string.charAt(i), 16) << 4)
|
||||||
byte[] resultingArray = new byte[resultSize];
|
+ Character.digit(string.charAt(i+1), 16));
|
||||||
|
|
||||||
for (int i = 0; i < resultSize; i++){
|
|
||||||
resultingArray[i] = (byte) Integer.parseInt(string.substring(i*2, i*2+2), 16);
|
|
||||||
}
|
}
|
||||||
return resultingArray;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] flip(byte[] bytes){
|
public static byte[] flip(byte[] bytes){
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
*/
|
*/
|
||||||
package libKonogonka.Tools;
|
package libKonogonka.Tools;
|
||||||
|
|
||||||
import libKonogonka.ctraes.InFileStreamProducer;
|
import libKonogonka.aesctr.InFileStreamProducer;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,9 @@ package libKonogonka.Tools.NCA;
|
||||||
import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader;
|
import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader;
|
||||||
import libKonogonka.Tools.PFS0.PFS0Provider;
|
import libKonogonka.Tools.PFS0.PFS0Provider;
|
||||||
import libKonogonka.Tools.RomFs.RomFsProvider;
|
import libKonogonka.Tools.RomFs.RomFsProvider;
|
||||||
import libKonogonka.ctraes.AesCtrBufferedInputStream;
|
import libKonogonka.aesctr.AesCtrBufferedInputStream;
|
||||||
import libKonogonka.ctraes.AesCtrDecryptForMediaBlocks;
|
import libKonogonka.aesctr.AesCtrDecryptForMediaBlocks;
|
||||||
import libKonogonka.ctraes.InFileStreamProducer;
|
import libKonogonka.aesctr.InFileStreamProducer;
|
||||||
import libKonogonka.exceptions.EmptySectionException;
|
import libKonogonka.exceptions.EmptySectionException;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
*/
|
*/
|
||||||
package libKonogonka.Tools.NCA.NCASectionTableBlock;
|
package libKonogonka.Tools.NCA.NCASectionTableBlock;
|
||||||
|
|
||||||
|
import libKonogonka.Converter;
|
||||||
import libKonogonka.RainbowDump;
|
import libKonogonka.RainbowDump;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
@ -82,7 +83,7 @@ public class NcaFsHeader {
|
||||||
generation = Arrays.copyOfRange(tableBlockBytes, 0x140, 0x144);
|
generation = Arrays.copyOfRange(tableBlockBytes, 0x140, 0x144);
|
||||||
secureValue = Arrays.copyOfRange(tableBlockBytes, 0x144, 0x148);
|
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));
|
sparseInfo = new SparseInfo(Arrays.copyOfRange(tableBlockBytes, 0x148, 0x178));
|
||||||
compressionInfo = new CompressionInfo(Arrays.copyOfRange(tableBlockBytes, 0x178, 0x1a0));
|
compressionInfo = new CompressionInfo(Arrays.copyOfRange(tableBlockBytes, 0x178, 0x1a0));
|
||||||
|
|
|
@ -20,7 +20,7 @@ package libKonogonka.Tools.NPDM;
|
||||||
|
|
||||||
import libKonogonka.Tools.NPDM.ACI0.ACI0Provider;
|
import libKonogonka.Tools.NPDM.ACI0.ACI0Provider;
|
||||||
import libKonogonka.Tools.NPDM.ACID.ACIDProvider;
|
import libKonogonka.Tools.NPDM.ACID.ACIDProvider;
|
||||||
import libKonogonka.ctraes.InFileStreamProducer;
|
import libKonogonka.aesctr.InFileStreamProducer;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
*/
|
*/
|
||||||
package libKonogonka.Tools.NSO;
|
package libKonogonka.Tools.NSO;
|
||||||
|
|
||||||
import libKonogonka.ctraes.InFileStreamProducer;
|
import libKonogonka.aesctr.InFileStreamProducer;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
*/
|
*/
|
||||||
package libKonogonka.Tools.NSO;
|
package libKonogonka.Tools.NSO;
|
||||||
|
|
||||||
import libKonogonka.ctraes.InFileStreamProducer;
|
import libKonogonka.aesctr.InFileStreamProducer;
|
||||||
import net.jpountz.lz4.LZ4Factory;
|
import net.jpountz.lz4.LZ4Factory;
|
||||||
import net.jpountz.lz4.LZ4SafeDecompressor;
|
import net.jpountz.lz4.LZ4SafeDecompressor;
|
||||||
|
|
||||||
|
|
|
@ -22,13 +22,11 @@ import libKonogonka.RainbowDump;
|
||||||
import libKonogonka.Tools.ExportAble;
|
import libKonogonka.Tools.ExportAble;
|
||||||
import libKonogonka.Tools.ISuperProvider;
|
import libKonogonka.Tools.ISuperProvider;
|
||||||
import libKonogonka.Tools.NCA.NCASectionTableBlock.SuperBlockPFS0;
|
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.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
public class PFS0Provider extends ExportAble implements ISuperProvider {
|
public class PFS0Provider extends ExportAble implements ISuperProvider {
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
package libKonogonka.Tools.RomFs;
|
package libKonogonka.Tools.RomFs;
|
||||||
|
|
||||||
import libKonogonka.Converter;
|
import libKonogonka.Converter;
|
||||||
import libKonogonka.ctraes.InFileStreamProducer;
|
import libKonogonka.aesctr.InFileStreamProducer;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ package libKonogonka.Tools.RomFs;
|
||||||
import libKonogonka.Tools.ExportAble;
|
import libKonogonka.Tools.ExportAble;
|
||||||
import libKonogonka.Tools.RomFs.view.DirectoryMetaTablePlainView;
|
import libKonogonka.Tools.RomFs.view.DirectoryMetaTablePlainView;
|
||||||
import libKonogonka.Tools.RomFs.view.FileMetaTablePlainView;
|
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.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
|
|
@ -20,14 +20,12 @@ package libKonogonka.Tools.XCI;
|
||||||
|
|
||||||
import libKonogonka.Tools.ExportAble;
|
import libKonogonka.Tools.ExportAble;
|
||||||
import libKonogonka.Tools.ISuperProvider;
|
import libKonogonka.Tools.ISuperProvider;
|
||||||
import libKonogonka.ctraes.InFileStreamProducer;
|
import libKonogonka.aesctr.InFileStreamProducer;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static libKonogonka.Converter.*;
|
import static libKonogonka.Converter.*;
|
||||||
|
|
|
@ -20,7 +20,7 @@ package libKonogonka.Tools.other.System2;
|
||||||
|
|
||||||
import libKonogonka.Converter;
|
import libKonogonka.Converter;
|
||||||
import libKonogonka.RainbowDump;
|
import libKonogonka.RainbowDump;
|
||||||
import libKonogonka.ctraesclassic.AesCtrDecryptClassic;
|
import libKonogonka.aesctr.AesCtrDecryptClassic;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,8 @@ package libKonogonka.Tools.other.System2;
|
||||||
import libKonogonka.KeyChainHolder;
|
import libKonogonka.KeyChainHolder;
|
||||||
import libKonogonka.Tools.ExportAble;
|
import libKonogonka.Tools.ExportAble;
|
||||||
import libKonogonka.Tools.other.System2.ini1.Ini1Provider;
|
import libKonogonka.Tools.other.System2.ini1.Ini1Provider;
|
||||||
import libKonogonka.ctraes.InFileStreamProducer;
|
import libKonogonka.aesctr.InFileStreamProducer;
|
||||||
import libKonogonka.ctraesclassic.InFileStreamClassicProducer;
|
import libKonogonka.aesctr.InFileStreamClassicProducer;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
|
@ -20,7 +20,7 @@ package libKonogonka.Tools.other.System2.ini1;
|
||||||
|
|
||||||
import libKonogonka.Tools.ExportAble;
|
import libKonogonka.Tools.ExportAble;
|
||||||
import libKonogonka.Tools.other.System2.System2Header;
|
import libKonogonka.Tools.other.System2.System2Header;
|
||||||
import libKonogonka.ctraesclassic.InFileStreamClassicProducer;
|
import libKonogonka.aesctr.InFileStreamClassicProducer;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
package libKonogonka.Tools.other.System2.ini1;
|
package libKonogonka.Tools.other.System2.ini1;
|
||||||
|
|
||||||
import libKonogonka.Tools.ExportAble;
|
import libKonogonka.Tools.ExportAble;
|
||||||
import libKonogonka.ctraesclassic.InFileStreamClassicProducer;
|
import libKonogonka.aesctr.InFileStreamClassicProducer;
|
||||||
|
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ package libKonogonka.Tools.other.System2.ini1;
|
||||||
|
|
||||||
import libKonogonka.Tools.NSO.SegmentHeader;
|
import libKonogonka.Tools.NSO.SegmentHeader;
|
||||||
import libKonogonka.blz.BlzDecompress;
|
import libKonogonka.blz.BlzDecompress;
|
||||||
import libKonogonka.ctraesclassic.InFileStreamClassicProducer;
|
import libKonogonka.aesctr.InFileStreamClassicProducer;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with libKonogonka. If not, see <https://www.gnu.org/licenses/>.
|
along with libKonogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package libKonogonka.ctraes;
|
package libKonogonka.aesctr;
|
||||||
|
|
||||||
import libKonogonka.RainbowDump;
|
import libKonogonka.RainbowDump;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
@ -27,15 +27,24 @@ import java.io.*;
|
||||||
public class AesCtrBufferedInputStream extends BufferedInputStream {
|
public class AesCtrBufferedInputStream extends BufferedInputStream {
|
||||||
private final static Logger log = LogManager.getLogger(AesCtrBufferedInputStream.class);
|
private final static Logger log = LogManager.getLogger(AesCtrBufferedInputStream.class);
|
||||||
|
|
||||||
private final AesCtrDecryptForMediaBlocks decryptor;
|
private final AesCtrDecrypt decryptor;
|
||||||
private final long mediaOffsetPositionStart;
|
private final long encryptedStartOffset;
|
||||||
private final long mediaOffsetPositionEnd;
|
private final long encryptedEndOffset;
|
||||||
private final long fileSize;
|
private final long fileSize;
|
||||||
|
|
||||||
private byte[] decryptedBytes;
|
private byte[] decryptedBytes;
|
||||||
private long pseudoPos;
|
private long pseudoPos;
|
||||||
private int pointerInsideDecryptedSection;
|
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,
|
public AesCtrBufferedInputStream(AesCtrDecryptForMediaBlocks decryptor,
|
||||||
long ncaOffsetPosition,
|
long ncaOffsetPosition,
|
||||||
long mediaStartOffset,
|
long mediaStartOffset,
|
||||||
|
@ -44,13 +53,36 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
|
||||||
long fileSize){
|
long fileSize){
|
||||||
super(inputStream, 0x200);
|
super(inputStream, 0x200);
|
||||||
this.decryptor = decryptor;
|
this.decryptor = decryptor;
|
||||||
this.mediaOffsetPositionStart = ncaOffsetPosition + (mediaStartOffset * 0x200);
|
this.encryptedStartOffset = ncaOffsetPosition + (mediaStartOffset * 0x200);
|
||||||
this.mediaOffsetPositionEnd = ncaOffsetPosition + (mediaEndOffset * 0x200);
|
this.encryptedEndOffset = ncaOffsetPosition + (mediaEndOffset * 0x200);
|
||||||
this.fileSize = fileSize;
|
this.fileSize = fileSize;
|
||||||
|
|
||||||
log.trace("\n Offset Position "+ncaOffsetPosition+
|
log.trace("\n Offset Position "+ncaOffsetPosition+
|
||||||
"\n MediaOffsetPositionStart "+RainbowDump.formatDecHexString(mediaOffsetPositionStart)+
|
"\n MediaOffsetPositionStart "+RainbowDump.formatDecHexString(encryptedStartOffset)+
|
||||||
"\n MediaOffsetPositionEnd "+RainbowDump.formatDecHexString(mediaOffsetPositionEnd));
|
"\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
|
@Override
|
||||||
|
@ -90,7 +122,7 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
|
||||||
return b.length;
|
return b.length;
|
||||||
}
|
}
|
||||||
log.trace("1. Pointer Inside + End Position Outside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+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;
|
int bytesFromEnd = len - bytesFromFirstBlock - middleBlocksCount * 0x200;
|
||||||
//1
|
//1
|
||||||
System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesFromFirstBlock);
|
System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesFromFirstBlock);
|
||||||
|
@ -108,7 +140,7 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
|
||||||
}
|
}
|
||||||
if (isEndPositionInsideEncryptedSection(len)) {
|
if (isEndPositionInsideEncryptedSection(len)) {
|
||||||
log.trace("2. End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
|
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 fullEncryptedBlocks = (len - bytesTillEncrypted) / 0x200;
|
||||||
int incompleteEncryptedBytes = (len - bytesTillEncrypted) % 0x200;
|
int incompleteEncryptedBytes = (len - bytesTillEncrypted) % 0x200;
|
||||||
System.arraycopy(readChunk(bytesTillEncrypted), 0, b, 0, bytesTillEncrypted);
|
System.arraycopy(readChunk(bytesTillEncrypted), 0, b, 0, bytesTillEncrypted);
|
||||||
|
@ -133,34 +165,31 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
|
||||||
try{
|
try{
|
||||||
decryptedBytes = decryptor.decryptNext(readChunk(0x200));
|
decryptedBytes = decryptor.decryptNext(readChunk(0x200));
|
||||||
}
|
}
|
||||||
catch (Exception e){
|
catch (Exception e){ throw new IOException(e); }
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetAndSkip(long blockSum) throws IOException{
|
private void resetAndSkip(long blockSum) throws IOException{
|
||||||
try{
|
try{
|
||||||
decryptor.reset();
|
decryptor.resetAndSkip(blockSum);
|
||||||
decryptor.skipNext(blockSum); // recalculate
|
|
||||||
}
|
|
||||||
catch (Exception e){
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
}
|
||||||
|
catch (Exception e){ throw new IOException(e); }
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] readChunk(int bytes) throws IOException{
|
private byte[] readChunk(int bytes) throws IOException{
|
||||||
byte[] chunkBytes = new byte[bytes];
|
byte[] chunkBytes = new byte[bytes];
|
||||||
long actuallyRead = super.read(chunkBytes, 0, bytes);
|
long actuallyRead = super.read(chunkBytes, 0, bytes);
|
||||||
if (actuallyRead != bytes)
|
if (actuallyRead != bytes)
|
||||||
throw new IOException("Can't read. " + actuallyRead + "/"+ bytes);
|
throw new IOException("Can't read. "+ bytes +"/" + actuallyRead);
|
||||||
return chunkBytes;
|
return chunkBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isPointerInsideEncryptedSection(){
|
private boolean isPointerInsideEncryptedSection(){
|
||||||
return (pseudoPos-pointerInsideDecryptedSection >= mediaOffsetPositionStart) &&
|
return (pseudoPos-pointerInsideDecryptedSection >= encryptedStartOffset) &&
|
||||||
(pseudoPos-pointerInsideDecryptedSection < mediaOffsetPositionEnd);
|
(pseudoPos-pointerInsideDecryptedSection < encryptedEndOffset);
|
||||||
}
|
}
|
||||||
private boolean isEndPositionInsideEncryptedSection(long requestedBytesCount){
|
private boolean isEndPositionInsideEncryptedSection(long requestedBytesCount){
|
||||||
return ((pseudoPos-pointerInsideDecryptedSection + requestedBytesCount) >= mediaOffsetPositionStart) &&
|
return ((pseudoPos-pointerInsideDecryptedSection + requestedBytesCount) >= encryptedStartOffset) &&
|
||||||
((pseudoPos-pointerInsideDecryptedSection + requestedBytesCount) < mediaOffsetPositionEnd);
|
((pseudoPos-pointerInsideDecryptedSection + requestedBytesCount) < encryptedEndOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -175,7 +204,7 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
|
||||||
|
|
||||||
if (isEndPositionInsideEncryptedSection(n)){ // If we need to move somewhere out of the encrypted section
|
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)+")");
|
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);
|
resetAndSkip(blocksToSkipCountingFromStart);
|
||||||
|
|
||||||
long leftovers = realCountOfBytesToSkip % 0x200; // most likely will be 0; TODO: a lot of tests
|
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
|
if (isEndPositionInsideEncryptedSection(n)) { //pointer will be inside Encrypted Section, but now outside
|
||||||
log.trace("5. End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+n)+")");
|
log.trace("5. End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+n)+")");
|
||||||
//skip to start if the block we need
|
//skip to start if the block we need
|
||||||
long bytesToSkipTillEncryptedBlock = mediaOffsetPositionStart - pseudoPos;
|
long bytesToSkipTillEncryptedBlock = encryptedStartOffset - pseudoPos;
|
||||||
long blocksToSkipCountingFromStart = (n - bytesToSkipTillEncryptedBlock) / 0x200; // always positive
|
long blocksToSkipCountingFromStart = (n - bytesToSkipTillEncryptedBlock) / 0x200; // always positive
|
||||||
long bytesToSkipTillRequiredBlock = bytesToSkipTillEncryptedBlock + blocksToSkipCountingFromStart * 0x200;
|
long bytesToSkipTillRequiredBlock = bytesToSkipTillEncryptedBlock + blocksToSkipCountingFromStart * 0x200;
|
||||||
long leftovers = n - bytesToSkipTillRequiredBlock; // most likely will be 0;
|
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);
|
log.trace("Skip loop: skipped: "+skipped+"\tmustSkip "+mustSkip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized int read() throws IOException {
|
public synchronized int read() throws IOException {
|
||||||
byte[] b = new byte[1];
|
byte[] b = new byte[1];
|
32
src/main/java/libKonogonka/aesctr/AesCtrDecrypt.java
Normal file
32
src/main/java/libKonogonka/aesctr/AesCtrDecrypt.java
Normal 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;
|
||||||
|
}
|
|
@ -16,51 +16,35 @@
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with libKonogonka. If not, see <https://www.gnu.org/licenses/>.
|
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.Cipher;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.nio.ByteBuffer;
|
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 SecretKeySpec key;
|
||||||
private final byte[] ivArray;
|
private final byte[] ivArray;
|
||||||
private Cipher cipher;
|
private Cipher cipher;
|
||||||
|
|
||||||
public AesCtrDecryptClassic(String keyString, byte[] ivArray) throws Exception{
|
public AesCtrDecryptClassic(String keyString, byte[] ivArray) throws Exception{
|
||||||
if ( ! BCinitialized)
|
super();
|
||||||
initBCProvider();
|
this.key = new SecretKeySpec(Converter.hexStringToByteArray(keyString), "AES");
|
||||||
byte[] keyArray = hexStrToByteArray(keyString);
|
|
||||||
this.ivArray = ivArray;
|
this.ivArray = ivArray;
|
||||||
key = new SecretKeySpec(keyArray, "AES");
|
reset();
|
||||||
cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
|
|
||||||
IvParameterSpec iv = new IvParameterSpec(ivArray.clone());
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, key, iv);
|
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
public byte[] decryptNext(byte[] encryptedData) {
|
public byte[] decryptNext(byte[] encryptedData) {
|
||||||
return cipher.update(encryptedData);
|
return cipher.update(encryptedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Initializes cipher again using updated IV
|
public void resetAndSkip(long blockCount) throws Exception{
|
||||||
* @param blocks - how many blocks from encrypted section start should be skipped. Block size = 0x200
|
reset(calculateCtr(blockCount * 0x200));
|
||||||
* */
|
|
||||||
public void resetAndSkip(long blocks) throws Exception{
|
|
||||||
reset(calculateCtr(blocks * 0x200));
|
|
||||||
}
|
}
|
||||||
private byte[] calculateCtr(long offset){
|
private byte[] calculateCtr(long offset){
|
||||||
BigInteger ctr = new BigInteger(ivArray);
|
BigInteger ctr = new BigInteger(ivArray);
|
||||||
|
@ -75,9 +59,7 @@ public class AesCtrDecryptClassic {
|
||||||
return ctrCalculated;
|
return ctrCalculated;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Initializes cipher again using initial IV
|
|
||||||
* */
|
|
||||||
public void reset() throws Exception{
|
public void reset() throws Exception{
|
||||||
reset(ivArray.clone());
|
reset(ivArray.clone());
|
||||||
}
|
}
|
||||||
|
@ -87,14 +69,4 @@ public class AesCtrDecryptClassic {
|
||||||
IvParameterSpec iv = new IvParameterSpec(updatedIvArray);
|
IvParameterSpec iv = new IvParameterSpec(updatedIvArray);
|
||||||
cipher.init(Cipher.DECRYPT_MODE, key, iv);
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,10 +16,9 @@
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with libKonogonka. If not, see <https://www.gnu.org/licenses/>.
|
along with libKonogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package libKonogonka.ctraesclassic;
|
package libKonogonka.aesctr;
|
||||||
|
|
||||||
import libKonogonka.IProducer;
|
import libKonogonka.IProducer;
|
||||||
import libKonogonka.ctraes.InFileStreamProducer;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -145,7 +144,7 @@ public class InFileStreamClassicProducer implements IProducer {
|
||||||
else
|
else
|
||||||
is = Files.newInputStream(filePath);
|
is = Files.newInputStream(filePath);
|
||||||
|
|
||||||
AesCtrClassicBufferedInputStream stream = new AesCtrClassicBufferedInputStream(
|
AesCtrBufferedInputStream stream = new AesCtrBufferedInputStream(
|
||||||
decryptor, encryptedStartOffset, encryptedEndOffset, is, fileSize);
|
decryptor, encryptedStartOffset, encryptedEndOffset, is, fileSize);
|
||||||
|
|
||||||
if (offset != stream.skip(offset))
|
if (offset != stream.skip(offset))
|
|
@ -16,7 +16,7 @@
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with libKonogonka. If not, see <https://www.gnu.org/licenses/>.
|
along with libKonogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package libKonogonka.ctraes;
|
package libKonogonka.aesctr;
|
||||||
|
|
||||||
import libKonogonka.IProducer;
|
import libKonogonka.IProducer;
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,7 +7,7 @@ import libKonogonka.Tools.RomFs.FileSystemEntry;
|
||||||
import libKonogonka.Tools.RomFs.RomFsProvider;
|
import libKonogonka.Tools.RomFs.RomFsProvider;
|
||||||
import libKonogonka.Tools.other.System2.System2Provider;
|
import libKonogonka.Tools.other.System2.System2Provider;
|
||||||
import libKonogonka.Tools.other.System2.ini1.Ini1Provider;
|
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.Assertions;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
|
@ -6,14 +6,13 @@ import libKonogonka.Tools.NCA.NCAProvider;
|
||||||
import libKonogonka.Tools.RomFs.FileSystemEntry;
|
import libKonogonka.Tools.RomFs.FileSystemEntry;
|
||||||
import libKonogonka.Tools.RomFs.RomFsProvider;
|
import libKonogonka.Tools.RomFs.RomFsProvider;
|
||||||
import libKonogonka.Tools.other.System2.System2Provider;
|
import libKonogonka.Tools.other.System2.System2Provider;
|
||||||
import libKonogonka.ctraes.InFileStreamProducer;
|
import libKonogonka.aesctr.InFileStreamProducer;
|
||||||
import org.junit.jupiter.api.*;
|
import org.junit.jupiter.api.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
|
@ -8,7 +8,7 @@ import libKonogonka.Tools.RomFs.RomFsProvider;
|
||||||
import libKonogonka.Tools.other.System2.System2Provider;
|
import libKonogonka.Tools.other.System2.System2Provider;
|
||||||
import libKonogonka.Tools.other.System2.ini1.Ini1Provider;
|
import libKonogonka.Tools.other.System2.ini1.Ini1Provider;
|
||||||
import libKonogonka.Tools.other.System2.ini1.KIP1Provider;
|
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.Assertions;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
|
@ -8,7 +8,7 @@ import libKonogonka.Tools.RomFs.RomFsProvider;
|
||||||
import libKonogonka.Tools.other.System2.System2Provider;
|
import libKonogonka.Tools.other.System2.System2Provider;
|
||||||
import libKonogonka.Tools.other.System2.ini1.Ini1Provider;
|
import libKonogonka.Tools.other.System2.ini1.Ini1Provider;
|
||||||
import libKonogonka.Tools.other.System2.ini1.KIP1Provider;
|
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.Assertions;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
|
@ -24,8 +24,8 @@ import libKonogonka.TitleKeyChainHolder;
|
||||||
import libKonogonka.Tools.NCA.NCAProvider;
|
import libKonogonka.Tools.NCA.NCAProvider;
|
||||||
import libKonogonka.Tools.PFS0.PFS0Provider;
|
import libKonogonka.Tools.PFS0.PFS0Provider;
|
||||||
import libKonogonka.Tools.PFS0.PFS0subFile;
|
import libKonogonka.Tools.PFS0.PFS0subFile;
|
||||||
import libKonogonka.ctraes.AesCtrBufferedInputStream;
|
import libKonogonka.aesctr.AesCtrBufferedInputStream;
|
||||||
import libKonogonka.ctraes.AesCtrDecryptForMediaBlocks;
|
import libKonogonka.aesctr.AesCtrDecryptForMediaBlocks;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
|
@ -23,8 +23,8 @@ import libKonogonka.RainbowDump;
|
||||||
import libKonogonka.Tools.NCA.NCAProvider;
|
import libKonogonka.Tools.NCA.NCAProvider;
|
||||||
import libKonogonka.Tools.PFS0.PFS0Provider;
|
import libKonogonka.Tools.PFS0.PFS0Provider;
|
||||||
import libKonogonka.Tools.PFS0.PFS0subFile;
|
import libKonogonka.Tools.PFS0.PFS0subFile;
|
||||||
import libKonogonka.ctraes.AesCtrBufferedInputStream;
|
import libKonogonka.aesctr.AesCtrBufferedInputStream;
|
||||||
import libKonogonka.ctraes.AesCtrDecryptForMediaBlocks;
|
import libKonogonka.aesctr.AesCtrDecryptForMediaBlocks;
|
||||||
import org.junit.jupiter.api.*;
|
import org.junit.jupiter.api.*;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
|
|
@ -18,13 +18,13 @@
|
||||||
*/
|
*/
|
||||||
package libKonogonka.unsorted;
|
package libKonogonka.unsorted;
|
||||||
|
|
||||||
import libKonogonka.ctraes.AesCtrBufferedInputStream;
|
import libKonogonka.aesctr.AesCtrBufferedInputStream;
|
||||||
import libKonogonka.KeyChainHolder;
|
import libKonogonka.KeyChainHolder;
|
||||||
import libKonogonka.RainbowDump;
|
import libKonogonka.RainbowDump;
|
||||||
import libKonogonka.Tools.NCA.NCAProvider;
|
import libKonogonka.Tools.NCA.NCAProvider;
|
||||||
import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader;
|
import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader;
|
||||||
import libKonogonka.Tools.RomFs.FileSystemEntry;
|
import libKonogonka.Tools.RomFs.FileSystemEntry;
|
||||||
import libKonogonka.ctraes.AesCtrDecryptForMediaBlocks;
|
import libKonogonka.aesctr.AesCtrDecryptForMediaBlocks;
|
||||||
import org.junit.jupiter.api.*;
|
import org.junit.jupiter.api.*;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
|
Loading…
Reference in a new issue