Yury Bendersky et al.
Replies
0
Voices
1

Elusive Password

If I’m going to protect my code or data with encryption, I need a cryptographic algorithm and a secret key or Password. I have no problems with the cryptographic algorithm – I know that it is reliably protected from hacking by itself. What to do with the Password? It would be possible to transfer it to the user separately so that he would enter it every time before starting the protected program, but this cannot be called friendly. Attempts to hide it with encryption or on the server lead to a vicious circle.

I came up with a method that seems me to satisfactorily solve this problem [1]. The code Original.java and its method getPassword(byte[]) are based on two principles. The first is a strictly proven mathematical fact about the impossibility of exact decompilation, i.e. any decompiler produces code that does not match the original. Moreover, for any decompiler, there is code that cannot be decompiled at all [5]. The second principle is that any change in the runtime environment, in our case, the discrepancy between the source and decompiled codes can be detected and protective measures can be taken. In physics, for example, a similar phenomenon is called the observer effect [2], [3].

Sources
      final public class Original {
         private void definePackage(StringBuffer buffer) {
            newInstance(buffer);
         }
         public void getPassword(byte[] key) {
            StringBuffer buffer = new StringBuffer();
            getPermissions(buffer); findResource(buffer); getURLs(buffer);
            defineClass(buffer); isSealed(buffer); definePackage(buffer);
            findClass(buffer); findClass(buffer); defineClass(buffer);
            getPermissions(buffer); definePackage(buffer); defineClass(buffer);
            isSealed(buffer); getURLs(buffer); findResource(buffer);
            String str = buffer.toString();
            buffer = null;
            byte[] temp = str.getBytes();
            byte[] array = new byte[str.length() / 3];
            for (int i = 0; i < array.length; i++) {
               try {
                  array[i] = Integer.valueOf(str.substring(3 * i, 3 * i + 3)).byteValue();
               } catch (Exception e) {
                  array[i] = (byte) (temp[3 * i] ^ temp[3 * i + 1] ^ temp[3 * i + 2]);
               }
            }
            ...
            System.out.println("Original:  " + bytesToHex(key));
         }
         ...
      }
Original.class decompiled by the best decompilers [4]
by AndroChef
      public final class AndroChef {
         private void definePackage(StringBuffer buffer) {
            this.newInstance(buffer);
         }
         public void getPassword(byte[] key) {
            StringBuffer buffer = new StringBuffer();
            this.getPermissions(buffer);
            ... 
            String str = buffer.toString();
            buffer = null;
            byte[] temp = str.getBytes();
            byte[] array = new byte[str.length() / 3];
            for (int bytes = 0; bytes < array.length; ++bytes) {
               try {
                  array[bytes] = Integer.valueOf(str.substring(3 * bytes,
3 * bytes + 3)).byteValue();
               } catch (Exception var9) {
                  array[bytes] = (byte) (temp[3 * bytes] ^ temp[3 * bytes + 1]
^ temp[3 * bytes + 2]);
               }
            }
            ...
            System.out.println("AndroChef: " + this.bytesToHex(key));
         }
         ...
      }
by Cavaj
      public final class Cavaj {
         private void definePackage(StringBuffer buffer) {
            newInstance(buffer);
         }
         public void getPassword(byte key[]) {
            StringBuffer buffer = new StringBuffer();
            getPermissions(buffer);
            findResource(buffer);
            ...
            findResource(buffer);
            String str = buffer.toString();
            buffer = null;
            byte temp[] = str.getBytes();
            byte array[] = new byte[str.length() / 3];
            for (int i = 0; i < array.length; i++) {
               try {
                  array[i] = Integer.valueOf(str.substring(3 * i, 3 * i + 3)).byteValue();
               } catch (Exception e) {
                  array[i] = (byte) (temp[3 * i] ^ temp[3 * i + 1] ^ temp[3 * i + 2]);
               }
            }
            ...
            System.out.println("Cavaj: " + bytesToHex(key));
         }
         ...
      }
by CFRDecompiler
      public final class CFRDecompiler {
         ...
         public void getPassword(byte[] key) {
            StringBuffer buffer = new StringBuffer();
            this.getPermissions(buffer);
            ...
            String str = buffer.toString();
            buffer = null;
            byte[] temp = str.getBytes();
            byte[] array = new byte[str.length() / 3];
            for (int i = 0; i < array.length; ++i) {
               try {
                  array[i] = Integer.valueOf(str.substring(3 * i, 3 * i + 3)).byteValue();
                  continue;
               } catch (Exception e) {
                  array[i] = (byte) (temp[3 * i] ^ temp[3 * i + 1] ^ temp[3 * i + 2]);
               }
            }
            ...
            System.out.println("CFR:       " + this.bytesToHex(bytes));
         }
         ...
      }
