Java SHA-1 hash an unsigned BYTE - java

Hy guys!
I have the following problem:
I need to hash an unsigned byte in Java which is(would be...) between 0-255.
The main problem is that java doesnt have an unsigned Byte type at all.
I found a workaround for this, and used int instead of byte with a little modification.
The main problem is: Java.securitys Messagedigest.digest function only accepts byte array types, but i would need to give it an int array.
Anybody has a simpe workaround for this?
I was looking for a third party sha-1 function, but didnt found any. Nor any sample code.
So basically what i need:
I have an unsigned byte value for example: 0xFF and need to get the following sha1 hash: 85e53271e14006f0265921d02d4d736cdc580b0b
any help would be greatly appreciated.

It's important to understand that there is no difference between signed and unsigned bytes with respect to their representation. Signedness is about how bytes are treated by arithmetic operations (other than addition and subtraction, in the case of 2's complement representation).
So, if you use bytes for data storage, all you need is to make sure that you treat them as unsigned when converting values to bytes (use explicit cast with (byte), point 1) and from bytes (prevent sign extension with & 0xff, point 2):
public static void main(String[] args) throws Exception {
byte[] in = { (byte) 0xff }; // (1)
byte[] hash = MessageDigest.getInstance("SHA-1").digest(in);
System.out.println(toHexString(hash));
}
private static String toHexString(byte[] in) {
StringBuilder out = new StringBuilder(in.length * 2);
for (byte b: in)
out.append(String.format("%02X", b & 0xff)); // (2)
return out.toString();
}

Look at Apache Commons Codec library, method DigestUtils.sha(String data). It may be useful for you.

The digest won't care about how Java perceives the sign of a byte; it cares only about the bit pattern of the byte. Try this:
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update((byte) 0xFF);
byte[] result = digest.digest();
StringBuilder buffer = new StringBuilder();
for (byte each : result)
buffer.append(String.format("%02x", 0xFF & each));
System.out.println(buffer.toString());
This should print 85e53271e14006f0265921d02d4d736cdc580b0b.

Related

Java BigInteger toByteArray specify number of bytes to use

I was wondering it there is any way to specify how many bytes to use when creating the byte array using toByteArray method. For example:
BigInteger bigInteger = new BigInteger("-12");
I want bigInteger.toByteArray() to return an array with values FF and F4 (assuming that the value is represented using 4 bytes - short variable, but it returns only F4.
You could just make your own helper class.
public class BigIntegerHelper {
public static byte[] toByteArray(BigInteger big, int minLength) {
byte[] base=big.toByteArray();
byte[] returnArray=new byte[Math.max(base.length, minLength)];
if ((base[0]&128)!=0) {
Arrays.fill(returnArray, (byte) 0xFF);
}
System.arraycopy(base,0,returnArray,returnArray.length-base.length,base.length);
return returnArray;
}
}
It's unclear exactly what you're going for but BigInteger has a shortValue() method that might help.
For example,
BigInteger big = new BigInteger("-12");
short s = big.shortValue();
byte [] bytes = ByteBuffer.allocate(2).putShort(s).array();
Something similar can be done if you want 4 bytes (int) or 8 bytes(long).

Standard way to create a hash in Java

The question is about the correct way of creating a hash in Java:
Lets assume I have a positive BigInteger value that I would like to create a hash from. Lets assume that below instance of the messageDigest is a valid instance of (SHA-256)
public static final BigInteger B = new BigInteger("BD0C61512C692C0CB6D041FA01BB152D4916A1E77AF46AE105393011BAF38964DC46A0670DD125B95A981652236F99D9B681CBF87837EC996C6DA04453728610D0C6DDB58B318885D7D82C7F8DEB75CE7BD4FBAA37089E6F9C6059F388838E7A00030B331EB76840910440B1B27AAEAEEB4012B7D7665238A8E3FB004B117B58", 16);
byte[] byteArrayBBigInt = B.toByteArray();
this.printArray(byteArrayBBigInt);
messageDigest.reset();
messageDigest.update(byteArrayBBigInt);
byte[] outputBBigInt = messageDigest.digest();
Now I only assume that the code below is correct, as according to the test the hashes I produce match with the one produced by:
http://www.fileformat.info/tool/hash.htm?hex=BD0C61512C692C0CB6D041FA01BB152D4916A1E77AF46AE105393011BAF38964DC46A0670DD125B95A981652236F99D9B681CBF87837EC996C6DA04453728610D0C6DDB58B318885D7D82C7F8DEB75CE7BD4FBAA37089E6F9C6059F388838E7A00030B331EB76840910440B1B27AAEAEEB4012B7D7665238A8E3FB004B117B58
However I am not sure why we are doing the step below i.e.
because the returned byte array after the digest() call is signed and in this case it is a negative, I suspect that we do need to convert it to a positive number i.e. we can use a function like that.
public static String byteArrayToHexString(byte[] b) {
String result = "";
for (int i=0; i < b.length; i++) {
result += Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1);
}
return result;
}
thus:
String hex = byteArrayToHexString(outputBBigInt)
BigInteger unsignedBigInteger = new BigInteger(hex, 16);
When I construct a BigInteger from the new hex string and convert it back to byte array then I see that the sign bit, that is most significant bit i.e. the leftmost bit, is set to 0 which means that the number is positive, moreover the whole byte is constructed from zeros ( 00000000 ).
My question is: Is there any RFC that describes why do we need to convert the hash always to a "positive" unsigned byte array. I mean even if the number produced after the digest call is negative it is still a valid hash, right? thus why do we need that additional procedure. Basically, I am looking for a paper: standard or rfc describing that we need to do so.
A hash consists of an octet string (called a byte array in Java). How you convert it to or from a large number (a BigInteger in Java) is completely out of the scope for cryptographic hash algorithms. So no, there is no RFC to describe it as there is (usually) no reason to treat a hash as a number. In that sense a cryptographic hash is rather different from Object.hashCode().
That you can only treat hexadecimals as unsigned is a bit of an issue, but if you really want to then you can first convert it back to a byte array, and then perform new BigInteger(result). That constructor does threat the encoding within result as signed. Note that in protocols it is often not needed to convert back and forth to hexadecimals; hexadecimals are mainly for human consumption, a computer is fine with bytes.

