62

I just Obfuscated my Android code using proguard and then decompiled it. There are a number of strings I would really like to hide from prying eyes. When I decompiled my code the strings were there for everyone to see...and change. One of the strings is a URL to my licensing server and they could in effect change the url to point to a fake server (as I will be releasing the server code to the public). What is the best way of hiding this sort of information?

Also, I noticed that the R class strings are all random numbers but I can't find the R class in the decompiled code. Where is it?

Foe example I see: new SimpleCursorAdapter(localActivity, 2130903058, localCursor, arrayOfString, arrayOfInt);

2130903058 is a layout file but what is it referencing? The number means nothing unless it is pointing to some sort of address.

Anthony
  • 12,407
  • 12
  • 64
  • 88
jax
  • 37,735
  • 57
  • 182
  • 278

7 Answers7

35

Assuming you are happy with obscure rather than secure, there a number of mechanisms you could use, but obfuscaters like proguard are not going to be able to help you.

To achieve this you will need to do encoding or encryption of the string yourself, the approach you use depends on what you are trying to defend against, if it you are just trying to hide from obvious inspection, than encoding may be sufficient (see android.util.Base64, http://developer.android.com/reference/android/util/Base64.html). Note that encoding is in NO WAY SECURE and all it will to is remove the obvious reference to your site.

If you are trying to defend against something more, then you could move to actually encrypting the string, to do this you would use a symmetric cipher like AES via javax.crypto.Cipher, http://www.androidsnippets.org/snippets/39/index.html provides a decent usage example. Again this is more annoying then secure to would be hackers, as you will need to store the key somewhere in your jar thus negating any cryptographic security.

To make this clearer, the basic steps would be:

  1. Manually create an encrypt your string using a known key.
  2. Convert your code to use a decrypted version of this string, example:

Before:

public class Foo {
    private String mySecret = "http://example.com";

    ...
}

Becomes:

public class Foo {
    private String encrypted = "<manually created encrypted string>";
    private String key = "<key used for encryption";
    private String mySecret = MyDecryptUtil.decrypt(encrypted, key);

    ...
}

A (good) alternative to all of this is considering using a third party drm solution such as the licensing server google provides http://android-developers.blogspot.com/2010/07/licensing-service-for-android.html. This may be more secure than something you roll your self, but is subject to very similar limitations to what I described above.

Mark Hibberd
  • 2,070
  • 1
  • 15
  • 12
  • What about storing some class files on the server. Is is possible to download and install new class files after an application is already installed? Is there a way to do this in a secure fashion, ie not allowing someone to copy the files of an already registered device and just using them? – jax Dec 13 '10 at 09:38
  • 23
    You can add a number of layers, but in the end, you are not going to be able to prevent a determined hacker. At some point you are better off investing your time in the rest of your product, make it good enough (read as valuable enough) and people won't want to steal it. – Mark Hibberd Dec 13 '10 at 09:43
  • 3
    "In the end, you are not going to be able to prevent a determined hacker" --> These are the best words in this long thread. Mark is right saying so, the best we can do is to slow down attackers only. – Krypton Apr 04 '13 at 01:43
  • 2
    Perhaps I'm missing something but encrypting the URL doesn't seem more secure since you still have to include the key used to decrypt the value in your code. A determined hacker could still decompile the APK, obtain the key and then manually decrypt the secret. – William Seemann Oct 27 '14 at 07:30
  • 1
    Look at my example of hiding api keys, tokens, etc. from the naked eye: https://gist.github.com/shomeser/68f4fe360be0edac95e4 – Oleksii K. Mar 24 '15 at 15:00
  • @Krypton. I think we could do something much better than that. See my answer. – cibercitizen1 Feb 23 '16 at 12:00
  • Keeping the encrypted text and the key in the same hardcoded place does not provide any "security" at all. I understand that the answer is pretty old but I don't think that could be considered secure even in 2010, this is just pure garbage. If you want to encrypt the strings than you should keep the symmetric key as safe as possible otherwise you're just wasting time. – fredmaggiowski Nov 15 '17 at 12:51
26

Hi all.

  1. Let secret be the text you want to hide

  2. Find the keyhash of your debug/release.keystore. Let k1 be this key.

(use tools keytool+openssl: keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64 )

  1. Use a tool (external to the android code) to encrypt secret with k1

    encrypted = encode (secret, k1)

(For instance: https://jwt.io, for java: https://github.com/jwtk/jjwt).

  1. In your android java code write down encrypted. When you need the decoded version of encrypted (this is, the original secret) write

original = decode(encrypted, get_my_keyhash_programmatically() )

That's all. This works because the original secret is not shown on java source code, neither the k1 to decode it. And, if a hacker wants to print your decoded secret, he must change code and recompile, signing his .apk with his own keystore not yours, and thus not getting the right original secret. (The "only" point is whether k1 can be figured out from your original .apk).

Note: get_my_keyhash_programmatically():

try {
    PackageInfo info = getPackageManager().getPackageInfo(
            "el nombre de su paquete por ejemplo com.tarea.u8",
            PackageManager.GET_SIGNATURES);
    for (Signature signature : info.signatures) {
        MessageDigest md = MessageDigest.getInstance("SHA");
        md.update(signature.toByteArray());
        Log.d("KeyHash:", Base64.encodeToString(md.digest(), Base64.DEFAULT));
    }
} catch (PackageManager.NameNotFoundException e) {

} catch (NoSuchAlgorithmException e) {

}
Community
  • 1
  • 1
cibercitizen1
  • 20,944
  • 16
  • 72
  • 95
  • You say "Use a tool to encrypt secret with k1 - for instance: https://jwt.io." However, when I go to jwt.io and try to use it to create a token from my own JSON, using my own key (putting that in the "secret" field), it just tells me the secret is invalid. "secret" is the only string it accepts. So how do I CREATE a token with my own key? – jkane001 Mar 15 '16 at 21:12
  • 1
    @Krypton like what? give me an example please of a way to break this – Catalin Mar 22 '16 at 10:49
  • 5
    An experienced hacker should reverse the java function encode() in like 5 minutes, and he could figure out that app's cert hash is used to encode very easily. Using a hooking framework like XPosed, he can extract your app's cert hash at runtime. From then on, he uses that hash to decode all of the strings. – Krypton Mar 23 '16 at 07:55
  • 4
    Wouldn't this create problems during development? I thought Android Studio signs debug builds with an auto-generated certificate that will be different for all developers and is meant to be easily expendable. – Blago Dec 17 '18 at 22:58
  • @cibercitizen1 can you tell me about this below line original = decode(encrypted, get_my_keyhash_programmatically() ) I guess original is the string where we are going to save the 'secret' key ... get_key_hash is also understandable. But from which class this *decode* method you are using. I am not getting this decode method. – Umair Feb 11 '20 at 10:20
  • @Umair 'original' is the real/actual string you don't want to be readable, it is not any key. 'encrypted' is the encrypted version of 'original'. 'get_my_keyhash_programmatically()' is the string you used to cypher and de-cypher 'original'. – cibercitizen1 Feb 11 '20 at 16:53
  • Thanks for ur kind reply. And yes, original, encrypted, keyhash and getKeyHash method everything i know what they are doing. Only thing i couldn't figure out is the decode method. I mean from which class or what methodology this decode will do to bring back the original key. Will be highly appreciated if you can put some light on it . – Umair Feb 11 '20 at 17:21
  • @Umair the encode/decode methods must be library methods. I don't remember right know if there are any in the standard library, *probably* there are. But I can suggest you jwt: https://jwt.io (there are portings to java). – cibercitizen1 Feb 11 '20 at 18:30
10

what I did was create a long list of static strings in my global utility class. Someplace within the long list of strings I put my passkey in multiple chunks.

with my code it's easy to see what the real passkeys are - but once the obfuscator gets to work all the statics will have name like A, B, C, etc. and it won't be easy to spot any more.

Someone Somewhere
  • 23,475
  • 11
  • 118
  • 166
  • can you please provide sample code of what you said. Thanks – Sam Mar 06 '14 at 06:09
  • 3
    Hi sam, just create a public class with a whole bunch of `public static String variable1 = "fake data";` when I say a "whole bunch" I mean like a hundred of those Strings. It's easy to create a file like this using excel. Then, hide some important data amongst all those "fake" lines. Once the obfuscator gets to work, all this data is going to look like a mess. When you want to use the data, combine a few individual Strings to recreate what you want to hide. You can go a step further by somehow encoding those lines of text so that it looks even more like a mess. – Someone Somewhere Mar 06 '14 at 20:00
  • the point is: to make the person, who's reverse engineering your code, to have to work for it. The more unappealing you can make it, the better the likelihood that it's not worth their time. – Someone Somewhere Mar 06 '14 at 20:05
  • 2
    The caveat is you have to use the code somewhere as compilers / proguard may remove unused code, I.e. don't just dump in a bunch of unused string variables. – Kevin Lee Apr 18 '16 at 05:44
  • 2
    As for combining strings.. depending on how you do it, proguard may put them all together with a string builder. Basically you should check the decompiled output – Kevin Lee Apr 18 '16 at 05:46
2

I used ROT47. It's not very secure, but easy to use and implement, because it's a symetric encoder/decoder

w_g
  • 164
  • 3
1

You should google for "Just another Perl hacker". These are programms that print out a string with obfuscated code. There are also lots of examples in other languages then Perl on the net.

Wikipedia entry

iuiz
  • 957
  • 1
  • 10
  • 23
0

Here is what I currently use it has hacks to support sprintf functions which spilled plain-text in compiled binary file. You could now use w_sprintf_s instead of sprintf, like so

char test[256] = { 0 };
w_sprintf_s(test, 256, XorStr("test test :D %d %+d\n"), 1, 1337);

or use it like this to print stuff on screen for example

w_printf(XorStr("test I print this and can't see me inside .dll or .exe"));

works on variables, if you have a custom printf() you could use that as well..

char szGuid[255] = { 0 };
//generate serial code removed.
char finalSerial[512] = { 0 };
XorCompileTime::w_sprintf(finalSerial, XorStr("serial information=%s"), szGuid);
myprintf(XorStr("Your Hardware ID: %s\n"), szGuid);


May add support for wchar_t wide strings like arkan did.. but I have no use for them right now as I don't write anything in symbols / unicode.

Here is a file just rename the code below to a XorString.h file and include it in your project simple as that

#pragma once
#include <string>
#include <array>
#include <cstdarg>

#define BEGIN_NAMESPACE( x ) namespace x {
#define END_NAMESPACE }

BEGIN_NAMESPACE(XorCompileTime)

constexpr auto time = __TIME__;
constexpr auto seed = static_cast< int >(time[7]) + static_cast< int >(time[6]) * 10 + static_cast< int >(time[4]) * 60 + static_cast< int >(time[3]) * 600 + static_cast< int >(time[1]) * 3600 + static_cast< int >(time[0]) * 36000;

// 1988, Stephen Park and Keith Miller
// "Random Number Generators: Good Ones Are Hard To Find", considered as "minimal standard"
// Park-Miller 31 bit pseudo-random number generator, implemented with G. Carta's optimisation:
// with 32-bit math and without division

template < int N >
struct RandomGenerator
{
private:
    static constexpr unsigned a = 16807; // 7^5
    static constexpr unsigned m = 2147483647; // 2^31 - 1

    static constexpr unsigned s = RandomGenerator< N - 1 >::value;
    static constexpr unsigned lo = a * (s & 0xFFFF); // Multiply lower 16 bits by 16807
    static constexpr unsigned hi = a * (s >> 16); // Multiply higher 16 bits by 16807
    static constexpr unsigned lo2 = lo + ((hi & 0x7FFF) << 16); // Combine lower 15 bits of hi with lo's upper bits
    static constexpr unsigned hi2 = hi >> 15; // Discard lower 15 bits of hi
    static constexpr unsigned lo3 = lo2 + hi;

public:
    static constexpr unsigned max = m;
    static constexpr unsigned value = lo3 > m ? lo3 - m : lo3;
};

template <>
struct RandomGenerator< 0 >
{
    static constexpr unsigned value = seed;
};

template < int N, int M >
struct RandomInt
{
    static constexpr auto value = RandomGenerator< N + 1 >::value % M;
};

template < int N >
struct RandomChar
{
    static const char value = static_cast< char >(1 + RandomInt< N, 0x7F - 1 >::value);
};

template < size_t N, int K >
struct XorString
{
private:
    const char _key;
    std::array< char, N + 1 > _encrypted;

    constexpr char enc(char c) const
    {
        return c ^ _key;
    }

    char dec(char c) const
    {
        return c ^ _key;
    }

public:
    template < size_t... Is >
    constexpr __forceinline XorString(const char* str, std::index_sequence< Is... >) : _key(RandomChar< K >::value), _encrypted{ enc(str[Is])... }
    {
    }

    __forceinline decltype(auto) decrypt(void)
    {
        for (size_t i = 0; i < N; ++i) {
            _encrypted[i] = dec(_encrypted[i]);
        }
        _encrypted[N] = '\0';
        return _encrypted.data();
    }
};

//--------------------------------------------------------------------------------
//-- Note: XorStr will __NOT__ work directly with functions like printf.
//         To work with them you need a wrapper function that takes a const char*
//         as parameter and passes it to printf and alike.
//
//         The Microsoft Compiler/Linker is not working correctly with variadic 
//         templates!
//  
//         Use the functions below or use std::cout (and similar)!
//--------------------------------------------------------------------------------

static auto w_printf = [](const char* fmt, ...) {
    va_list args;
    va_start(args, fmt);
    vprintf_s(fmt, args);
    va_end(args);
};

static auto w_printf_s = [](const char* fmt, ...) {
    va_list args;
    va_start(args, fmt);
    vprintf_s(fmt, args);
    va_end(args);
};

static auto w_sprintf = [](char* buf, const char* fmt, ...) {
    va_list args;
    va_start(args, fmt);
    vsprintf(buf, fmt, args);
    va_end(args);
};

static auto w_sprintf_s = [](char* buf, size_t buf_size, const char* fmt, ...) {
    va_list args;
    va_start(args, fmt);
    vsprintf_s(buf, buf_size, fmt, args);
    va_end(args);
};

#define XorStr( s ) ( XorCompileTime::XorString< sizeof( s ) - 1, __COUNTER__ >( s, std::make_index_sequence< sizeof( s ) - 1>() ).decrypt() )

END_NAMESPACE
SSpoke
  • 5,656
  • 10
  • 72
  • 124
-1

You can use DexGuard to encrypt strings, probably more effectively than you could achieve manually, and without burdening the source code.

Ahmad Ronagh
  • 790
  • 10
  • 16