80

I am trying to use the library DigestUtils in Android 2.3.1 using JDK 1.6, however I get the following error when executing the app:

Could not find method org.apache.commons.codec.binary.Hex.encodeHexString, referenced from method org.apache.commons.codec.digest.DigestUtils.shaHex

Here you have the stacktrace:

02-03 10:25:45.153: I/dalvikvm(1230): Could not find method org.apache.commons.codec.binary.Hex.encodeHexString, referenced from method org.apache.commons.codec.digest.DigestUtils.shaHex
02-03 10:25:45.153: W/dalvikvm(1230): VFY: unable to resolve static method 329: Lorg/apache/commons/codec/binary/Hex;.encodeHexString ([B)Ljava/lang/String;
02-03 10:25:45.153: D/dalvikvm(1230): VFY: replacing opcode 0x71 at 0x0004
02-03 10:25:45.153: D/dalvikvm(1230): VFY: dead code 0x0007-0008 in Lorg/apache/commons/codec/digest/DigestUtils;.shaHex ([B)Ljava/lang/String;
02-03 10:25:45.163: D/AndroidRuntime(1230): Shutting down VM
02-03 10:25:45.163: W/dalvikvm(1230): threadid=1: thread exiting with uncaught exception (group=0x40015560)
02-03 10:25:45.173: E/AndroidRuntime(1230): FATAL EXCEPTION: main
02-03 10:25:45.173: E/AndroidRuntime(1230): java.lang.NoSuchMethodError: org.apache.commons.codec.binary.Hex.encodeHexString
02-03 10:25:45.173: E/AndroidRuntime(1230):     at org.apache.commons.codec.digest.DigestUtils.md5Hex(DigestUtils.java:226)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at com.caumons.trainingdininghall.ConnectionProfileActivity.onCreate(ConnectionProfileActivity.java:20)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1586)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1638)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at android.app.ActivityThread.access$1500(ActivityThread.java:117)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:928)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at android.os.Handler.dispatchMessage(Handler.java:99)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at android.os.Looper.loop(Looper.java:123)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at android.app.ActivityThread.main(ActivityThread.java:3647)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at java.lang.reflect.Method.invokeNative(Native Method)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at java.lang.reflect.Method.invoke(Method.java:507)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at dalvik.system.NativeStart.main(Native Method)

The line of code which causes the exception is:

String hash = DigestUtils.shaHex("textToHash");

I have executed the same code in a Java class outside Android and it works! So, I do not know why when working with Android it does not work... I put the libraty inside a new libs/ folder in my app and updated the BuildPath to use it. If I try to use md5 instead of sha1 I get the same exception. Any help would be appreciated! Thank you.

UPDATE:

As this is a very active question, I've changed the accepted answer in favour of @DA25, as his solution is straightforward and the high number of upvotes prove that it works.

Caumons
  • 8,153
  • 9
  • 60
  • 77
  • Did you edit all the source files ? What is the best way for me to edit all the source files? If possible, can you share the new jar files that you have created. – Hemant May 18 '12 at 10:23
  • Open the sources with Eclipse and change the package name to what you want. Then, use some command to replace the old strings that referenced the original package name. Where could I share the generated jar? – Caumons May 21 '12 at 14:03

8 Answers8

150

I ran into the same issue trying to use DigestUtils in my Android app. This was the best answer I could find by searching, but I was reluctant to rebuild the .jar file with the namespace changed. After spending some time on this issue, I found an easier way to solve the problem for my case. The problem statement for my code was

String s = DigestUtils.md5Hex(data);

Replace this statement with the following and it will work:

String s = new String(Hex.encodeHex(DigestUtils.md5(data)));

Similarly, for shaHex exampl, you can change it to

String hash = new String(Hex.encodeHex(DigestUtils.sha("textToHash")));

This works because even though Android does not have encodeHexString(), it does have encodeHex(). Hope this would help others who run into the same issue.

Emil Adz
  • 38,699
  • 35
  • 127
  • 177
