android proguard ile kod koruma

Merhaba, bu yazıda Android içerisinde de kullanılan ve Java için kod koruma ve optimizasyonu konularında sıklıkla kullanılan bir kütüphane olan ProGuard’ın nasıl çalıştığını ve kodlarımızı korumak için neler yapmamız gerektiğini inceleyeceğiz.

ProGuard, kodları karıştırıyor, kullanılmayan sınıfları, fonksiyonları vb. otomatik olarak algılayıp siliyor, Java bytecode’u optimize ediyor, uygulama boyutunun düşmesini ve uygulamanın görece daha hızlı çalışmasını sağlıyor.

Android Studio ile birlikte ProGuard ayarlarını yapmak çok basitleştirildi. Öncelikle modül (genelde app olur) içerisindeki build.gradle dosyasını açıyoruz. Aşağıdaki gibi bazı satırlar gözünüze çarpacaktır.

apply plugin: 'com.android.application'

android {
    compileSdkVersion 16
    buildToolsVersion "23.0.0"
    defaultConfig {
        applicationId "com.moonbridge.proguardexample"
    }
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    productFlavors {
        flavor1 {
            proguardFile 'flavor1-specific-rules.pro'
        }
        flavor2 {
            proguardFile 'flavor2-specific-rules.pro'
        }
    }
}

dependencies {
    compile 'com.android.support:support-v4:18.0.0'
}

Şimdi yukarıdaki dosyada bizi ilgilendiren kısımlardan ilki buildTypes altındaki minifyEnabled seçeneği, bu seçeneğin true olması durumunda Android Studio ProGuard’ı devreye alıyor. Hemen bir alt satırda ProGuard’a özel proguard-android.txt dosyası var. Bunu da otomatik oluşturuyor. Sonraki proguard-rules.pro dosyasını ise yine aynı modülde bu üzerinde çalıştığımız build.gradle dosyasının bulunduğu yere yeni bir dosya oluşturuyoruz ve ismini proguard-rules.pro olarak veriyoruz.

Bu dosya içerisine istersek karıştırılmasını istemediğimiz sınıfları tanımlayabiliriz. Biz herhangi bir tanımlama yapmadan otomatik olarak tüm sınıflarımızı ve kodlarımızı karıştırmasını istiyoruz onun için dosyayı oluşturup boş bırakıyoruz.

Alt kısımdaki productFlavors bölümünde ise uygulamanız için birden fazla flavor (free-trial-full gibi) tanımlamışsanız ve bunlar için özel ayarlarınız varsa onu tanımlayabiliyorsunuz.

https://github.com/berkanuslu/ProGuardExample

Yukarıdaki adresten örnek uygulamaya ulaşabilirsiniz. Uygulamada basit olarak 3 farklı String değeri oluşturuldu. Bunlar aşağıdaki gibidir.

public String testString1 = "testString1";
public static String testString2 = "testString2";
public static final String testString3 = "testString3";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Log.d("PROGUARD",
            "Normal String: " +testString1 +
                    " - Static String: " + testString2 +
                    " - Static Final String: " + testString3);
}

