View difference between Paste ID: ZpSY1Nf7 and TtuNF5J5
SHOW: | | - or go back to the newest paste.
1-
import java.io.File;
1+
package unself;
2-
import java.io.FileNotFoundException;
2+
3-
import java.io.IOException;
3+
import java.io.*;
4-
import java.io.RandomAccessFile;
4+
5-
import java.io.UnsupportedEncodingException;
5+
import java.security.*;
6
import java.security.spec.*;
7-
import java.security.InvalidAlgorithmParameterException;
7+
8-
import java.security.InvalidKeyException;
8+
9-
import java.security.NoSuchAlgorithmException;
9+
10-
import java.security.spec.AlgorithmParameterSpec;
10+
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-
public class EDAT {
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-
     * 
40+
41
     * @param outFile Path to store the decrypted data
42-
     * 
42+
     * @param klicense Authentication key. Also used for decryption on free
43
     * content
44
     * @param keyFromRif Key obtained after decrypting rif60
45-
     * @param devKLic Authentication key. Also used for decryption on free content
45+
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-
    public int decryptFile(String inFile, String outFile, byte[] devKLic, byte[] keyFromRif) throws FileNotFoundException, IOException {
49+
        RandomAccessFile raf;
50
        raf = new RandomAccessFile(fin, "r");
51-
        RandomAccessFile raf = null;
51+
        if (!checkSignature(raf)) {
52
            System.out.println("WARNING: FILE HAS BEEN TAMPERED");
53
        }
54-
        int result = validateNPD(fin.getName(), devKLic, ptr, raf); //Validate NPD hashes
54+
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-
        byte[] rifkey = getKey(npd, data, devKLic, keyFromRif); //Obtain the key for decryption (result of sc471 or sdatkey)
60+
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-
     * 
82+
83-
     * Performs checks on the header:
83+
     *
84-
     * -Version check: Must be between 0 and 3 included
84+
     * Performs checks on the header: -Version check: Must be between 0 and 4
85-
     * -Flags check: Checks that only valid active flags are set for given version.
85+
     * included -Flags check: Checks that only valid active flags are set for
86-
     *  Ver 0, 1 : Debug and compress
86+
     * given version. Ver 0, 1 : Debug and compress Ver 2 : Debug, compress,
87-
     *  Ver 2 : Debug, compress, SDAT, Keys encrypted,..
87+
     * SDAT, Keys encrypted,.. Ver 3 & 4: Debug, compress, SDAT, Keys
88-
     *  Ver 3: Debug, compress, SDAT, Keys encrypted...
88+
     * encrypted... -Metadata section hash: Checks that metadata section is
89-
     * -Metadata section hash: Checks that metadata section is valid (uses encryption key)
89+
     * valid (uses encryption key) -Header hash: Checks that header is correct
90-
     * -Header hash: Checks that header is correct (uses encryption key)
90+
     * (uses encryption key)
91
     *
92
     * @param rifKey
93
     * @param data
94
     * @param npd
95
     * @param in
96-
     * @throws IOException 
96+
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 ((data.getFlags() & 0x7FFFFFFE) != 0) return STATUS_ERROR_INCORRECT_FLAGS;            
106+
107
            if ((data.getFlags() & 0x7FFFFFFE) != 0) {
108-
            if ((data.getFlags() & 0x7EFFFFE0) != 0) return STATUS_ERROR_INCORRECT_FLAGS;
108+
                return STATUS_ERROR_INCORRECT_FLAGS;
109-
        } else if (npd.getVersion() == 3) {
109+
110-
            if ((data.getFlags() & 0x7EFFFFC0) != 0) return STATUS_ERROR_INCORRECT_FLAGS;
110+
111-
        } else return STATUS_ERROR_INCORRECT_VERSION;
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-
        if ((data.getFlags() & FLAG_DEBUG) != 0) hashFlag |= 0x01000000;
118+
119
            System.out.printf("ERROR: VERSION %d DETECTED\r\n", npd.getVersion());
120
            return STATUS_ERROR_INCORRECT_VERSION;
121-
        boolean result = a.doAll(hashFlag, 0x00000001, header, 0, out, 0, header.length, new byte[0x10], new byte[0x10], rifKey, expectedHash, 0);
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-
        System.out.println("Checking metadata hash:");
127+
128-
        a = new AppLoader();
128+
129-
        a.doInit(hashFlag, 0x00000001, new byte[0x10], new byte[0x10], rifKey);
129+
130
        int hashFlag = ((data.getFlags() & FLAG_KEYENCRYPTED) == 0) ? 0x00000002 : 0x10000002;
131-
        int sectionSize = ((data.getFlags() & FLAG_COMPRESSED) != 0) ? 0x20 : 0x010;
131+
        if ((data.getFlags() & FLAG_DEBUG) != 0) {
132-
        //Determine the metadatasection total len 
132+
            hashFlag |= 0x01000000;
133-
        int numBlocks = (int) ((data.getFileLen().intValue() + data.getBlockSize() - 11) / data.getBlockSize());
133+
134
        int keyIndex = 0;
135-
        int readed = 0;
135+
        if (npd.getVersion() == 4) {
136-
        int baseOffset = 0x100;
136+
            keyIndex = 1;
137-
        //baseOffset +=  modifier; //There is an unknown offset to add to the metadatasection... value seen 0
137+
138-
        long remaining = sectionSize * numBlocks;
138+
139-
        while (remaining > 0) {
139+
        boolean result = a.doAll(hashFlag, 0x00000001, header, 0, out, 0, header.length, new byte[0x10], new byte[0x10], rifKey, expectedHash, 0, keyIndex);
140-
            int lenToRead = (HEADER_MAX_BLOCKSIZE > remaining) ? (int) remaining : HEADER_MAX_BLOCKSIZE;
140+
141-
            in.seek(baseOffset + readed);
141+
142-
            byte[] content = new byte[lenToRead];
142+
143-
            out = new byte[lenToRead];
143+
144-
            in.readFully(content);
144+
145-
            a.doUpdate(content, 0, out, 0, lenToRead);
145+
            System.out.println("Checking metadata hash:");
146-
            readed += lenToRead;
146+
            a = new AppLoader();
147-
            remaining -= lenToRead;
147+
            a.doInit(hashFlag, 0x00000001, new byte[0x10], new byte[0x10], rifKey, keyIndex);
148
149-
        result = a.doFinal(header, 0x90);
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-
            System.out.println("Error verifying metadatasection. Data tampered");
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-
                for (int j = 0; j<0x10;j++) {
285+
        if (npd.getVersion() == 4) {
286-
                    expectedHash[j] = (byte)(metadata[j] ^ metadata[j+0x10]);
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-
                if (i == numBlocks - 1) {
291+
292-
                    len = data.getFileLen().mod(BigInteger.valueOf(data.getBlockSize())).intValue();
292+
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-
                if (i == numBlocks - 1) {
298+
299-
                    len = data.getFileLen().mod(BigInteger.valueOf(data.getBlockSize())).intValue();
299+
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-
            System.out.printf("Offset: %016X, len: %08X, realLen: %08X, endCompress: %d\r\n", offset, len, realLen,compressionEndBlock);
304+
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-
            }            
335+
336
            ToolsImpl.aesecbEncrypt(rifkey, blockKey, 0, key, 0, blockKey.length);
337-
            byte[] iv = (npd.getVersion() <= 1)?(new byte[0x10]):npd.getDigest();
337+
338-
            boolean result = a.doAll(hashFlag, cryptoFlag, encryptedData, 0, decryptedData, 0, encryptedData.length, key, iv, hash, expectedHash, 0);
338+
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-
        byte[] baseKey = (npd.getVersion() <= 1)?(new byte[0x10]):npd.getDevHash();
355+
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-
        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) {
372+
373-
            doInit(hashFlag, cryptoFlag, key, iv, hash);
373+
374
        return STATUS_OK;
375
    }
376
377
    private byte[] calculateBlockKey(int blk, NPD npd) {
378-
        public void doInit(int hashFlag, int cryptoFlag, byte[] key, byte[] iv, byte[] hashKey) {
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-
            getCryptoKeys(cryptoFlag, calculatedKey, calculatedIV, key, iv);
382+
383-
            getHashKeys(hashFlag, calculatedHash, hashKey);
383+
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-
        private void getCryptoKeys(int cryptoFlag, byte[] calculatedKey, byte[] calculatedIV, byte[] key, byte[] iv) {
402+
403
            byte[] calculatedIV = new byte[iv.length];
404
            byte[] calculatedHash = new byte[hashKey.length];
405
            getCryptoKeys(cryptoFlag, calculatedKey, calculatedIV, key, iv, keyIndex);
406-
                    ToolsImpl.aescbcDecrypt(EDATKeys.EDATKEY, EDATKeys.EDATIV, key, 0, calculatedKey, 0, calculatedKey.length);
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.arraycopy(EDATKeys.EDATKEY, 0, calculatedKey, 0, calculatedKey.length);
411+
412-
                    System.arraycopy(EDATKeys.EDATIV, 0, calculatedIV, 0, calculatedIV.length);
412+
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 getHashKeys(int hashFlag, byte[] calculatedHash, byte[] hash) {
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, EDATKeys.EDATIV, hash, 0, calculatedHash, 0, calculatedHash.length);
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-
                    System.arraycopy(EDATKeys.EDATHASH, 0, calculatedHash, 0, calculatedHash.length);
433+
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-
            if ((cryptoFlag & 0x0F000000) != 0) cryptoDebug = true;
460+
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-
            if ((hashFlag & 0x0F000000) != 0) hashDebug = 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-
                if (hashDebug || compareBytes(expectedhash, hashOffset, result, 0, hashLen)) return true;
586+
587-
                else {
587+
588-
                    System.out.printf("Error on hash. Expected %s. Obtained %s\r\n",ConversionUtils.getHexString(expectedhash), ConversionUtils.getHexString(result));
588+
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-
                if (hashDebug || compareBytes(expectedhash, hashOffset, calculatedhash, 0, hashLen)) return true;
696+
697-
                else {
697+
698-
                    System.out.printf("Error on hash. Expected %s. Obtained %s\r\n",ConversionUtils.getHexString(expectedhash), ConversionUtils.getHexString(calculatedhash));
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
}