DA25
  • 1,516
  • 2
  • 9
  • 2
  • 7
    I do not understand why just calling the problematic methods inside `Hex.encodeHex()` and setting the result to a String constructor works for you! Could you be more specific? Is the method `encodeHex()` from the DigestUtils library? Have you tried `new String(DigestUtils.md5(data));` directly? – Caumons Feb 23 '12 at 10:28
  • 4
    I know this is an old question, but for anyone interested: this is caused by Android bundling its own version of commons-codec 1.2. Any version newer than than will not be available on the device. – KennethJ Aug 31 '14 at 22:49
  • This does not work. I checked on Android API level 22. – lovesh May 01 '15 at 18:27
  • 1
    @lovesh What exactly doesn't work for you? Just checked - nothing changed in API 22 and it works same as before. – Alex Lipov Jul 07 '15 at 20:51
  • @Caumons the problematic method is DigestUtils.md5Hex and he's not calling that one, but DigestUtils.md5 instead and converting it to hex with a different method encodeHex. – Fran Marzoa Mar 21 '18 at 16:20
  • Note that `DigestUtils.sha()` is deprecated.In my case I just used: `String hash = String(Hex.encodeHex(DigestUtils.sha1(data)));` – K. Stopa Mar 16 '20 at 12:29
37

Since there's no clear answer for the root cause of this problem, I'd like to clarify what's happening here.

Why the NoSuchMethodError is thrown in the first place?

According to exception stack trace, the line that causes the fault is 226 in DigestUtils#md5hex method. Let's see what we have there (I'm assuming you have used version 1.4, since this is the only release where Hex#encodeHexString method is being invoked in line 226):

public static String md5Hex(String data) {
    return Hex.encodeHexString(md5(data));
}

The exception says java.lang.NoSuchMethodError: org.apache.commons.codec.binary.Hex.encodeHexString. Let's understand why.

First of all, Android framework already includes the Commons Codec library (except the DigestUtils class). Yes, it is not exposed as part of the Android SDK and you cannot use it directly. But you still want to use it. So what you do? You add Commons Codec library as part of your application. The compiler doesn't complain - from his point of view everything was fine.

But what happens at runtime? Let's follow your exception stack trace:
First, you're calling DigestUtils#md5Hex from your Activity's onCreate method. As I wrote above, the framework doesn't include that class, so DigestUtils (from Commons Codec version 1.4) is loaded from your dex.
Next, md5hex method tries to invoke Hex#encodeHexString method. Hex class is part of the Commons Codec library that included in framework. The thing is that its version is 1.3 (ancient release from July 2004). Hex class exists in boot classpath, which means that the runtime will always favor it instead of the Hex class that packaged inside your dex. You can see warnings about it in your application logs when you start your app (with Dalvik runtime):

D/dalvikvm? DexOpt: 'Lorg/apache/commons/codec/binary/Hex;' has an earlier definition; blocking out
I/dalvikvm? DexOpt: not resolving ambiguous class 'Lorg/apache/commons/codec/binary/Hex;'
D/dalvikvm? DexOpt: not verifying/optimizing 'Lorg/apache/commons/codec/binary/Hex;': multiple definitions
I/dalvikvm? Could not find method org.apache.commons.codec.binary.Hex.encodeHexString, referenced from method org.apache.commons.codec.digest.DigestUtils.md5Hex

Hex#encodeHexString method was introduced in version 1.4 of Commons Codec library and therefore it doesn't exist in framework's Hex class. The runtime can't find this method and thus throws NoSuchMethodError exception.

Why the accepted answer's solution works?

String s = new String(Hex.encodeHex(DigestUtils.md5(data)));

First, DigestUtils#md5 method is called. As I already stated, DigestUtils class that will be used is the one that packaged in your dex. This method doesn't use any other Commons Codec classes, so no problem with it.

Next, Hex#encodeHex will be called. The Hex class that will be used is the framework's one (version 1.3). The encodeHex method (that takes a single parameter - byte array) exists in version 1.3 of Commons Codec library, and therefore this code will work fine.

What would I suggest?

My suggested solution is to rename the classes namespace/package. By doing so I'm explicitly specifying which code is going to execute, and prevent bizarre behavior that may occur because of versioning issues.

You can do it manually (as Caumons wrote in his answer), or automatically with jarjar tool.

See this issue summary and tips for using jarjar in my blogpost.

Alex Lipov
  • 11,917
  • 4
  • 58
  • 80
  • 3
    Thank you so much man! I had the same issue with Apache Commons Lang jar (3.2 and above) - but only on all phones from the vendor Xiomi. Apparently, these phones have an old version of Apache Commons Lang as part of system runtime. So I used to get exceptions like: STACK_TRACE=java.lang.NoSuchMethodError: org.apache.commons.lang3.mutable.MutableBoolean.setTrue STACK_TRACE=java.lang.NoSuchMethodError: org.apache.commons.lang3.StringEscapeUtils.escapeXml10 Following your advice, I used jarjar tool to rename the package namespace to something unique to my app. – La Machine Jul 13 '16 at 10:41
  • 1
    Thanks, you inspired me to write this simple showcase project https://github.com/allpaykz/digest-utils – c0rp Aug 03 '17 at 09:21
