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 | } |