by DJ_JavaDecompiler
      public final class DJ_JavaDecompiler {
         private void definePackage(StringBuffer buffer) {
            newInstance(buffer);
         }
         public void getPassword(byte key[]) {
            StringBuffer buffer = new StringBuffer();
            ...
            findClass(buffer);
            String str = buffer.toString();
            buffer = null;
            byte temp[] = str.getBytes();
            byte array[] = new byte[str.length() / 3];
            for (int i = 0; i < array.length; i++)
               hash(array);
            }
            byte bytes[] = new byte[16];
            System.out.println("DJ_DEC: " + bytesToHex(key));
         }
         private void findResource(StringBuffer buffer) {
            newInstance(buffer);
         }
         private void defineClass(StringBuffer buffer) {
            newInstance(buffer);
         }
         ...
      }
by FernFlower
      public final class FernFlower {
         private void definePackage(StringBuffer buffer) {
            this.newInstance(buffer);
         }
         public void getPassword(byte[] key) {
            StringBuffer buffer = new StringBuffer();
            this.getPermissions(buffer);
            this.findResource(buffer);
            this.getURLs(buffer);
            this.defineClass(buffer);
            this.isSealed(buffer);
            ...
            this.getURLs(buffer);
            this.findResource(buffer);
            String str = buffer.toString();
            buffer = null;
            byte[] temp = str.getBytes();
            byte[] array = new byte[str.length() / 3];
            for (int i = 0; i < array.length; ++i) {
               try {
                  array[i] = Integer.valueOf(str.substring(3 * i, 3 * i + 3)).byteValue();
               } catch (Exception var9) {
                  array[i] = (byte) (temp[3 * i] ^ temp[3 * i + 1] ^ temp[3 * i + 2]);
               }
            }
            ...
            System.out.println("FernFlower: " + bytesToHex(key));
         }
         ...
      }
by JD_GUI
      public final class JD_GUI {
         private void definePackage(StringBuffer buffer) {
            newInstance(buffer);
         }
         public void getPassword(byte[] key) {
            StringBuffer buffer = new StringBuffer();
            ...
            String str = buffer.toString();
            System.out.println("JD-GUI: " + bytesToHex(key));
         }
         private void getURLs(StringBuffer buffer) {
            newInstance(buffer);
         }
      }

All these decompiled codes work without errors and are very close to the original, but differ from it in about 200 places with the length of the original code about 400 lines. This is enough for getPassword(…) method to detect these differences and produce results that have nothing to do with the original password. Below is the testing program and the results of its work.
      public class TestDecompilers {
         public static void main(String[] args) {
            byte[] secretKeySpec = "abcdefghijklmnopqrstuvwxyz".getBytes();
            new Original().getPassword(secretKeySpec);
            secretKeySpec = "abcdefghijklmnopqrstuvwxyz".getBytes();
            new AndroChef().getPassword(secretKeySpec);
            secretKeySpec = "abcdefghijklmnopqrstuvwxyz".getBytes();
            new Cavaj().getPassword(secretKeySpec);
            secretKeySpec = "abcdefghijklmnopqrstuvwxyz".getBytes();
            new CFRDecompiler().getPassword(secretKeySpec);
            secretKeySpec = "abcdefghijklmnopqrstuvwxyz".getBytes();
            new DJ_JavaDecompiler().getPassword(secretKeySpec);
            secretKeySpec = "abcdefghijklmnopqrstuvwxyz".getBytes();
            new FernFlow().getPassword(secretKeySpec);
            secretKeySpec = "abcdefghijklmnopqrstuvwxyz".getBytes();
            new JD_GUI().getPassword(secretKeySpec);
         }
      }

All these classes you can download from https://www.bisguard.com/downloads/test_passwords.jar
to test results as well as to try one more decompiler.
Results

Original:    0d495a9e3f49a44e2a3cc7f4d4a5dff2
AndroChef:   682d23d2e196bad1d8834525112e588b

Cavaj:       e27877cf1a707cc1d2a59e4b47c98d10
CFR:         682d23d2e196bad1d8834525112e588b
DJ_DEC:      fbb9b671169ee842722bdb86d5a5e8f2
FernFlower:  682d23d2e196bad1d8834525112e588b
JD-GUI:      939e1a5ba653090c9624d7e0fd91402b

  1. Yury Bendersky, Java patent #150933, 2002
  2. Werner Heisenberg, Observer effect (Wikipedia)
  3. Werner Heisenberg, Uncertainty principle (Wikipedia)
  4. https://javahungry.blogspot.com/2018/12/8-best-java-decompiler-in-2019.html
  5. Strict decompiling/translation is impossible, 1993 [Breu92Breu93]

Leave a comment