19

Finally I get the answer and it works well. As described in No such method error in Apache codec for another type of encrypt (Base64) I tried to reproduce the same issue and I get exactly the same error. So I was in the case of the question attached. As they say, it seems to be an internal name collision with the package name org.apache.commons.codec and as stated by @Don I changed it to com.apache.commons.codec and worked fine! How I did it?

I downloaded the source code and changed the 3 directories org to com. I also replaced all the occurrences of the package name in the files where they appear and also changed the references in the docs to com/apache/commons/codec/. (Do not try to remane them manually or you will spend the hole day). Then I compiled the library and generated the jar with Ant, which I called commons-codec-1.6-android.jar. I put the jar in the libs/ folder of my Android app and added it to the buildpath. Also, I attached the sources as the folder which contains all the files. So now I have the library ready to use with Android!

Hope that it helps someone else!

Community
  • 1
  • 1
Caumons
  • 8,153
  • 9
  • 60
  • 77
  • 2
    This is the right answer but it seems highly improbable that devs will change the digest source. This should be reported to apache. In my dev, I chose the other option given by @DA25 – Snicolas Jun 22 '12 at 08:19
  • 1
    For those using Maven: no need to do this by hand. There is a plugin for it, see explanation here: http://stackoverflow.com/a/16916552/621690 – Risadinha Sep 09 '13 at 11:23
  • @Caumons can you please share the library, I'm in same trouble. – Nitin Misra Jan 31 '14 at 07:14
  • @NitinMisra have you read the update or the comment above from Risadinha? – Caumons Jan 31 '14 at 14:30
  • @Caumons I use `new String(DigestUtils.md5(data));` as you suggested earlier. Is it safe? – Nitin Misra Jan 31 '14 at 14:31
  • @NitinMisra the answer's high number of upvotes seem to prove so ;) – Caumons Jan 31 '14 at 20:07
3

Thanks @DA25

This is working fine for me

I have add dependency

compile 'commons-codec:commons-codec:1.9'

ref: http://mvnrepository.com/artifact/commons-codec/commons-codec/1.9

my function

public String encode(String key, String data) {
    try {

        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);

        return new String(Hex.encodeHex(sha256_HMAC.doFinal(data.getBytes("UTF-8"))));

    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }

    return null;
}
Bikesh M
  • 6,863
  • 4
  • 35
  • 49
1

For me proguard removed the class during obfuscation .Add this to your Proguard rules.

-keep class org.apache.commons.** { *; }

Here's the method I was using of apache package.

Hex.encodeHex(digest)
pratham kesarkar
  • 3,438
  • 2
  • 15
  • 27
0

Add method

public static String byteArrayToHexString(byte[] bytes) {
    final char[] toDigits = "0123456789abcdef".toCharArray();
    int l = bytes.length;
    char[] out = new char[l << 1];

    int i = 0; for (int j = 0; i < l; ++i) {
        out[(j++)] = toDigits[((0xF0 & bytes[i]) >>> 4)];
        out[(j++)] = toDigits[(0xF & bytes[i])];
    }
    return new String(out);
}
reznic
  • 569
  • 5
  • 8
0

We used below code and it worked :

  HmacUtils hmacUtils = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, keyString);
  String digest = new String( Hex.encodeHex(hmacUtils.hmac(msg)));
Alok Gupta
  • 1,405
  • 17
  • 21
0

Another way to rename DigestUtils class will be with proguard. If you are not using proguard you can enable it and add this one line which will obfuscate only DigestUtils class, and leave everything else intact.

-keep class !org.apache.commons.codec.digest.DigestUtils,com.** { *; }

and add this to your app build.gradle

buildTypes {
        debug {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

Or OPTION 2 Use old version of the library in your code:

implementation("commons-codec:commons-codec:1.3"){
        force = true
    }

Need to use force = true if common-codec dependency come from third part library otherwise Gradle will resolve by default to higher version.

Roman Nazarevych
  • 6,184
  • 2
  • 54
  • 62