Yukarıda da görüldüğü gibi 3 farklı String değişken tanımladık. Bunlardan public String ve public static String ile tanımladıklarımız dex2jar ile başkası tarafından açıldığında aynı şekilde görüntülenecektir. Ancak public static final String ifadesi ile tanımlanan sabit (constant) değeri proguard tarafından tüm kod içerisinde kullanılan tüm alanlara yapıştırılır. Ancak ProGuard herhangi bir şekilde String ifadeleri obfuscate (karıştırma) işlemini yapmaz (Bkz. http://proguard.sourceforge.net/FAQ.html#encrypt), bunun için Gradle üzerinde bazı işlemler yaparak String ifadeleri de obfuscate edebiliriz.

Bu örnek uygulamayla birlikte Build->Generate Signed APK yolunu izleyerek uygulamamızın release modunda imzalanmasını sağlıyoruz. Burada ProGuard otomatik çalışarak işlemleri arka planda gerçekleştiriyor. Sonuçta proje dosyalarında signedAPK/app-release.apk isminde bir dosyamız oluşuyor. Bu dosyayı dex2jar ile açmaya çalıştırıp nasıl bir sonuç döndüreceğine bakalım.

http://sourceforge.net/projects/dex2jar/files/

Yukarıdaki adresten dex2jar-2.0.zip dosyasını indirip zip dosyasını açıyoruz. Bundan sonrası için Mac Terminal’i açıyoruz ve zip dosyasını açtığımız konuma gidiyoruz. Ayrıca kolaylık olması açısından açılan dex2jar-2.0 klasörünün içerisine oluşturduğumuz .apk uzantılı dosyayı da kopyalıyoruz.

sudo chmod +x d2j_invoke.sh //dosyaya çalıştırılabilir izni veriyoruz. Bu işlemi bir kere yapmamız yeterlidir.
sh ./d2j-dex2jar.sh ./app-release.apk

Bu komutları çalıştırdıktan sonra app-release-dex2jar.jar isminde bir dosyamız oluşuyor. Bu dosyayı http://jd.benow.ca/ adresindeki JD-GUI uygulaması ile açarsanız karşımıza aşağıdaki gibi ProGuard işleminden geçirilmiş kodlar çıkmaktadır.

Bu şekilde ProGuard ile kodlarımızı optimize edip koruma altına alabiliriz. String ifadeleri de obfuscate etmek için aşağıdaki utils.gradle dosyasını projemizin içerisine ekliyoruz. gradle.properties dosyası içerisine aşağıdaki gibi bir SECRET_KEY ifadesi tanımlıyoruz.

SECRET_KEY=ab76239ef64454356754239ef6210898

Daha sonra modülümüzün içerisindeki build.gradle dosyası içerisinde öncelikle utils.gradle dosyamızı çağırıyoruz ve defaultConfig alanında yeni bir BuildConfig tanımlaması yapıyoruz.

apply plugin: 'com.android.application'
apply from: "$rootDir/utils.gradle"

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.0"

    defaultConfig {
        applicationId "com.moonbridge.proguardexample"
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
        buildConfigField 'String', 'SECRET_KEY', toJavaCodeString(SECRET_KEY)
    }
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.0.0'
}

utils.gradle

class Utils {
    static def r = new Random(System.currentTimeMillis())
}

def String toJavaCodeString(String string) {
    byte[] b = string.getBytes();
    int c = b.length;
    StringBuilder sb = new StringBuilder();

    sb.append("(new Object() {");
    sb.append("int t;");
    sb.append("public String toString() {");
    sb.append("byte[] buf = new byte[");
    sb.append(c);
    sb.append("];");

    for (int i = 0; i < c; ++i) {
        int t = Utils.r.nextInt();
        int f = Utils.r.nextInt(24) + 1;

        t = (t & ~(0xff << f)) | (b[i] << f);

        sb.append("t = ");
        sb.append(t);
        sb.append(";");
        sb.append("buf[");
        sb.append(i);
        sb.append("] = (byte) (t >>> ");
        sb.append(f);
        sb.append(");");
    }

    sb.append("return new String(buf);");
    sb.append("}}.toString())");

    return sb.toString();
}

ext.toJavaCodeString = this.&toJavaCodeString

Bu işlemin ardından MainActivity.java dosyamız içerisinde aşağıdaki gibi SECRET_KEY değişkenimizi kullanıyoruz.

Log.d("PROGUARD",
        "Normal String: " +testString1 +
                " - Static String: " + testString2 +
                " - Static Final String: " + testString3 + BuildConfig.SECRET_KEY);

Tekrar Build->Generate Signed APK yolunu izleyip uygulamamızı imzalıyoruz ve dex2jar işleminden geçirip .jar uzantılı dosyasını çıkarıyoruz. JD-GUI ile bu dosyayı açtığımızda aşağıdaki gibi bir ekranla SECRET_KEY değerimizin görüntülenmediğini görüyoruz.

Artık String değerlerimiz de güvende ve Android kodumuzu gönül rahatlığı ile dağıtabiliriz!!! Ancak çok güzel bir Türk atasözü der ki: “Hırsıza kilit dayanmaz!”…

Saygılar…