Encrypt 4 digits pin with hex enumeration system, resulting 16 chars string

im looking for a way to encrypt a four digits password and as a result get a 16chars string.
So far ive got 64chars String using this
public static String digestHex(String text) {
StringBuilder stringBuffer = new StringBuilder();
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");// SHA-256
digest.reset();
for (byte b : digest.digest(text.getBytes("UTF-8"))) {
stringBuffer.append(Integer.toHexString((int) (b & 0xff)));
}
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
e.printStackTrace();
}
return stringBuffer.toString();
}
being text = 1234
the resulting String is = 3ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4 Using Java btw :D
Any "encryption" scheme where you are encrypting a 4 digit number without an additional key is effectively a lookup scheme. Since there are only 10,000 unique "inputs" to the lookup scheme, it will be relatively easy to crack your encryption ... by trying all of the inputs.
In other words, the security of your encrypted PIN numbers is an illusion ... unless you do something like "seeding" the input before you encrypt it.
The security of you scheme aside - there are easier ways to do this:
// Your original - with the horrible exception hiding removed.
public static String digestHex(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException {
StringBuilder stringBuffer = new StringBuilder();
MessageDigest digest = MessageDigest.getInstance("SHA-256");// SHA-256
digest.reset();
for (byte b : digest.digest(text.getBytes("UTF-8"))) {
stringBuffer.append(Integer.toHexString((int) (b & 0xff)));
}
return stringBuffer.toString();
}
// Uses BigInteger.
public static String digest(String text, int base) throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");// SHA-256
digest.reset();
BigInteger b = new BigInteger(digest.digest(text.getBytes("UTF-8")));
return b.toString(base);
}
public void test() throws NoSuchAlgorithmException, UnsupportedEncodingException {
System.out.println("Hex:" + digestHex("1234"));
System.out.println("Hex:" + digest("1234", 16));
System.out.println("36:" + digest("1234", 36));
System.out.println("Max:" + digest("1234", Character.MAX_RADIX));
}
This allows you to generate the string in a higher base - thus shortening the number but sadly you still do not achieve 16.
I would suggest you use one of the simple CRC algorithms if you are really instistent on 16 characters. Alternatively you could try base 62 or base 64 - there are many implementations out there.
You are using SHA-256. This algorithm generates 32 bytes long messages (256 bits, more details here).
This is why you obtain a 64 bytes long hex string as an output: Integer.toHexString((int) (b & 0xff)) converts each single b byte of the MessageDigest into a 2 bytes long hex String representation.
To obtain a 16 bytes long String, you can either use MD5 (16 bytes output, 32 if converted in hex), derive that string or use a completely different way such as actually using encryption (using javax.crypto.Cipher).
I'd need to know what you would like to to to elaborate further, knowing that using MessageDigestis actually hashing, not encryption, while in the first line of your post you are speaking of encryption. One of the difference resides in the fact that hash codes are not designed to be reversed but compared, unlike encryption which is reversible. See this interesting SO post on this.

