Pastebin launched a little side project called VERYVIRAL.com, check it out ;-) Want more features on Pastebin? Sign Up, it's FREE!
Guest

Untitled

By: a guest on Dec 21st, 2011  |  syntax: None  |  size: 30.73 KB  |  views: 621  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
This paste has a previous version, view the difference. Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. import java.io.File;
  2. import java.io.FileNotFoundException;
  3. import java.io.IOException;
  4. import java.io.RandomAccessFile;
  5. import java.io.UnsupportedEncodingException;
  6. import java.math.BigInteger;
  7. import java.security.InvalidAlgorithmParameterException;
  8. import java.security.InvalidKeyException;
  9. import java.security.NoSuchAlgorithmException;
  10. import java.security.spec.AlgorithmParameterSpec;
  11. import javax.crypto.Cipher;
  12. import javax.crypto.Mac;
  13. import javax.crypto.NoSuchPaddingException;
  14. import javax.crypto.ShortBufferException;
  15. import javax.crypto.spec.IvParameterSpec;
  16. import javax.crypto.spec.SecretKeySpec;
  17.  
  18. public class EDAT {
  19.  
  20.     public static final int STATUS_ERROR_INPUTFILE_IO = -100;
  21.     public static final int STATUS_ERROR_HASHTITLEIDNAME = -1;
  22.     public static final int STATUS_ERROR_HASHDEVKLIC = -2;
  23.     public static final int STATUS_ERROR_MISSINGKEY = -3;
  24.     public static final int STATUS_ERROR_HEADERCHECK = -4;
  25.     public static final int STATUS_ERROR_DECRYPTING = -5;
  26.     public static final int STATUS_ERROR_INCORRECT_FLAGS = -6;
  27.     public static final int STATUS_ERROR_INCORRECT_VERSION = -7;
  28.     public static final int STATUS_OK = 0;
  29.     public static final long FLAG_COMPRESSED = 0x00000001L;
  30.     public static final long FLAG_0x02 = 0x00000002L;
  31.     public static final long FLAG_KEYENCRYPTED = 0x00000008L;
  32.     public static final long FLAG_0x10 = 0x00000010L;
  33.     public static final long FLAG_0x20 = 0x00000020L;
  34.     public static final long FLAG_SDAT = 0x01000000L;
  35.     public static final long FLAG_DEBUG = 0x80000000L;
  36.  
  37.  
  38.  
  39.     /**
  40.      *
  41.      * This function reads given file and decrypts it
  42.      *
  43.      * @param inFile EDAT file to decrypt
  44.      * @param outFile Path to store the decrypted data
  45.      * @param devKLic Authentication key. Also used for decryption on free content
  46.      * @param keyFromRif Key obtained after decrypting rif60
  47.      * @return Result of the operation
  48.      */
  49.     public int decryptFile(String inFile, String outFile, byte[] devKLic, byte[] keyFromRif) throws FileNotFoundException, IOException {
  50.         File fin = new File(inFile);
  51.         RandomAccessFile raf = null;
  52.         raf = new RandomAccessFile(fin, "r");
  53.         NPD[] ptr = new NPD[1]; //Ptr to Ptr
  54.         int result = validateNPD(fin.getName(), devKLic, ptr, raf); //Validate NPD hashes
  55.         if (result < 0) {
  56.             return result;
  57.         }
  58.         NPD npd = ptr[0];
  59.         EDATData data = getEDATData(raf); //Get flags, blocksize and file len
  60.         byte[] rifkey = getKey(npd, data, devKLic, keyFromRif); //Obtain the key for decryption (result of sc471 or sdatkey)
  61.         if (rifkey == null) {
  62.             System.out.println("ERROR: Key for decryption is missing");
  63.             return STATUS_ERROR_MISSINGKEY;
  64.         } else {
  65.             System.out.println("DECRYPTION KEY: " + ConversionUtils.getHexString(rifkey));
  66.         }
  67.         result = checkHeader(rifkey, data, npd, raf);
  68.         if (result < 0) {
  69.             return result;
  70.         }
  71.         RandomAccessFile out = new RandomAccessFile(outFile, "rw");
  72.         result = decryptData(raf, out, npd, data, rifkey);
  73.         if (result < 0) {
  74.             return result;
  75.         }
  76.         raf.close();
  77.         return STATUS_OK;
  78.     }
  79.     private static final int HEADER_MAX_BLOCKSIZE = 0x3C00;
  80.  
  81.     /**
  82.      *
  83.      * Performs checks on the header:
  84.      * -Version check: Must be between 0 and 3 included
  85.      * -Flags check: Checks that only valid active flags are set for given version.
  86.      *  Ver 0, 1 : Debug and compress
  87.      *  Ver 2 : Debug, compress, SDAT, Keys encrypted,..
  88.      *  Ver 3: Debug, compress, SDAT, Keys encrypted...
  89.      * -Metadata section hash: Checks that metadata section is valid (uses encryption key)
  90.      * -Header hash: Checks that header is correct (uses encryption key)
  91.      * @param rifKey
  92.      * @param data
  93.      * @param npd
  94.      * @param in
  95.      * @return
  96.      * @throws IOException
  97.      */
  98.     private int checkHeader(byte[] rifKey, EDATData data, NPD npd, RandomAccessFile in) throws IOException {
  99.         in.seek(0);
  100.         byte[] header = new byte[0xA0];
  101.         byte[] out = new byte[0xA0];
  102.         byte[] expectedHash = new byte[0x10];
  103.         //Version check
  104.         System.out.println("Checking NPD Version:" + npd.getVersion());
  105.         if ((npd.getVersion() == 0) || (npd.getVersion() == 1)) {
  106.             if ((data.getFlags() & 0x7FFFFFFE) != 0) return STATUS_ERROR_INCORRECT_FLAGS;            
  107.         } else if (npd.getVersion() == 2) {
  108.             if ((data.getFlags() & 0x7EFFFFE0) != 0) return STATUS_ERROR_INCORRECT_FLAGS;
  109.         } else if (npd.getVersion() == 3) {
  110.             if ((data.getFlags() & 0x7EFFFFC0) != 0) return STATUS_ERROR_INCORRECT_FLAGS;
  111.         } else return STATUS_ERROR_INCORRECT_VERSION;
  112.  
  113.         in.readFully(header);
  114.         in.readFully(expectedHash);
  115.         System.out.println("Checking header hash:");
  116.         AppLoader a = new AppLoader();
  117.         int hashFlag = ((data.getFlags() & FLAG_KEYENCRYPTED) == 0) ? 0x00000002 : 0x10000002;
  118.         if ((data.getFlags() & FLAG_DEBUG) != 0) hashFlag |= 0x01000000;
  119.  
  120.         //Veryfing header
  121.         boolean result = a.doAll(hashFlag, 0x00000001, header, 0, out, 0, header.length, new byte[0x10], new byte[0x10], rifKey, expectedHash, 0);
  122.         if (!result) {
  123.             System.out.println("Error verifying header. Is rifKey valid?.");
  124.             return STATUS_ERROR_HEADERCHECK;
  125.         }
  126.         if ((data.getFlags() & FLAG_0x20) == 0) {
  127.         System.out.println("Checking metadata hash:");
  128.         a = new AppLoader();
  129.         a.doInit(hashFlag, 0x00000001, new byte[0x10], new byte[0x10], rifKey);
  130.  
  131.         int sectionSize = ((data.getFlags() & FLAG_COMPRESSED) != 0) ? 0x20 : 0x010;
  132.         //Determine the metadatasection total len
  133.         int numBlocks = (int) ((data.getFileLen().intValue() + data.getBlockSize() - 11) / data.getBlockSize());
  134.  
  135.         int readed = 0;
  136.         int baseOffset = 0x100;
  137.         //baseOffset +=  modifier; //There is an unknown offset to add to the metadatasection... value seen 0
  138.         long remaining = sectionSize * numBlocks;
  139.         while (remaining > 0) {
  140.             int lenToRead = (HEADER_MAX_BLOCKSIZE > remaining) ? (int) remaining : HEADER_MAX_BLOCKSIZE;
  141.             in.seek(baseOffset + readed);
  142.             byte[] content = new byte[lenToRead];
  143.             out = new byte[lenToRead];
  144.             in.readFully(content);
  145.             a.doUpdate(content, 0, out, 0, lenToRead);
  146.             readed += lenToRead;
  147.             remaining -= lenToRead;
  148.         }
  149.         result = a.doFinal(header, 0x90);
  150.  
  151.  
  152.         if (!result) {
  153.             System.out.println("Error verifying metadatasection. Data tampered");
  154.             return STATUS_ERROR_HEADERCHECK;
  155.         }
  156.         } else {
  157.             System.out.println("Flag 0x20 detected. Ignore metadatasection hash");
  158.         }
  159.         return STATUS_OK;
  160.     }
  161.  
  162.     private byte[] decryptMetadataSection(byte[] metadata) {
  163.         byte[] result = new byte[0x10];
  164.         result[0x00] = (byte) (metadata[0xC] ^ metadata[0x8] ^ metadata[0x10]);
  165.         result[0x01] = (byte) (metadata[0xD] ^ metadata[0x9] ^ metadata[0x11]);
  166.         result[0x02] = (byte) (metadata[0xE] ^ metadata[0xA] ^ metadata[0x12]);
  167.         result[0x03] = (byte) (metadata[0xF] ^ metadata[0xB] ^ metadata[0x13]);
  168.         result[0x04] = (byte) (metadata[0x4] ^ metadata[0x8] ^ metadata[0x14]);
  169.         result[0x05] = (byte) (metadata[0x5] ^ metadata[0x9] ^ metadata[0x15]);
  170.         result[0x06] = (byte) (metadata[0x6] ^ metadata[0xA] ^ metadata[0x16]);
  171.         result[0x07] = (byte) (metadata[0x7] ^ metadata[0xB] ^ metadata[0x17]);
  172.         result[0x08] = (byte) (metadata[0xC] ^ metadata[0x0] ^ metadata[0x18]);
  173.         result[0x09] = (byte) (metadata[0xD] ^ metadata[0x1] ^ metadata[0x19]);
  174.         result[0x0A] = (byte) (metadata[0xE] ^ metadata[0x2] ^ metadata[0x1A]);
  175.         result[0x0B] = (byte) (metadata[0xF] ^ metadata[0x3] ^ metadata[0x1B]);
  176.         result[0x0C] = (byte) (metadata[0x4] ^ metadata[0x0] ^ metadata[0x1C]);
  177.         result[0x0D] = (byte) (metadata[0x5] ^ metadata[0x1] ^ metadata[0x1D]);
  178.         result[0x0E] = (byte) (metadata[0x6] ^ metadata[0x2] ^ metadata[0x1E]);
  179.         result[0x0F] = (byte) (metadata[0x7] ^ metadata[0x3] ^ metadata[0x1F]);
  180.         return result;
  181.     }
  182.  
  183.     private EDATData getEDATData(RandomAccessFile in) throws IOException {
  184.         in.seek(0x80);
  185.         byte[] data = new byte[0x10];
  186.         in.readFully(data);
  187.         return EDATData.createEDATData(data);
  188.     }
  189.  
  190.     private boolean compareBytes(byte[] value1, int offset1, byte[] value2, int offset2, int len) {
  191.         boolean result = true;
  192.         for (int i = 0; i < len; i++) {
  193.             if (value1[i + offset1] != value2[i + offset2]) {
  194.                 result = false;
  195.                 break;
  196.             }
  197.         }
  198.         return result;
  199.     }
  200.  
  201.     private int validateNPD(String filename, byte[] devKLic, NPD[] npdPtr, RandomAccessFile in) throws IOException {
  202.         in.seek(0);
  203.         byte[] npd = new byte[0x80];
  204.         in.readFully(npd);
  205.         byte[] extraData = new byte[0x04];
  206.         in.readFully(extraData);
  207.         long flag = ConversionUtils.be32(extraData, 0);
  208.         if ((flag & FLAG_SDAT) != 0) {
  209.             System.out.println("INFO: SDAT detected. NPD header is not validated");
  210.         } else if (!checkNPDHash1(filename, npd)) {
  211.             return STATUS_ERROR_HASHTITLEIDNAME;
  212.         } else if (devKLic == null) {
  213.             System.out.println("WARNING: Can not validate devklic header");
  214.         } else if (!checkNPDHash2(devKLic, npd)) {
  215.             return STATUS_ERROR_HASHDEVKLIC;
  216.         }
  217.         npdPtr[0] = NPD.createNPD(npd);
  218.         return STATUS_OK;
  219.     }
  220.  
  221.     private boolean checkNPDHash1(String filename, byte[] npd) throws UnsupportedEncodingException {
  222.         byte[] fileBytes = filename.getBytes("US-ASCII");
  223.         byte data1[] = new byte[0x30 + fileBytes.length];
  224.         System.arraycopy(npd, 0x10, data1, 0, 0x30);
  225.         System.arraycopy(fileBytes, 0x00, data1, 0x30, fileBytes.length);
  226.         byte[] hash1 = ToolsImpl.CMAC128(EDATKeys.npdrm_omac_key3, data1, 0, data1.length);
  227.         boolean result1 = compareBytes(hash1, 0, npd, 0x50, 0x10);
  228.         if (result1) {
  229.             System.out.println("NPD hash 1 is valid (" + ConversionUtils.getHexString(hash1) + ")");
  230.         }
  231.         return result1;
  232.     }
  233.  
  234.     private boolean checkNPDHash2(byte[] klicensee, byte[] npd) {
  235.         byte[] xoredKey = new byte[0x10];
  236.         ToolsImpl.XOR(xoredKey, klicensee, EDATKeys.npdrm_omac_key2);
  237.         byte[] calculated = ToolsImpl.CMAC128(xoredKey, npd, 0, 0x60);
  238.         boolean result2 = compareBytes(calculated, 0, npd, 0x60, 0x10);
  239.         if (result2) {
  240.             System.out.println("NPD hash 2 is valid (" + ConversionUtils.getHexString(calculated) + ")");
  241.         }
  242.         return result2;
  243.     }
  244.  
  245.  
  246.     private byte[] getKey(NPD npd, EDATData data, byte[] devKLic, byte[] keyFromRif) {
  247.         byte[] result = null;
  248.         if ((data.getFlags() & FLAG_SDAT) != 0) {
  249.             //Case SDAT
  250.             result = new byte[0x10];
  251.             ToolsImpl.XOR(result, npd.getDevHash(), EDATKeys.SDATKEY);
  252.         } else {
  253.             //Case EDAT
  254.             if (npd.getLicense() == 0x03) {
  255.                 result = devKLic;
  256.             } else if (npd.getLicense() == 0x02) {
  257.                 result = keyFromRif;
  258.             }
  259.         }
  260.         return result;
  261.     }
  262.  
  263.     private int decryptData(RandomAccessFile in, RandomAccessFile out, NPD npd, EDATData data, byte[] rifkey) throws IOException {
  264.         int numBlocks = (int) ((data.getFileLen().intValue() + data.getBlockSize() - 1) / data.getBlockSize());
  265.         int metadataSectionSize = ((data.getFlags() & FLAG_COMPRESSED) != 0 || (data.getFlags() & FLAG_0x20) != 0) ? 0x20 : 0x10;
  266.         int baseOffset = 0x100; //+ offset (unknown)
  267.         for (int i = 0; i < numBlocks; i++) {
  268.             in.seek(baseOffset + i * metadataSectionSize);
  269.             byte[] expectedHash = new byte[0x14];
  270.             long offset;
  271.             int len;
  272.             int compressionEndBlock = 0;
  273.             if ((data.getFlags() & FLAG_COMPRESSED) != 0) {
  274.                 byte[] metadata = new byte[0x20];
  275.                 in.readFully(metadata);
  276.                 byte[] result = decryptMetadataSection(metadata);
  277.                 offset = ConversionUtils.be64(result, 0).intValue(); // + offset (unknown)
  278.                 len = Long.valueOf(ConversionUtils.be32(result, 8)).intValue();
  279.                 compressionEndBlock = Long.valueOf(ConversionUtils.be32(result, 0xC)).intValue();
  280.                 System.arraycopy(metadata, 0, expectedHash, 0, 0x10);
  281.             } else if ((data.getFlags() & FLAG_0x20) != 0) {
  282.                 byte[] metadata = new byte[0x20];
  283.                 in.seek(baseOffset + i * (metadataSectionSize + data.getBlockSize()));
  284.                 in.readFully(metadata);
  285.                 for (int j = 0; j<0x10;j++) {
  286.                     expectedHash[j] = (byte)(metadata[j] ^ metadata[j+0x10]);
  287.                     System.arraycopy(metadata, 0x10, expectedHash, 0x10, 0x4);
  288.                 }
  289.                 offset = baseOffset + i * (metadataSectionSize + data.getBlockSize()) + metadataSectionSize;
  290.                 len = Long.valueOf(data.getBlockSize()).intValue();
  291.                 if (i == numBlocks - 1) {
  292.                     len = data.getFileLen().mod(BigInteger.valueOf(data.getBlockSize())).intValue();
  293.                 }
  294.             } else {
  295.                 in.readFully(expectedHash);
  296.                 offset = baseOffset + i * data.getBlockSize() + numBlocks * metadataSectionSize;
  297.                 len = Long.valueOf(data.getBlockSize()).intValue();
  298.                 if (i == numBlocks - 1) {
  299.                     len = data.getFileLen().mod(BigInteger.valueOf(data.getBlockSize())).intValue();
  300.                 }
  301.             }
  302.             int realLen = len;
  303.             len = (len + 0xF) & 0xFFFFFFF0;
  304.             System.out.printf("Offset: %016X, len: %08X, realLen: %08X, endCompress: %d\r\n", offset, len, realLen,compressionEndBlock);
  305.             in.seek(offset);
  306.             byte[] encryptedData = new byte[len];
  307.             byte[] decryptedData = new byte[len];
  308.             in.readFully(encryptedData);
  309.             byte[] key = new byte[0x10];
  310.             byte[] hash = new byte[0x10];
  311.             byte[] blockKey = calculateBlockKey(i, npd);
  312.  
  313.             ToolsImpl.aesecbEncrypt(rifkey, blockKey, 0, key, 0, blockKey.length);
  314.             if ((data.getFlags() & FLAG_0x10) != 0) {
  315.                 ToolsImpl.aesecbEncrypt(rifkey, key, 0, hash, 0, key.length);
  316.             } else {
  317.                 System.arraycopy(key, 0, hash, 0, key.length);
  318.             }
  319.             int cryptoFlag = ((data.getFlags() & FLAG_0x02) == 0) ? 0x2 : 0x1;
  320.             int hashFlag;
  321.             if ((data.getFlags() & FLAG_0x10) == 0) {
  322.                 hashFlag = 0x02;
  323.             } else if ((data.getFlags() & FLAG_0x20) == 0) {
  324.                 hashFlag = 0x04;
  325.             } else {
  326.                 hashFlag = 0x01;
  327.             }
  328.             if ((data.getFlags() & FLAG_KEYENCRYPTED) != 0) {
  329.                 cryptoFlag |= 0x10000000;
  330.                 hashFlag |= 0x10000000;
  331.             }
  332.             if ((data.getFlags() & FLAG_DEBUG) != 0) {
  333.                 cryptoFlag |= 0x01000000;
  334.                 hashFlag |= 0x01000000;
  335.             }            
  336.             AppLoader a = new AppLoader();
  337.             byte[] iv = (npd.getVersion() <= 1)?(new byte[0x10]):npd.getDigest();
  338.             boolean result = a.doAll(hashFlag, cryptoFlag, encryptedData, 0, decryptedData, 0, encryptedData.length, key, iv, hash, expectedHash, 0);
  339.             if (!result) {
  340.                 System.out.println("Error decrypting block " + i);
  341.                 return STATUS_ERROR_DECRYPTING;
  342.             }
  343.             if ((data.getFlags() & FLAG_COMPRESSED) != 0) {
  344.                 //byte[] decompress = new byte[Long.valueOf(data.getBlockSize()).intValue()];
  345.                 //DECOMPRESS: MISSING ALGORITHM                
  346.                 //out.write(decompress, 0, data.getBlockSize());
  347.             } else {
  348.                 out.write(decryptedData, 0, realLen);
  349.             }
  350.         }
  351.         return STATUS_OK;
  352.     }
  353.  
  354.     private byte[] calculateBlockKey(int blk, NPD npd) {
  355.         byte[] baseKey = (npd.getVersion() <= 1)?(new byte[0x10]):npd.getDevHash();
  356.         byte[] result = new byte[0x10];
  357.         System.arraycopy(baseKey, 0, result, 0, 0xC);
  358.         result[0xC] = (byte) (blk >> 24 & 0xFF);
  359.         result[0xD] = (byte) (blk >> 16 & 0xFF);
  360.         result[0xE] = (byte) (blk >> 8 & 0xFF);
  361.         result[0xF] = (byte) (blk & 0xFF);
  362.         return result;
  363.     }
  364.  
  365.     class AppLoader {
  366.  
  367.         private Decryptor dec;
  368.         private Hash hash;
  369.         private boolean hashDebug = false;
  370.         private boolean cryptoDebug = false; //NOT USED??
  371.  
  372.         public boolean doAll(int hashFlag, int cryptoFlag, byte[] in, int inOffset, byte[] out, int outOffset, int len, byte[] key, byte[] iv, byte[] hash, byte[] expectedHash, int hashOffset) {
  373.             doInit(hashFlag, cryptoFlag, key, iv, hash);
  374.             doUpdate(in, inOffset, out, outOffset, len);
  375.             return doFinal(expectedHash, hashOffset);
  376.         }
  377.  
  378.         public void doInit(int hashFlag, int cryptoFlag, byte[] key, byte[] iv, byte[] hashKey) {
  379.             byte[] calculatedKey = new byte[key.length];
  380.             byte[] calculatedIV = new byte[iv.length];
  381.             byte[] calculatedHash = new byte[hashKey.length];
  382.             getCryptoKeys(cryptoFlag, calculatedKey, calculatedIV, key, iv);
  383.             getHashKeys(hashFlag, calculatedHash, hashKey);
  384.             setDecryptor(cryptoFlag);
  385.             setHash(hashFlag);
  386.             System.out.println("ERK:  " + ConversionUtils.getHexString(calculatedKey));
  387.             System.out.println("IV:   " + ConversionUtils.getHexString(calculatedIV));
  388.             System.out.println("HASH: " + ConversionUtils.getHexString(calculatedHash));
  389.             dec.doInit(calculatedKey, calculatedIV);
  390.             hash.doInit(calculatedHash);
  391.         }
  392.  
  393.         public void doUpdate(byte[] in, int inOffset, byte[] out, int outOffset, int len) {
  394.             hash.doUpdate(in, inOffset, len);
  395.             dec.doUpdate(in, inOffset, out, outOffset, len);
  396.         }
  397.  
  398.         public boolean doFinal(byte[] expectedhash, int hashOffset) {
  399.             return hash.doFinal(expectedhash, hashOffset);
  400.         }
  401.  
  402.         private void getCryptoKeys(int cryptoFlag, byte[] calculatedKey, byte[] calculatedIV, byte[] key, byte[] iv) {
  403.             int mode = cryptoFlag & 0xF0000000;
  404.             switch (mode) {
  405.                 case 0x10000000:
  406.                     ToolsImpl.aescbcDecrypt(EDATKeys.EDATKEY, EDATKeys.EDATIV, key, 0, calculatedKey, 0, calculatedKey.length);
  407.                     System.arraycopy(iv, 0, calculatedIV, 0, calculatedIV.length);
  408.                     System.out.println("MODE: Encrypted ERK");
  409.                     break;
  410.                 case 0x20000000:
  411.                     System.arraycopy(EDATKeys.EDATKEY, 0, calculatedKey, 0, calculatedKey.length);
  412.                     System.arraycopy(EDATKeys.EDATIV, 0, calculatedIV, 0, calculatedIV.length);
  413.                     System.out.println("MODE: Default ERK");
  414.                     break;
  415.                 case 0x00000000:
  416.                     System.arraycopy(key, 0, calculatedKey, 0, calculatedKey.length);
  417.                     System.arraycopy(iv, 0, calculatedIV, 0, calculatedIV.length);
  418.                     System.out.println("MODE: Unencrypted ERK");
  419.                     break;
  420.                 default:
  421.                     throw new IllegalStateException("Crypto mode is not valid: Undefined keys calculator");
  422.             }
  423.         }
  424.  
  425.         private void getHashKeys(int hashFlag, byte[] calculatedHash, byte[] hash) {
  426.             int mode = hashFlag & 0xF0000000;
  427.             switch (mode) {
  428.                 case 0x10000000:
  429.                     ToolsImpl.aescbcDecrypt(EDATKeys.EDATKEY, EDATKeys.EDATIV, hash, 0, calculatedHash, 0, calculatedHash.length);
  430.                     System.out.println("MODE: Encrypted HASHKEY");
  431.                     break;
  432.                 case 0x20000000:
  433.                     System.arraycopy(EDATKeys.EDATHASH, 0, calculatedHash, 0, calculatedHash.length);
  434.                     System.out.println("MODE: Default HASHKEY");
  435.                     break;
  436.                 case 0x00000000:
  437.                     System.arraycopy(hash, 0, calculatedHash, 0, calculatedHash.length);
  438.                     System.out.println("MODE: Unencrypted HASHKEY");
  439.                     break;
  440.                 default:
  441.                     throw new IllegalStateException("Hash mode is not valid: Undefined keys calculator");
  442.             }
  443.         }
  444.  
  445.         private void setDecryptor(int cryptoFlag) {
  446.             int aux = cryptoFlag & 0xFF;
  447.             switch (aux) {
  448.                 case 0x01:
  449.                     dec = new NoCrypt();
  450.                     System.out.println("MODE: Decryption Algorithm NONE");
  451.                     break;
  452.                 case 0x02:
  453.                     dec = new AESCBC128Decrypt();
  454.                     System.out.println("MODE: Decryption Algorithm AESCBC128");
  455.                     break;
  456.                 default:
  457.                     throw new IllegalStateException("Crypto mode is not valid: Undefined decryptor");
  458.  
  459.             }
  460.             if ((cryptoFlag & 0x0F000000) != 0) cryptoDebug = true;
  461.  
  462.         }
  463.  
  464.         private void setHash(int hashFlag) {
  465.             int aux = hashFlag & 0xFF;
  466.             switch (aux) {
  467.                 case 0x01:
  468.                     hash = new HMAC();
  469.                     hash.setHashLen(0x14);
  470.                     System.out.println("MODE: Hash HMAC Len 0x14");
  471.                     break;
  472.                 case 0x02:
  473.                     hash = new CMAC();
  474.                     hash.setHashLen(0x10);
  475.                     System.out.println("MODE: Hash CMAC Len 0x10");
  476.                     break;
  477.                 case 0x04:
  478.                     hash = new HMAC();
  479.                     hash.setHashLen(0x10);
  480.                     System.out.println("MODE: Hash HMAC Len 0x10");
  481.                     break;
  482.                 default:
  483.                     throw new IllegalStateException("Hash mode is not valid: Undefined hash algorithm");
  484.             }
  485.             if ((hashFlag & 0x0F000000) != 0) hashDebug = true;
  486.         }
  487.  
  488.         abstract class Decryptor {
  489.  
  490.             public abstract void doInit(byte[] key, byte[] iv);
  491.  
  492.             public abstract void doUpdate(byte[] in, int inOffset, byte[] out, int outOffset, int len);
  493.         }
  494.  
  495.         class NoCrypt extends Decryptor {
  496.  
  497.             @Override
  498.             public void doInit(byte[] key, byte[] iv) {
  499.                 //Do nothing
  500.             }
  501.  
  502.             @Override
  503.             public void doUpdate(byte[] in, int inOffset, byte[] out, int outOffset, int len) {
  504.                 System.arraycopy(in, inOffset, out, outOffset, len);
  505.             }
  506.         }
  507.  
  508.         class AESCBC128Decrypt extends Decryptor {
  509.  
  510.             Cipher c;
  511.  
  512.             @Override
  513.             public void doInit(byte[] key, byte[] iv) {
  514.                 try {
  515.                     SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
  516.                     c = Cipher.getInstance("AES/CBC/NoPadding");
  517.                     AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);
  518.                     c.init(Cipher.DECRYPT_MODE, skeySpec, paramSpec);
  519.                 } catch (InvalidKeyException ex) {
  520.                     throw new IllegalStateException(ex);
  521.                 } catch (InvalidAlgorithmParameterException ex) {
  522.                     throw new IllegalStateException(ex);
  523.                 } catch (NoSuchAlgorithmException ex) {
  524.                     throw new IllegalStateException(ex);
  525.                 } catch (NoSuchPaddingException ex) {
  526.                     throw new IllegalStateException(ex);
  527.                 }
  528.             }
  529.  
  530.             @Override
  531.             public void doUpdate(byte[] in, int inOffset, byte[] out, int outOffset, int len) {
  532.                 try {
  533.                     c.update(in, inOffset, len, out, outOffset);
  534.                 } catch (ShortBufferException ex) {
  535.                     throw new IllegalStateException(ex);
  536.                 }
  537.             }
  538.         }
  539.  
  540.         abstract class Hash {
  541.  
  542.             public abstract void setHashLen(int len);
  543.  
  544.             public abstract void doInit(byte[] key);
  545.  
  546.             public abstract void doUpdate(byte[] in, int inOffset, int len);
  547.  
  548.             public abstract boolean doFinal(byte[] expectedhash, int hashOffset);
  549.         }
  550.  
  551.         class HMAC extends Hash {
  552.  
  553.             private int hashLen;
  554.             private Mac mac;
  555.  
  556.             @Override
  557.             public void setHashLen(int len) {
  558.                 if (len == 0x10 || len == 0x14) {
  559.                     hashLen = len;
  560.                 } else {
  561.                     throw new IllegalArgumentException("Hash len must be 0x10 or 0x14");
  562.                 }
  563.             }
  564.  
  565.             @Override
  566.             public void doInit(byte[] key) {
  567.                 try {
  568.                     SecretKeySpec skeySpec = new SecretKeySpec(key, "HmacSHA1");
  569.                     mac = Mac.getInstance("HmacSHA1");
  570.                     mac.init(skeySpec);
  571.                 } catch (InvalidKeyException ex) {
  572.                     throw new IllegalStateException(ex);
  573.                 } catch (NoSuchAlgorithmException ex) {
  574.                     throw new IllegalStateException(ex);
  575.                 }
  576.             }
  577.  
  578.             @Override
  579.             public void doUpdate(byte[] in, int inOffset, int len) {
  580.                 mac.update(in, inOffset, len);
  581.             }
  582.  
  583.             @Override
  584.             public boolean doFinal(byte[] expectedhash, int hashOffset) {
  585.                 byte[] result = mac.doFinal();
  586.                 if (hashDebug || compareBytes(expectedhash, hashOffset, result, 0, hashLen)) return true;
  587.                 else {
  588.                     System.out.printf("Error on hash. Expected %s. Obtained %s\r\n",ConversionUtils.getHexString(expectedhash), ConversionUtils.getHexString(result));
  589.                     return false;
  590.                 }
  591.             }
  592.         }
  593.  
  594.         class CMAC extends Hash {
  595.  
  596.             int hashLen;
  597.             byte[] key;
  598.             byte[] K1;
  599.             byte[] K2;
  600.             byte[] nonProcessed;
  601.             byte[] previous;
  602.  
  603.             public CMAC() {
  604.                 hashLen = 0x10;
  605.             }
  606.  
  607.             @Override
  608.             public void setHashLen(int len) {
  609.                 if (len == 0x10) {
  610.                     hashLen = len;
  611.                 } else {
  612.                     throw new IllegalArgumentException("Hash len must be 0x10");
  613.                 }
  614.             }
  615.  
  616.             @Override
  617.             public void doInit(byte[] key) {
  618.                 this.key = key;
  619.                 K1 = new byte[0x10];
  620.                 K2 = new byte[0x10];
  621.                 calculateSubkey(key, K1, K2);
  622.                 nonProcessed = null;
  623.                 previous = new byte[0x10];
  624.             }
  625.  
  626.             private void calculateSubkey(byte[] key, byte[] K1, byte[] K2) {
  627.                 byte[] zero = new byte[0x10];
  628.                 byte[] L = new byte[0x10];
  629.                 ToolsImpl.aesecbEncrypt(key, zero, 0, L, 0, zero.length);
  630.                 BigInteger aux = new BigInteger(1, L);
  631.                 if ((L[0] & 0x80) != 0) {
  632.                     //Case MSB is set
  633.                     aux = aux.shiftLeft(1).xor(BigInteger.valueOf(0x87));
  634.                 } else {
  635.                     aux = aux.shiftLeft(1);
  636.                 }
  637.                 byte[] aux1 = aux.toByteArray();
  638.                 if (aux1.length >= 0x10) {
  639.                     System.arraycopy(aux1, aux1.length - 0x10, K1, 0, 0x10);
  640.                 } else {
  641.                     System.arraycopy(zero, 0, K1, 0, zero.length);
  642.                     System.arraycopy(aux1, 0, K1, 0x10 - aux1.length, aux1.length);
  643.                 }
  644.                 aux = new BigInteger(1, K1);
  645.                 if ((K1[0] & 0x80) != 0) {
  646.                     aux = aux.shiftLeft(1).xor(BigInteger.valueOf(0x87));
  647.                 } else {
  648.                     aux = aux.shiftLeft(1);
  649.                 }
  650.                 aux1 = aux.toByteArray();
  651.                 if (aux1.length >= 0x10) {
  652.                     System.arraycopy(aux1, aux1.length - 0x10, K2, 0, 0x10);
  653.                 } else {
  654.                     System.arraycopy(zero, 0, K2, 0, zero.length);
  655.                     System.arraycopy(aux1, 0, K2, 0x10 - aux1.length, aux1.length);
  656.                 }
  657.             }
  658.  
  659.             @Override
  660.             public void doUpdate(byte[] in, int inOffset, int len) {
  661.                 byte[] data;
  662.                 if (nonProcessed != null) {
  663.                     int totalLen = len + nonProcessed.length;
  664.                     data = new byte[totalLen];
  665.                     System.arraycopy(nonProcessed, 0, data, 0, nonProcessed.length);
  666.                     System.arraycopy(in, inOffset, data, nonProcessed.length, len);
  667.                 } else {
  668.                     data = new byte[len];
  669.                     System.arraycopy(in, inOffset, data, 0, len);
  670.                 }
  671.                 int count = 0;
  672.                 while (count < data.length - 0x10) {
  673.                     byte[] aux = new byte[0x10];
  674.                     System.arraycopy(data, count, aux, 0, aux.length);
  675.                     ToolsImpl.XOR(aux, aux, previous);
  676.                     ToolsImpl.aesecbEncrypt(key, aux, 0, previous, 0, aux.length);
  677.                     count += 0x10;
  678.                 }
  679.                 nonProcessed = new byte[data.length - count];
  680.                 System.arraycopy(data, count, nonProcessed, 0, nonProcessed.length);
  681.             }
  682.  
  683.             @Override
  684.             public boolean doFinal(byte[] expectedhash, int hashOffset) {
  685.                 byte[] aux = new byte[0x10];
  686.                 System.arraycopy(nonProcessed, 0, aux, 0, nonProcessed.length);
  687.                 if (nonProcessed.length == 0x10) {
  688.                     ToolsImpl.XOR(aux, aux, K1);
  689.                 } else {
  690.                     aux[nonProcessed.length] = (byte) 0x80;
  691.                     ToolsImpl.XOR(aux, aux, K2);
  692.                 }
  693.                 ToolsImpl.XOR(aux, aux, previous);
  694.                 byte[] calculatedhash = new byte[0x10];
  695.                 ToolsImpl.aesecbEncrypt(key, aux, 0, calculatedhash, 0, aux.length);
  696.                 if (hashDebug || compareBytes(expectedhash, hashOffset, calculatedhash, 0, hashLen)) return true;
  697.                 else {
  698.                     System.out.printf("Error on hash. Expected %s. Obtained %s\r\n",ConversionUtils.getHexString(expectedhash), ConversionUtils.getHexString(calculatedhash));
  699.                     return false;
  700.                 }
  701.             }
  702.         }
  703.     }
  704. }