mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-05-29 10:24:38 +00:00
Added Saving Harman encrypted SWFs
Added Editing encrypted flag on header panel Added `-encrypt` command on CLI for Harman encryption
This commit is contained in:
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2023 JPEXS, All rights reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3.0 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library.
|
||||
*/
|
||||
package com.jpexs.decompiler.flash;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*
|
||||
* Based on swfdecrypt.py by NathaanTFM
|
||||
*/
|
||||
public class HarmanDecryption {
|
||||
|
||||
private static final String GLOBAL_KEY = "Adobe AIR SDK (c) 2021 HARMAN Internation Industries Incorporated";
|
||||
|
||||
private static int sum(byte[] data) {
|
||||
int s = 0;
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
s += data[i] & 0xff;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
private static long getkey(byte[] data) {
|
||||
int dsum = sum(data);
|
||||
int dmod = dsum % GLOBAL_KEY.length();
|
||||
String s = GLOBAL_KEY.substring(dmod) + GLOBAL_KEY.substring(0, dmod);
|
||||
s += " EncryptSWF ";
|
||||
s += "" + dsum;
|
||||
|
||||
long ret = 0;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
int code = s.charAt(i);
|
||||
ret *= 31;
|
||||
ret += code;
|
||||
}
|
||||
|
||||
return ret & 0xffffffffL;
|
||||
}
|
||||
|
||||
private static long unpack(byte[] data, int start) {
|
||||
return (data[start] & 0xff)
|
||||
+ ((long) (data[start + 1] & 0xff) << 8)
|
||||
+ ((long) (data[start + 2] & 0xff) << 16)
|
||||
+ ((long) (data[start + 3] & 0xff) << 24);
|
||||
}
|
||||
|
||||
public InputStream decrypt(InputStream is, byte[] header) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
|
||||
header[0] -= 32; // to uppercase
|
||||
|
||||
long key = getkey(header);
|
||||
|
||||
//get the length
|
||||
DataInputStream dais = new DataInputStream(is);
|
||||
|
||||
byte[] encryptedLengthBytes = new byte[4];
|
||||
dais.readFully(encryptedLengthBytes);
|
||||
long encryptedLength = unpack(encryptedLengthBytes, 0);
|
||||
int decryptedLength = (int) (encryptedLength ^ key);
|
||||
|
||||
//padded length
|
||||
int paddedLength = (int) (decryptedLength + 0x1F) & ~0x1F;
|
||||
|
||||
//aes iv
|
||||
byte[] aesIV = new byte[16];
|
||||
System.arraycopy(header, 0, aesIV, 0, header.length); //header
|
||||
System.arraycopy(encryptedLengthBytes, 0, aesIV, 8, 4); //encrypted length
|
||||
aesIV[12] = (byte) (key & 0xff);
|
||||
aesIV[13] = (byte) ((key >> 8) & 0xff);
|
||||
aesIV[14] = (byte) ((key >> 16) & 0xff);
|
||||
aesIV[15] = (byte) ((key >> 24) & 0xff);
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
aesIV[i] ^= GLOBAL_KEY.charAt(i);
|
||||
}
|
||||
|
||||
// aes key
|
||||
// this one is stored at the end of the file
|
||||
byte[] aesKey = new byte[32];
|
||||
byte[] data = new byte[paddedLength];
|
||||
dais.readFully(data);
|
||||
|
||||
byte[] aesKeyData = new byte[32];
|
||||
dais.readFully(aesKeyData);
|
||||
|
||||
for (int i = 0; i < 32; i += 4) {
|
||||
long value = unpack(aesKeyData, i);
|
||||
if ((i & 4) == 4) {
|
||||
value -= key;
|
||||
} else {
|
||||
value += key;
|
||||
}
|
||||
aesKey[i] = (byte) (value & 0xff);
|
||||
aesKey[i + 1] = (byte) ((value >> 8) & 0xff);
|
||||
aesKey[i + 2] = (byte) ((value >> 16) & 0xff);
|
||||
aesKey[i + 3] = (byte) ((value >> 24) & 0xff);
|
||||
}
|
||||
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(aesKey, "AES");
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(aesIV));
|
||||
|
||||
byte[] decryptedData = cipher.doFinal(data);
|
||||
return new ByteArrayInputStream(Arrays.copyOfRange(decryptedData, 0, decryptedLength));
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,7 @@ import com.jpexs.decompiler.flash.exporters.script.AS2ScriptExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.script.AS3ScriptExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.settings.ScriptExportSettings;
|
||||
import com.jpexs.decompiler.flash.exporters.shape.ShapeExportData;
|
||||
import com.jpexs.decompiler.flash.harman.HarmanSwfEncrypt;
|
||||
import com.jpexs.decompiler.flash.helpers.HighlightedText;
|
||||
import com.jpexs.decompiler.flash.helpers.HighlightedTextWriter;
|
||||
import com.jpexs.decompiler.flash.helpers.NulWriter;
|
||||
@@ -1140,15 +1141,20 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
|
||||
*/
|
||||
@Override
|
||||
public void saveTo(OutputStream os) throws IOException {
|
||||
checkCharset();
|
||||
byte[] uncompressedData = saveToByteArray(false);
|
||||
compress(new ByteArrayInputStream(uncompressedData), os, compression, lzmaProperties);
|
||||
saveTo(os, gfx, false);
|
||||
}
|
||||
|
||||
public void saveTo(OutputStream os, boolean gfx, boolean includeImported) throws IOException {
|
||||
checkCharset();
|
||||
byte[] uncompressedData = saveToByteArray(gfx, includeImported);
|
||||
compress(new ByteArrayInputStream(uncompressedData), os, compression, lzmaProperties);
|
||||
byte[] newUncompressedData = saveToByteArray(gfx, includeImported);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
compress(new ByteArrayInputStream(newUncompressedData), baos, compression, lzmaProperties);
|
||||
byte[] newCompressedData = baos.toByteArray();
|
||||
if (encrypted) {
|
||||
encrypt(new ByteArrayInputStream(newCompressedData), os);
|
||||
} else {
|
||||
os.write(newCompressedData);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getHeaderBytes() {
|
||||
@@ -2002,8 +2008,30 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts Harman AIR encryption
|
||||
*
|
||||
* Encrypts Harman AIR encryption
|
||||
*/
|
||||
public static boolean encrypt(InputStream is, OutputStream os) throws IOException {
|
||||
byte[] hdr = new byte[8];
|
||||
|
||||
// SWFheader: signature, version and fileSize
|
||||
if (is.read(hdr) != 8) {
|
||||
throw new SwfOpenException(AppResources.translate("error.swf.headerTooShort"));
|
||||
}
|
||||
|
||||
decodeHeader(hdr);
|
||||
|
||||
byte[] encrypted;
|
||||
try {
|
||||
encrypted = HarmanSwfEncrypt.encrypt(is, hdr);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException ex) {
|
||||
return false;
|
||||
}
|
||||
os.write(encrypted);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts Harman AIR encryption
|
||||
*/
|
||||
public static boolean decrypt(InputStream is, OutputStream os) throws IOException {
|
||||
byte[] hdr = new byte[8];
|
||||
@@ -2019,11 +2047,10 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
|
||||
case 'c':
|
||||
case 'z':
|
||||
case 'f':
|
||||
HarmanDecryption dec = new HarmanDecryption();
|
||||
try {
|
||||
is = dec.decrypt(is, hdr); //Note: this call will uppercase hdr[0]
|
||||
byte[] decrypted = HarmanSwfEncrypt.decrypt(is, hdr); //Note: this call will uppercase hdr[0]
|
||||
os.write(hdr);
|
||||
Helper.copyStream(is, os);
|
||||
os.write(decrypted);
|
||||
return true;
|
||||
} catch (IOException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException ex) {
|
||||
throw new SwfOpenException(AppResources.translate("error.swf.decryptionProblem"));
|
||||
@@ -2091,14 +2118,16 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
|
||||
case 'c':
|
||||
case 'z':
|
||||
case 'f':
|
||||
header.encrypted = true;
|
||||
HarmanDecryption dec = new HarmanDecryption();
|
||||
header.encrypted = true;
|
||||
byte[] decrypted;
|
||||
try {
|
||||
is = dec.decrypt(is, hdr); //Note: this call will uppercase hdr[0]
|
||||
} catch (IOException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException ex) {
|
||||
decrypted = HarmanSwfEncrypt.decrypt(is, hdr);
|
||||
} catch (IOException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException ex) {
|
||||
throw new SwfOpenException(AppResources.translate("error.swf.decryptionProblem"));
|
||||
}
|
||||
}
|
||||
is = new ByteArrayInputStream(decrypted);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
switch (hdr[0]) {
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2023 JPEXS, All rights reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3.0 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library.
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.harman;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class HarmanBinaryDataEncrypt {
|
||||
|
||||
private static final String GLOBAL_KEY = "Adobe AIR SDK (c) 2021 HARMAN Internation Industries Incorporated";
|
||||
|
||||
private static final String[] hexStringCache;
|
||||
|
||||
static {
|
||||
hexStringCache = new String[256];
|
||||
for (int i = 0; i < hexStringCache.length; i++) {
|
||||
hexStringCache[i] = String.format("%02x", i);
|
||||
}
|
||||
}
|
||||
|
||||
public static String byteArrayToHex(byte[] data) {
|
||||
StringBuilder sb = new StringBuilder(data.length * 2);
|
||||
for (byte b : data) {
|
||||
sb.append(hexStringCache[b & 0xff]);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static byte[] encrypt(byte[] data) {
|
||||
byte[] result;
|
||||
try {
|
||||
SecureRandom random = new SecureRandom();
|
||||
int dataLen = data.length;
|
||||
int encryptedDataLen = (dataLen + 0x1F) & ~0x1F;
|
||||
int resultLen = 32 + encryptedDataLen;
|
||||
byte[] hashBytes = new byte[4];
|
||||
byte[] randomBytes1 = new byte[4];
|
||||
byte[] randomBytes2 = new byte[4];
|
||||
byte[] keyBytes = new byte[16];
|
||||
random.nextBytes(hashBytes);
|
||||
random.nextBytes(randomBytes1);
|
||||
random.nextBytes(randomBytes2);
|
||||
random.nextBytes(keyBytes);
|
||||
long random1 = unpack(randomBytes1, 0);
|
||||
long random2 = unpack(randomBytes2, 0);
|
||||
|
||||
|
||||
byte[] ivBytes = getIv(hashBytes, random1, random2);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
|
||||
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
|
||||
byte[] dataPadded = new byte[(int) encryptedDataLen];
|
||||
random.nextBytes(dataPadded);
|
||||
System.arraycopy(data, 0, dataPadded, 0, data.length);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
|
||||
byte[] encryptedBytes = cipher.doFinal(dataPadded);
|
||||
result = new byte[resultLen];
|
||||
long hash = unpack(hashBytes, 0);
|
||||
long resultLenXorHash = resultLen ^ hash;
|
||||
System.arraycopy(pack(resultLenXorHash), 0, result, 0, 4);
|
||||
long dataLenXorRandom1 = dataLen ^ random1;
|
||||
System.arraycopy(pack(dataLenXorRandom1), 0, result, 4, 4);
|
||||
System.arraycopy(randomBytes1, 0, result, 8, 4);
|
||||
System.arraycopy(randomBytes2, 0, result, 12, 4);
|
||||
|
||||
addBytes(keyBytes, 0, randomBytes2, 1);
|
||||
addBytes(keyBytes, 4, randomBytes2, -1);
|
||||
addBytes(keyBytes, 8, randomBytes2, 1);
|
||||
addBytes(keyBytes, 12, randomBytes2, -1);
|
||||
|
||||
System.arraycopy(keyBytes, 0, result, 16, 16);
|
||||
System.arraycopy(encryptedBytes, 0, result, 32, encryptedDataLen);
|
||||
} catch (UnsupportedEncodingException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException ex) {
|
||||
result = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static long unpack(byte[] data, int start) {
|
||||
return (data[start] & 0xff)
|
||||
+ ((long) (data[start + 1] & 0xff) << 8)
|
||||
+ ((long) (data[start + 2] & 0xff) << 16)
|
||||
+ ((long) (data[start + 3] & 0xff) << 24);
|
||||
}
|
||||
|
||||
private static void addBytes(byte[] toChangeBytes, int offset, byte[] data, int multiplier) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
toChangeBytes[offset + i] = (byte) (toChangeBytes[offset + i] + multiplier * data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] getIv(byte[] hashBytes, long random1, long random2) throws UnsupportedEncodingException {
|
||||
byte[] ivBytes = new byte[16];
|
||||
int offset = 0;
|
||||
|
||||
int offset2;
|
||||
for (offset2 = 0; offset2 < 4; offset2++) {
|
||||
offset += hashBytes[offset2] & 0xff;
|
||||
}
|
||||
|
||||
offset2 = offset % GLOBAL_KEY.length();
|
||||
String hashString = GLOBAL_KEY.substring(offset2) + GLOBAL_KEY.substring(0, offset2) + " EncryptSWF " + offset;
|
||||
byte[] hashStringBytes = hashString.getBytes("UTF-8");
|
||||
long hashCodeString = 0L;
|
||||
|
||||
for (int i = 0; i < hashStringBytes.length; ++i) {
|
||||
hashCodeString = 31 * hashCodeString + (hashStringBytes[i] & 0xff);
|
||||
}
|
||||
System.arraycopy(pack(hashCodeString), 0, ivBytes, 0, 4);
|
||||
System.arraycopy(hashBytes, 0, ivBytes, 4, 4);
|
||||
long hashSum = unpack(hashBytes, 0) + hashCodeString;
|
||||
long sumXorRandom1 = random1 ^ hashSum;
|
||||
long sumXorRandom2 = random2 ^ hashSum;
|
||||
System.arraycopy(pack(sumXorRandom1), 0, ivBytes, 8, 4);
|
||||
System.arraycopy(pack(sumXorRandom2), 0, ivBytes, 12, 4);
|
||||
return ivBytes;
|
||||
}
|
||||
|
||||
private static byte[] pack(long value) {
|
||||
byte[] ret = new byte[4];
|
||||
ret[0] = (byte) (value & 0xff);
|
||||
ret[1] = (byte) ((value >> 8) & 0xff);
|
||||
ret[2] = (byte) ((value >> 16) & 0xff);
|
||||
ret[3] = (byte) ((value >> 24) & 0xff);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static byte[] decrypt(byte[] data) {
|
||||
long encryptedLen = data.length;
|
||||
long encryptedLenXorHash = unpack(data, 0);
|
||||
long decryptedLenXorRandom1 = unpack(data, 4);
|
||||
|
||||
long random1 = unpack(data, 8);
|
||||
long random2 = unpack(data, 12);
|
||||
byte[] random2Bytes = Arrays.copyOfRange(data, 12, 16);
|
||||
byte[] keyBytes = Arrays.copyOfRange(data, 16, 32);
|
||||
byte[] encryptedBytes = Arrays.copyOfRange(data, 32, data.length);
|
||||
|
||||
long decryptedLen = decryptedLenXorRandom1 ^ random1;
|
||||
long hash = encryptedLen ^ encryptedLenXorHash;
|
||||
byte[] hashBytes = pack(hash);
|
||||
addBytes(keyBytes, 0, random2Bytes, -1);
|
||||
addBytes(keyBytes, 4, random2Bytes, 1);
|
||||
addBytes(keyBytes, 8, random2Bytes, -1);
|
||||
addBytes(keyBytes, 12, random2Bytes, 1);
|
||||
|
||||
try {
|
||||
byte[] ivBytes = getIv(hashBytes, random1, random2);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
|
||||
|
||||
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
|
||||
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
|
||||
byte[] ret = new byte[(int) decryptedLen];
|
||||
System.arraycopy(decryptedBytes, 0, ret, 0, (int) decryptedLen);
|
||||
return ret;
|
||||
} catch (UnsupportedEncodingException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
byte[] data = new byte[] {'A', 'B', 'C'};
|
||||
byte[] encrypted = encrypt(data);
|
||||
byte[] decrypted = decrypt(encrypted);
|
||||
if (!Arrays.equals(data, decrypted)) {
|
||||
throw new RuntimeException("Cannot encrypt/decrypt");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2023 JPEXS, All rights reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3.0 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library.
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.harman;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*
|
||||
* Based on swfdecrypt.py by NathaanTFM
|
||||
*/
|
||||
public class HarmanSwfEncrypt {
|
||||
|
||||
private static final String GLOBAL_KEY = "Adobe AIR SDK (c) 2021 HARMAN Internation Industries Incorporated";
|
||||
|
||||
|
||||
private static final String[] hexStringCache;
|
||||
|
||||
static {
|
||||
hexStringCache = new String[256];
|
||||
for (int i = 0; i < hexStringCache.length; i++) {
|
||||
hexStringCache[i] = String.format("%02x", i);
|
||||
}
|
||||
}
|
||||
|
||||
public static String byteArrayToHex(byte[] data) {
|
||||
StringBuilder sb = new StringBuilder(data.length * 2);
|
||||
for (byte b : data) {
|
||||
sb.append(hexStringCache[b & 0xff]);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static int sum(byte[] data) {
|
||||
int s = 0;
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
s += data[i] & 0xff;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
private static long getkey(byte[] data) {
|
||||
int dsum = sum(data);
|
||||
int dmod = dsum % GLOBAL_KEY.length();
|
||||
String s = GLOBAL_KEY.substring(dmod) + GLOBAL_KEY.substring(0, dmod);
|
||||
s += " EncryptSWF ";
|
||||
s += "" + dsum;
|
||||
|
||||
long ret = 0;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
int code = s.charAt(i);
|
||||
ret *= 31;
|
||||
ret += code;
|
||||
}
|
||||
|
||||
return ret & 0xffffffffL;
|
||||
}
|
||||
|
||||
private static long unpack(byte[] data, int start) {
|
||||
return (data[start] & 0xff)
|
||||
+ ((long) (data[start + 1] & 0xff) << 8)
|
||||
+ ((long) (data[start + 2] & 0xff) << 16)
|
||||
+ ((long) (data[start + 3] & 0xff) << 24);
|
||||
}
|
||||
|
||||
private static byte[] pack(long value) {
|
||||
return new byte[]{
|
||||
(byte) (value & 0xff),
|
||||
(byte) ((value >> 8) & 0xff),
|
||||
(byte) ((value >> 16) & 0xff),
|
||||
(byte) ((value >> 24) & 0xff)
|
||||
};
|
||||
}
|
||||
|
||||
private static byte[] readStream(InputStream is) throws IOException {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[1024]; // velikost bufferu, můžeš ji upravit podle potřeby
|
||||
|
||||
int bytesRead;
|
||||
while ((bytesRead = is.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
|
||||
public static byte[] encrypt(byte[] data) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
|
||||
return encrypt(new ByteArrayInputStream(data));
|
||||
}
|
||||
|
||||
public static byte[] encrypt(InputStream is) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
|
||||
byte[] header = new byte[8];
|
||||
DataInputStream dais = new DataInputStream(is);
|
||||
dais.readFully(header);
|
||||
return encrypt(is, header);
|
||||
}
|
||||
|
||||
public static byte[] encrypt(InputStream is, byte[] header) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
long key = getkey(header);
|
||||
|
||||
byte[] data = readStream(is);
|
||||
|
||||
int decryptedLength = data.length;
|
||||
long encryptedLength = decryptedLength ^ key;
|
||||
byte[] encryptedLengthBytes = pack(encryptedLength);
|
||||
|
||||
|
||||
int paddedLength = (int) (decryptedLength + 0x1F) & ~0x1F;
|
||||
|
||||
byte[] dataPadded = new byte[paddedLength];
|
||||
System.arraycopy(data, 0, dataPadded, 0, data.length);
|
||||
|
||||
byte[] aesIV = new byte[16];
|
||||
System.arraycopy(header, 0, aesIV, 0, header.length); //header
|
||||
System.arraycopy(encryptedLengthBytes, 0, aesIV, 8, 4); //encrypted length
|
||||
aesIV[12] = (byte) (key & 0xff);
|
||||
aesIV[13] = (byte) ((key >> 8) & 0xff);
|
||||
aesIV[14] = (byte) ((key >> 16) & 0xff);
|
||||
aesIV[15] = (byte) ((key >> 24) & 0xff);
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
aesIV[i] ^= GLOBAL_KEY.charAt(i);
|
||||
}
|
||||
|
||||
byte[] aesKey = new byte[32];
|
||||
|
||||
SecureRandom random = new SecureRandom();
|
||||
random.nextBytes(aesKey);
|
||||
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(aesKey, "AES");
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, new IvParameterSpec(aesIV));
|
||||
byte[] encryptedData = cipher.doFinal(dataPadded);
|
||||
header[0] += 32; //to lowercase
|
||||
|
||||
baos.write(header);
|
||||
baos.write(pack(encryptedLength));
|
||||
baos.write(encryptedData, 0, paddedLength);
|
||||
|
||||
byte[] aesKeyData = new byte[32];
|
||||
for (int i = 0; i < 32; i += 4) {
|
||||
long value = unpack(aesKey, i);
|
||||
if ((i & 4) == 4) {
|
||||
value += key;
|
||||
} else {
|
||||
value -= key;
|
||||
}
|
||||
aesKeyData[i] = (byte) (value & 0xff);
|
||||
aesKeyData[i + 1] = (byte) ((value >> 8) & 0xff);
|
||||
aesKeyData[i + 2] = (byte) ((value >> 16) & 0xff);
|
||||
aesKeyData[i + 3] = (byte) ((value >> 24) & 0xff);
|
||||
}
|
||||
|
||||
baos.write(aesKeyData);
|
||||
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
public static byte[] decrypt(byte[] data) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
|
||||
return decrypt(new ByteArrayInputStream(data));
|
||||
}
|
||||
|
||||
public static byte[] decrypt(InputStream is) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
|
||||
DataInputStream dais = new DataInputStream(is);
|
||||
byte[] header = new byte[8];
|
||||
dais.readFully(header);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
byte[] dec = decrypt(is, header);
|
||||
baos.write(header);
|
||||
baos.write(dec);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
public static byte[] decrypt(InputStream is, byte[] header) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
|
||||
header[0] -= 32; // to uppercase
|
||||
|
||||
long key = getkey(header);
|
||||
|
||||
DataInputStream dais = new DataInputStream(is);
|
||||
|
||||
byte[] encryptedLengthBytes = new byte[4];
|
||||
dais.readFully(encryptedLengthBytes);
|
||||
long encryptedLength = unpack(encryptedLengthBytes, 0);
|
||||
int decryptedLength = (int) (encryptedLength ^ key);
|
||||
|
||||
int paddedLength = (int) (decryptedLength + 0x1F) & ~0x1F;
|
||||
|
||||
byte[] aesIV = new byte[16];
|
||||
System.arraycopy(header, 0, aesIV, 0, header.length); //header
|
||||
System.arraycopy(encryptedLengthBytes, 0, aesIV, 8, 4); //encrypted length
|
||||
aesIV[12] = (byte) (key & 0xff);
|
||||
aesIV[13] = (byte) ((key >> 8) & 0xff);
|
||||
aesIV[14] = (byte) ((key >> 16) & 0xff);
|
||||
aesIV[15] = (byte) ((key >> 24) & 0xff);
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
aesIV[i] ^= GLOBAL_KEY.charAt(i);
|
||||
}
|
||||
|
||||
// aes key
|
||||
// this one is stored at the end of the file
|
||||
byte[] aesKey = new byte[32];
|
||||
byte[] data = new byte[paddedLength];
|
||||
dais.readFully(data);
|
||||
|
||||
byte[] aesKeyData = new byte[32];
|
||||
dais.readFully(aesKeyData);
|
||||
|
||||
for (int i = 0; i < 32; i += 4) {
|
||||
long value = unpack(aesKeyData, i);
|
||||
if ((i & 4) == 4) {
|
||||
value -= key;
|
||||
} else {
|
||||
value += key;
|
||||
}
|
||||
aesKey[i] = (byte) (value & 0xff);
|
||||
aesKey[i + 1] = (byte) ((value >> 8) & 0xff);
|
||||
aesKey[i + 2] = (byte) ((value >> 16) & 0xff);
|
||||
aesKey[i + 3] = (byte) ((value >> 24) & 0xff);
|
||||
}
|
||||
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(aesKey, "AES");
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(aesIV));
|
||||
|
||||
byte[] decryptedData = cipher.doFinal(data);
|
||||
return Arrays.copyOfRange(decryptedData, 0, decryptedLength);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
byte[] data = new byte[] {'C', 'W', 'S', 0x32, 0x01, 0x02, 0x03, 0x04, 0x41, 0x42, 0x43, 0xd, 0xa};
|
||||
byte[] encrypted = encrypt(data);
|
||||
byte[] decrypted = decrypt(encrypted);
|
||||
|
||||
if (!Arrays.equals(data, decrypted)) {
|
||||
throw new RuntimeException("Cannot encrypt/decrypt");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user