8

I'm a bit lost. For a project, I need to convert the output of a hash-function (SHA256) - which is a byte array - to a String using base 36.

So In the end, I want to convert the (Hex-String representation of the) Hash, which is

43A718774C572BD8A25ADBEB1BFCD5C0256AE11CECF9F9C3F925D0E52BEAF89

to base36, so the example String from above would be:

3SKVHQTXPXTEINB0AT1P0G45M4KI8U0HR8PGB96DVXSTDJKI1

For the actual conversion to base36, I found some piece of code here on StackOverflow:

public static String toBase36(byte[] bytes) {
    //can provide a (byte[], offset, length) method too
    StringBuffer sb = new StringBuffer();
    int bitsUsed = 0; //will point how many bits from the int are to be encoded
    int temp = 0;
    int tempBits = 0;
    long swap;
    int position = 0;

    while((position < bytes.length) || (bitsUsed != 0)) {
        swap = 0;
        if(tempBits > 0) {
            //there are bits left over from previous iteration
            swap = temp;
            bitsUsed = tempBits;
            tempBits = 0;
        }
        //fill some bytes
        while((position < bytes.length) && (bitsUsed < 36)) {
            swap <<= 8;
            swap |= bytes[position++];
            bitsUsed += 8;
        }
        if(bitsUsed > 36) {
            tempBits = bitsUsed - 36; //this is always 4
            temp = (int)(swap & ((1 << tempBits) - 1)); //get low bits
            swap >>= tempBits; //remove low bits
            bitsUsed = 36;
        }
        sb.append(Long.toString(swap, 36));
        bitsUsed = 0;
    }
    return sb.toString();
}

Now I'm doing this:

// this creates my hash, being a 256-bit byte array
byte[] hash = PBKDF2.deriveKey(key.getBytes(), salt.getBytes(), 2, 256);

System.out.println(hash.length); // outputs "256"
System.out.println(toBase36(hash)); // outputs total crap

the "total crap" is something like

-7-14-8-1q-5se81u0e-3-2v-24obre-73664-7-5-5cor1o9s-6h-4k6hr-5-4-rt2z0-30-8-2u-8-onz-4a2j-6-8-18-8trzza3-3-2x-6-4153to-4e3l01me-6-azz-2-k-4ckq-nav-gu-irqpxx-el-1j-6-rmf8hs-1bb5ax-3z25u-2-2r-t5-22-6-6w1v-1p

so it's not even close to what I want. I tried to find a solution now, but it seems I'm a bit lost here. How do I get the base36-encoded String representation of the Hash that I need?

Viktor Mellgren
  • 4,318
  • 3
  • 42
  • 75
Xenonite
  • 1,823
  • 4
  • 26
  • 39

2 Answers2

9

Try using BigInteger:

String hash = "43A718774C572BD8A25ADBEB1BFCD5C0256AE11CECF9F9C3F925D0E52BEAF89";
//use a radix of 16, default would be 10 
String base36 = new BigInteger( hash, 16 ).toString( 36 ).toUpperCase();
Thomas
  • 87,414
  • 12
  • 119
  • 157
  • @NateS please don't make edits that actually change an answer. If there are questions I encourage you to ask them via comments - in the case of your edit the constructor [`BigInteger(String, int)`](https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/math/BigInteger.html#%3Cinit%3E(java.lang.String,int)) _is_ visible. If that's not the case in your code please make sure you're using the right types (note that there's a `BigInteger` class provided by IBM which might lack that constructor). – Thomas Apr 21 '20 at 09:59
  • This does not really convert the hash to a Base36 string. It only returns the string representation of the number in base 36. You need to further convert that big number to an actual Base36 string by doing long division with divisor = 36 and dividend = that number – Rushil Paul Mar 30 '23 at 17:25
  • Check this to verify: https://ideone.com/wzD0gy – Rushil Paul Mar 30 '23 at 17:44
  • @RushilPaul you should read the JavaDoc carefully. On `BigInteger.toString(radix)` it states: "If the radix is outside the range from Character.MIN_RADIX (which is 2) to Character.MAX_RADIX (which is 36) inclusive,it will default to 10 (as is the case for Integer.toString)." - So your "proof" using `toString(64)` is actually converted to `toString(10)` and hence the output _will_ be different. – Thomas Apr 04 '23 at 06:36
  • Yes you're right. The first time I tried it with my custom division code, I was getting a different answer than what toString(36) was giving me, but that was because my baseCharset had letters first and digits later. The Java API places the digits first and letters later. So then I tried comparing the hash with the Base64 Java library and didn't notice this detail. Thanks for pointing it out. – Rushil Paul Apr 09 '23 at 19:36
  • For verification: https://ideone.com/X6OVRC – Rushil Paul Apr 09 '23 at 19:43
3

This might work:

BigInteger big = new BigInteger(your_byte_array_to_hex_string, 16);

big.toString(36);

Community
  • 1
  • 1
jm'
  • 107
  • 9