Search code examples
javalong-integerunsigned

unsigned long in java, using BigInteger for arithmetics, but BigInteger.toByteArray returns 14 bytes instead of 8


I have the following c code which id like to port to Java

unsigned long long* data=(unsigned long long*)pBuffer; // file data
unsigned long long crypt = 0x0000;
unsigned long long next_crypt;
unsigned int len = size >> 3;

for(unsigned int i=0; i<len;i++) {
        next_crypt = crypt+data[i]-0xCAFEBABE;      
        data[i] = ((data[i]<<0x1d)|(data[i]>>0x23))+0xCAFEBABE;
        data[i] =  (data[i]<<0x0e)|(data[i]>>0x32);
        data[i] = data[i] - crypt;
        crypt = next_crypt;     

    }

I tried to port this to java using long, however this would result in negative values. Therefor i switched to biginteger since i have to do arithmetics (bit shifting etc).

I got the desired 64bit unsigned long value using BigInteger, however when i wanted to convert it to byte (BigInteger.toByteArray) it was 14 bytes long and no longer 8 bytes - so i cannot modify my array/file anymore. I tried using toLongValue() but the data was incorrect.

Thanks


Solution

  • Your C code is relying on bits being shifted off the high-order end of the unsigned long long. (These are rotated around to the other end by the other shift.) BigInteger is arbitrary precision and hence has no end, so left-shifted bits are never shifted off.

    You could construct a 64-bit BigInteger bitwise AND mask and AND it after the left shifts. This is an intuitive solution.

    You could also just simply ignore the high-order bytes.

    byte[] bar = foo.toByteArray();
    if (bar.length > 8) {
        bar = System.arrayCopy(bar, bar.length - 8, new byte[8], 0, 8);
    }
    

    If len is large, then this simple solution would be wasteful of memory.

    In any case there is a saner and higher-performing solution. Java's signed integer types are all guaranteed to have two's complement semantics. The bit semantics for arithmetic with two's complement integers and unsigned integers are identical--the difference is only in the interpretation of the value! So just use the original C code (substituting in Java's long) and at the end, interpret them your way.

    byte[] longToByteArray(long x) {
        byte[] array = new byte[8];
        for (int i = 7; i >= 0; i--) {
            array[i] = (byte)x;
            x >>>= 8;
        }
    }
    

    By the way, be sure you replace the >> operator in the C code with Java's >>> operator.