Comparison of byte arrays

I try to compare 2 byte arrays.
Byte array 1 is an array with the last 3 bytes of a sha1 hash:
private static byte[] sha1SsidGetBytes(byte[] sha1)
{
return new byte[] {sha1[17], sha1[18], sha1[19]};
}
Byte array 2 is an array that I fill with 3 bytes coming from an hexadecimal string:
private static byte[] ssidGetBytes(String ssid)
{
BigInteger ssidBigInt = new BigInteger(ssid, 16);
return ssidBigInt.toByteArray();
}
How is it possible that this comparison:
if (Arrays.equals(ssidBytes, sha1SsidGetBytes(snSha1)))
{
}
works most of the times but sometimes not. Byte Order?
e.g. for "6451E6" (hex string) it works fine, for "ABED74" it does not...
The problem is pretty obvious if you try this:
BigInteger b1 = new BigInteger("6451E6", 16);
BigInteger b2 = new BigInteger("ABED74", 16);
System.out.println(b1.toByteArray().length);
System.out.println(b2.toByteArray().length);
Specifically, ABED74 creates a BigInteger whose byte array is 4 bytes long--so of course it's not going to be equal to any three byte array.
The straightforward fix is to change the return statement in ssidGetBytes from
return ssidBigInt.toByteArray();
to
byte[] ba = ssidBigInt.toByteArray();
return new byte[] { ba[ba.length - 3], ba[ba.length - 2], ba[ba.length - 1] };
Your approach of parsing a hex string via BigInteger is flawed, basically. For example, new BigInteger("ABED74").toByteArray() returns an array of 4 bytes, not three. While you could hack around this, you're fundamentally not trying to do anything involving BigInteger values... you're just trying to parse hex.
I suggest you use the Apache Codec library to do the parsing:
byte[] array = (byte[]) new Hex().decode(text);
(The API for Apache Codec leaves something to be desired, but it does work.)
From the javadoc's (emphasis mine):
http://download.oracle.com/javase/1.5.0/docs/api/java/math/BigInteger.html#toByteArray%28%29
Returns a byte array containing the
two's-complement representation of
this BigInteger. The byte array will
be in big-endian byte-order: the most
significant byte is in the zeroth
element. The array will contain the
minimum number of bytes required to
represent this BigInteger, including
at least one sign bit, which is
(ceil((this.bitLength() + 1)/8)).
(This representation is compatible
with the (byte[]) constructor.)
There is a lot of computations going on inside the ByteInteger(String,radix) constructor that you are using, which does not guarantee the constructed BigInteger will produce a byte array (via its toByteArray() method) comparable to the result of a String's getBytes() encoding.
The output of toByteArray() is intended to be used (mostly) as input to the (byte[]) constructor of BigInteger. It makes no guarantee for uses other than those.
Look at it like this: the output of toByteArray() is the byte representation of the BigInteger object and everything in it including internal attributes like magnitude. Those attributes do not exist in the input String, but are computed during construction of the BitInteger object.
That will be incompatible to the byte representation of the input String which only carries the initial numeric value with which to create a BigInteger.

Convert INT(0-255) to UTF8 char in Java

since I need to control some devices, I need to send some bytes to them. I'm creating those bytes by putting some int values together (and operator), creating a byte and finally attaching it to a String to send it over the radio function to the robot.
Unfortuantely Java has some major issues doing that (unsigned int problem)
Does anybody know, how I can convert an integer e.g.
x = 223;
to an 8-bit character in Java to attach it to a String ?
char = (char)x; // does not work !! 16 bit !! I need 8 bit !
A char is 16-bit in Java. Use a byte if you need an 8-bit datatype.
See How to convert Strings to and from UTF8 byte arrays in Java on how to convert a byte[] to String with UTF-8 encoding.
Sending a java.lang.String over the wire is probably the wrong approach here, since Strings are always 16-bit (since Java was designed for globalization and stuff). If your radio library allows you to pass a byte[] instead of a String, that will allow you to send 8-bit values without needing to worry about converting to UTF8. As far as converting from an int to an unsigned byte, you'll probably want to look at this article.
int to array of bytes
public byte[] intToByteArray(int num){
byte[] intBytes = new byte[4];
intBytes[0] = (byte) (num >>> 24);
intBytes[1] = (byte) (num >>> 16);
intBytes[2] = (byte) (num >>> 8);
intBytes[3] = (byte) num;
return intBytes;
}
note endianness here is big endian.

Categories