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