From 4d6dda002e653f91a5b20abde4f3c406d3f3fdae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Mon, 26 Oct 2015 20:41:47 +0100 Subject: [PATCH] Check / Set passwords for password tags --- .../flash/tags/EnableDebugger2Tag.java | 18 +- .../flash/tags/EnableDebuggerTag.java | 20 +- .../decompiler/flash/tags/ProtectTag.java | 18 +- .../flash/tags/base/PasswordTag.java | 29 +++ .../flash/types/annotations/Password.java | 33 +++ .../src/com/jpexs/helpers/MD5Crypt.java | 246 ++++++++++++++++++ 6 files changed, 359 insertions(+), 5 deletions(-) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PasswordTag.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/annotations/Password.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/helpers/MD5Crypt.java diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/EnableDebugger2Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/EnableDebugger2Tag.java index 564f46f34..db8e9b36b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/EnableDebugger2Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/EnableDebugger2Tag.java @@ -19,10 +19,13 @@ package com.jpexs.decompiler.flash.tags; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.tags.base.PasswordTag; import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.Password; import com.jpexs.decompiler.flash.types.annotations.Reserved; import com.jpexs.decompiler.flash.types.annotations.SWFType; import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.MD5Crypt; import java.io.IOException; /** @@ -30,7 +33,7 @@ import java.io.IOException; * * @author JPEXS */ -public class EnableDebugger2Tag extends Tag { +public class EnableDebugger2Tag extends Tag implements PasswordTag { public static final int ID = 64; @@ -43,6 +46,7 @@ public class EnableDebugger2Tag extends Tag { /** * MD5 hash of password */ + @Password public String passwordHash; /** @@ -52,7 +56,7 @@ public class EnableDebugger2Tag extends Tag { */ public EnableDebugger2Tag(SWF swf) { super(swf, ID, NAME, null); - passwordHash = "PasswordHash"; + setPassword(""); } /** @@ -84,4 +88,14 @@ public class EnableDebugger2Tag extends Tag { sos.writeUI16(reserved); sos.writeString(passwordHash); } + + @Override + public void setPassword(String password) { + this.passwordHash = MD5Crypt.crypt(password, 2); + } + + @Override + public boolean hasPassword(String password) { + return this.passwordHash.equals(MD5Crypt.crypt(password, 2)); + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/EnableDebuggerTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/EnableDebuggerTag.java index f7166822b..68fc5541d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/EnableDebuggerTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/EnableDebuggerTag.java @@ -19,7 +19,10 @@ package com.jpexs.decompiler.flash.tags; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.tags.base.PasswordTag; +import com.jpexs.decompiler.flash.types.annotations.Password; import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.MD5Crypt; import java.io.IOException; /** @@ -27,7 +30,7 @@ import java.io.IOException; * * @author JPEXS */ -public class EnableDebuggerTag extends Tag { +public class EnableDebuggerTag extends Tag implements PasswordTag { public static final int ID = 58; @@ -36,6 +39,7 @@ public class EnableDebuggerTag extends Tag { /** * MD5 hash of password */ + @Password public String passwordHash; /** @@ -45,7 +49,7 @@ public class EnableDebuggerTag extends Tag { */ public EnableDebuggerTag(SWF swf) { super(swf, ID, NAME, null); - passwordHash = "PasswordHash"; + setPassword(""); } /** @@ -77,4 +81,16 @@ public class EnableDebuggerTag extends Tag { sos.writeString(passwordHash); } } + + @Override + public void setPassword(String password) { + //Assuming EnableDebugger tag has same hash type as EnableDebugger2 + this.passwordHash = MD5Crypt.crypt(password, 2); + } + + @Override + public boolean hasPassword(String password) { + return this.passwordHash.equals(MD5Crypt.crypt(password, 2)); + } + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ProtectTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ProtectTag.java index e40c3aba2..791caeb8d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ProtectTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ProtectTag.java @@ -19,7 +19,10 @@ package com.jpexs.decompiler.flash.tags; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.tags.base.PasswordTag; +import com.jpexs.decompiler.flash.types.annotations.Password; import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.MD5Crypt; import java.io.IOException; /** @@ -27,7 +30,7 @@ import java.io.IOException; * * @author JPEXS */ -public class ProtectTag extends Tag { +public class ProtectTag extends Tag implements PasswordTag { public static final int ID = 24; @@ -36,6 +39,7 @@ public class ProtectTag extends Tag { /** * MD5 hash of password */ + @Password public String passwordHash; /** @@ -81,4 +85,16 @@ public class ProtectTag extends Tag { sos.writeString(passwordHash); } } + + @Override + public void setPassword(String password) { + //Assuming Protect tag has same hash type as EnableDebugger2 + this.passwordHash = MD5Crypt.crypt(password, 2); + } + + @Override + public boolean hasPassword(String password) { + return this.passwordHash.equals(MD5Crypt.crypt(password, 2)); + } + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PasswordTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PasswordTag.java new file mode 100644 index 000000000..74c689b29 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PasswordTag.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2010-2015 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.tags.base; + +/** + * + * @author JPEXS + */ +public interface PasswordTag { + + public void setPassword(String password); + + public boolean hasPassword(String password); + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/annotations/Password.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/annotations/Password.java new file mode 100644 index 000000000..2a95bf119 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/annotations/Password.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2010-2015 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.types.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Password field - it has MD5 crypted password. + * + * TODO: use this in GUI tag editor to set passwords + * + * @author JPEXS + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Password { +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/MD5Crypt.java b/libsrc/ffdec_lib/src/com/jpexs/helpers/MD5Crypt.java new file mode 100644 index 000000000..a4371451b --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/MD5Crypt.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2010-2015 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.helpers; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Random; + +/** + * MD5 crypt - based on passlib.hash.md5_crypt + * + * @author JPEXS + */ +public class MD5Crypt { + + private static final String SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + + private static final String HASH64_CHARS = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + public static final String MAGIC = "$1$"; + public static final String MAGIC_APACHE = "$apr1$"; + + public static boolean checkPassword(String password, String hash) { + String magic; + + if (hash.startsWith(MAGIC)) { + magic = MAGIC; + } else if (hash.startsWith(MAGIC_APACHE)) { + magic = MAGIC_APACHE; + } else { + return false; + } + + String checksum = hash.substring(magic.length()); + String salt = ""; + if (checksum.contains("$")) { + salt = checksum.substring(0, checksum.indexOf("$")); + } + return hash.equals(crypt(password, salt, magic)); + } + + public static String generateSalt(int length) { + Random rnd = new Random(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + sb.append(SALT_CHARS.charAt(rnd.nextInt(SALT_CHARS.length()))); + } + return sb.toString(); + } + + public static String crypt(String password, int saltLength, String magic) { + return crypt(password, generateSalt(saltLength), magic); + } + + public static String crypt(String password, int saltLength) { + return crypt(password, generateSalt(saltLength), MAGIC); + } + + public static String cryptApache(String password, int saltLength) { + return crypt(password, generateSalt(saltLength), MAGIC_APACHE); + } + + public static String crypt(String password, String salt) { + return crypt(password, salt, MAGIC); + } + + public static String cryptApache(String password, String salt) { + return crypt(password, salt, MAGIC_APACHE); + } + + private static String crypt(String password, String salt, String magic) { + + if (salt.startsWith(magic)) { + salt = salt.substring(magic.length()); + } + if (salt.length() > 8) { + salt = salt.substring(0, 8); + } + + byte[] passwordBytes = new byte[0]; + byte[] constBytes = new byte[0]; + byte[] saltBytes = new byte[0]; + try { + passwordBytes = password.getBytes("UTF-8"); + saltBytes = salt.getBytes("UTF-8"); + constBytes = magic.getBytes("UTF-8"); + } catch (UnsupportedEncodingException ex) { + //ignore + } + + MessageDigest b; + try { + b = MessageDigest.getInstance("MD5"); //Start MD5 digest B + } catch (NoSuchAlgorithmException ex) { + return null; + } + b.update(passwordBytes); //Add the password to digest B + b.update(saltBytes);//Add the salt to digest B + b.update(passwordBytes); //Add the password to digest B + byte[] digest_b = b.digest(); //Finish MD5 digest B + + MessageDigest a; + try { + a = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException ex) { + return null; + } + a.update(passwordBytes); //Add the password to digest A + a.update(constBytes); //Add the constant string $1$ to digest A + a.update(saltBytes); //Add the salt to digest A + + //For each block of 16 bytes in the password string, add digest B to digest A + for (int i = passwordBytes.length; i > 0; i -= 16) { + if (i >= 16) { + a.update(digest_b); + } else { //For the remaining N bytes of the password string, add the first N bytes of digest B to digest A + a.update(digest_b, 0, i); + } + } + + /* + For each bit in the binary representation of the length of the password string; starting with the lowest value bit, up to and including the largest-valued bit that is set to 1: + + If the current bit is set 0 (!! not 1), add the first character of the password to digest A. + Otherwise, add a NULL character to digest A. + + (If the password is the empty string, step 14 is omitted entirely). + */ + for (int i = passwordBytes.length; i > 0; i = i >>> 1) { + if ((i & 1) == 0) { + a.update(passwordBytes, 0, 1); + } else { + a.update((byte) 0); + } + } + + byte[] digest_a = a.digest(); //Finish MD5 digest A + byte[] round_result = null; + + //For 1000 rounds (round values 0..999 inclusive) + for (int round = 0; round < 1000; round++) { + MessageDigest c; + try { + c = MessageDigest.getInstance("MD5"); //Start MD5 digest C + } catch (NoSuchAlgorithmException ex) { + return null; + } + + //If the round is odd, add the password to digest C + if (round % 2 == 1) { + c.update(passwordBytes); + } + + //If the round is even, add the previous round’s result to digest C (for round 0, add digest A instead). + if (round % 2 == 0) { + if (round == 0) { + c.update(digest_a); + } else { + c.update(round_result); + } + } + //If the round is not a multiple of 3, add the salt to digest C. + if (round % 3 != 0) { + c.update(saltBytes); + } + + //If the round is not a multiple of 7, add the password to digest C. + if (round % 7 != 0) { + c.update(passwordBytes); + } + + //If the round is even, add the password to digest C. + if (round % 2 == 0) { + c.update(passwordBytes); + } + + //If the round is odd, add the previous round’s result to digest C (for round 0, add digest A instead). + if (round % 2 == 1) { + if (round == 0) { + c.update(digest_a); + } else { + c.update(round_result); + } + } + + //Use the final value of MD5 digest C as the result for this round. + round_result = c.digest(); + } + + //Transpose the 16 bytes of the final round’s result in the following order: + //12,6,0,13,7,1,14,8,2,15,9,3,5,10,4,11 + byte[] transposed = new byte[]{ + round_result[12], + round_result[6], + round_result[0], + round_result[13], + round_result[7], + round_result[1], + round_result[14], + round_result[8], + round_result[2], + round_result[15], + round_result[9], + round_result[3], + round_result[5], + round_result[10], + round_result[4], + round_result[11]}; + + //Encode the resulting 16 byte string into a 22 character hash64-encoded string + //(the 2 msb bits encoded by the last hash64 character are used as 0 padding). + StringBuilder result = new StringBuilder(); + + int dstCharRem = 22; + for (int srcBytePos = 0; srcBytePos < transposed.length; srcBytePos += 3, dstCharRem -= 4) { + long v = (transposed[srcBytePos] & 0xff); + if (srcBytePos + 1 < transposed.length) { + v |= ((transposed[srcBytePos + 1] & 0xff) << 8); + } + if (srcBytePos + 2 < transposed.length) { + v |= ((transposed[srcBytePos + 2] & 0xff) << 16); + } + for (int j = 0; j < (dstCharRem >= 4 ? 4 : dstCharRem); j++) { + result.append(HASH64_CHARS.charAt((int) (v & 0x3f))); + v >>>= 6; + } + } + return magic + salt + "$" + result.toString(); + } + +}