Java MessageDigest providing 31 bit instead of 32 bit - java

I am trying to generate the MD5 hashcode using MessageDigest in java. Please find the sample code below. The String I am trying is combined with some code (KO00001) and system date.
String s = "KO00001"+"25/10/2012";
byte[] buffer = s.getBytes();
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(buffer);
String str = new BigInteger(1,md5.digest()).toString(16).toUpperCase();
System.out.println(str+" length:"+str.length());
I am getting the length as 32 for all the string like KO0000126/10/2012, KO0000126/10/2012. But for the string "KO0000125/10/2012" the result length is 31.
What could be the reason?

I recommend org.apache.commons.codec.digest.DigestUtils.md5Hex() for this task.

It is just because of the number, which you get from the byte-array representation of the hashcode using BigInteger, is less than others by one position. In other words, the first (high) digit in this representation is ZERO.
Just compare:
KO0000125/10/2012:
10 radix: 14105476919805249323079848858308382190 length:38
16 radix: A9C9DB4AA1D19A4126E63A7ECFD0DEE length:31
KO0000124/10/2012:
10 radix: 132805232305710448716177011414649345991 length:39
16 radix: 63E95B7C6574B57A3FD202484D0D17C7 length:32
KO0000126/10/2012:
10 radix: 256722737628903394348650044729556126214 length:39
16 radix: C123003325E6846E04656C2F3E7ECA06 length:32

md5.digest() is 16 bytes.
I suspect that you then try to output it as a 32 character hex string.
For that purpose you should do something like.
public static String toHexString(byte[] bytes) {
if (bytes == null) {
throw new IllegalArgumentException("byte array must not be null");
}
StringBuffer hex = new StringBuffer(bytes.length * 2);
for (int i = 0; i < bytes.length; i++) {
hex.append(Character.forDigit((bytes[i] & 0XF0) >> 4, 16));
hex.append(Character.forDigit((bytes[i] & 0X0F), 16));
}
return hex.toString();
}
And the result is d41d8cd98f00b204e9800998ecf8427e
(edit 1: or as Tichodroma suggested: do not re-invent the wheel, use Apache Commons Codec. Actually the answer to a lot of these questions is 'check Apache Commons first')
(edit 2: do not use getBytes() without specifying an encoding)

Related

Convert hex to INT 32 and STRING Little Endian

First question:
Hex: F1620000
After convert hex to INT 32 i expect 253229, but i get -245235712.
I've tried these methods:
Integer.parseUnsignedInt(value, 16));
(int)Long.parseLong(value, 16));
new BigInteger(value, 16).intValue());
How i can get correct value?
Second question:
Hex: 9785908D9B9885828020912E208D2E
After covert hex to STRING i can get this value:
\u0097\u0085\u0090\u008d\u009b\u0098\u0085\u0082\u0080 \u0091. \u008d.
How can I display this value correctly in json? (usning JSONObject).
StringBuilder result = new StringBuilder();
for (int i = 0; i < value.length(); i += 2) {
String str = value.substring(i, i + 2);
result.append((char)Integer.parseInt(str, 16));
}
All your attempts are sufficient for parsing a hexadecimal string in an unsigned interpretation, but did not account for the “little endian” representation. You have to reverse the bytecode manually:
String value = "F1620000";
int i = Integer.reverseBytes(Integer.parseUnsignedInt(value, 16));
System.out.println(i);
25329
For your second task, the missing information was how to interpret the bytes to get to the character content. After searching a bit, the Codepage 866 seems to be the most plausible encoding:
String value = "9785908D9B9885828020912E208D2E";
byte[] bytes = new BigInteger(value, 16).toByteArray();
String result = new String(bytes, bytes[0]==0? 1: 0, value.length()/2, "CP866");
ЧЕРНЫШЕВА С. Н.

AES encrypt any java object to a base64 string

I'm trying to encrypt any java object (in this example an Integer, but Date should also work) to a base64 string using the Cipher class.
Basically I convert the given object into a byte array using ByteArrayOutputStream and encrypt this byte array with Cipher. See below
for (Integer i = 0; i < 10; i++) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput oos = new ObjectOutputStream(bos);
oos.writeObject(i);
oos.flush();
byte[] data = bos.toByteArray();
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec("&E(H+MbQeThWmZq4".getBytes("UTF-8"), "AES"));
String base64output = Base64.getEncoder().encodeToString(cipher.doFinal(data));
System.out.println(i + " - " + base64output);
}
The output
0 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa94LOaOdEXeZZm8qNoELOLdj
1 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa97aK6ELffW8n7vEkNAbC9RW
2 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa97mJ1m8lVtjwfGbHbMO2rxu
3 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa942rroZJbe2KN0/t8ukOkWd
4 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa97rbkvF4HLzuvGTm4JMJw+2
5 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa94zvSlIQe8RQI8t5/H74ShO
6 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa97tNLWZHmR0rNkDXZtVWA2Y
7 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa94lr84KZ6MnUsPOFyJIfDTB
8 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa97e6ihJ8SXmz9sy9XXwWeAz
9 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa97neBL2tLG2TXgCI/wDuyMo
seems strange to me because of the same prefix BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa9
for every encrypted object. In this example, I'm using the same key for every object but this should not be the reason for this issue.
I've also tested this example with Strings and Dates instead of Integers. Encoding Dates into byte arrays and encrypting them with the same method also lead
into this issue having an identical prefix for all Date objects, while encoding Strings with the same method seem to work fine. Every encoded and encrypted
String leads to another encrypted base64 String. See below
Outputs for encrypted Dates: (also with identical prefix)
0 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JRj1HrbSaioOqhbM2uZi2r0
1 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JQ0q0kophfAfiPxe0U+sb1R
2 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JTeTKnbYsLo6TjfuQF9PYIk
3 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JSrDPGtepg4HWUL6VeBtWg7
4 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JS7dlSsNjnY011F2BooNnKW
5 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JStO2xPQvT76/k+xMdaDBpQ
6 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JQqz4J3yO8G9taHi7b/Zefl
7 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JR8/fOAiuGM8tO8zMcju4Xk
8 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JSMDHi6UyD5QQY1jRXNCErc
9 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JRfKstfsC8dPYuPfd9f2B+B
Outputs for encrypted Strings: (works as expected)
0 - TNpI3oLRzH5id6c/yRJlQQ==
1 - yMkm+ZuYWs4EnISo56Zljw==
2 - 03i1Lv01Nn2sGDGmtpRAIg==
3 - 5skvWbkcVXfT2TScaGxNfQ==
4 - 0p9qg5U+DqAnCBdyji+L9Q==
5 - gD5xPtAMy34xC90hKCQeWA==
6 - oQwKUhuxC5X/f6U9G9la8Q==
7 - 72cvCiLks3DDaTLAQvoVfw==
8 - wQu7Ug5RHg5egbNTI0YXQw==
9 - x1BQVwy3r6MP3SDLl/mktw==
Any idea?
Edit:
Even when I use CBC or another encryption method like DES or Blowfish the same issue occurs. I expect that every byte array from ByteArrayOutputStream should be encrypted into a completely different base64 string even if they have an identical prefix with ~90% of their length.
Using object serialization before encryption is not a great idea. Either you encrypt the data for transport protection, in which case TLS makes much more sense. Or you are encrypting for longer time storage, in which case serialization is dangerous as the serialization format could change. Heck, you might want to change the entire language / runtime in the future.
I'd suggest you generate your own protocol. In that case you can for instance simply encode an integer to 4 bytes using ByteBuffer#putInt(int) or by using DataOutputStream#writeInt(int). That way your integer just takes the minimum amount of 4 bytes (as an unsigned 32 bit big endian value). For very complex methods you may even look at ASN.1 structures and encodings (which are implemented in Bouncy Castle, among other libraries).
A Java Date is really just a long internally, which can be perfectly stored in 8 bytes. Another option is to encode it to an (UTC) date string and store that using US ASCII compatible encoding (StandardCharsets.US_ASCII).
Beware that ECB mode is very dangerous. For instance, imagine that values above 0x00FFFFFF are uncommon and that you don't want to leak the presence of such values. Also imagine that the most significant byte is the last byte of a block that is filled with header bytes otherwise. In that case it is very easy to distinguish blocks with e.g. 0x01 from blocks with 0x00 which should be more common in this situation. So you immediately leak information about your plaintext.
This problem is just as prominent in CBC mode if you use a static IV rather than a random (or at least fully unpredictable) IV value. You have to use a random IV for each CBC encryption to be secure. You can store the IV together with the ciphertext. Commonly for CBC the 16 byte IV is simply prefixed to the ciphertext. However, preferably you should be using the authenticated GCM mode with a 12 byte random nonce instead.
It's a bit of a shame that Java allows reuse of the Cipher instances at all - as it for instance doesn't allow the Cipher to destroy key material after usage. That it defaults to an insecure mode where the IV is repeated is doubly shameful. You'll have to take care of the IV issue yourself.
Example of using GCM and ByteBuffer:
public static void main(String[] args) throws Exception {
// input, a date and message
Date date = new Date();
String message = "hello world";
// AES-128 key (replace by a real 256 bit key in your case)
SecretKey aesKey = new SecretKeySpec(new byte[128 / Byte.SIZE], "AES");
// default nonce sizes for GCM, using a constant should be preferred
int nonceSize = 96;
int tagSize = 128;
String cts;
try (StringWriter stringWriter = new StringWriter(); PrintWriter out = new PrintWriter(stringWriter)) {
for (Integer i = 0; i < 10; i++) {
byte[] randomNonce = createRandomIV(nonceSize);
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, randomNonce);
byte[] encodedMessage = message.getBytes(StandardCharsets.UTF_8);
ByteBuffer encodedNumberDateAndMessage = ByteBuffer.allocate(Integer.BYTES + Long.BYTES + encodedMessage.length);
encodedNumberDateAndMessage.putInt(i);
encodedNumberDateAndMessage.putLong(date.getTime());
encodedNumberDateAndMessage.put(encodedMessage);
// for reading we need to flip the buffer
encodedNumberDateAndMessage.flip();
ByteBuffer encryptedNumberDateAndMessage =
ByteBuffer.allocate(nonceSize / Byte.SIZE + encodedNumberDateAndMessage.limit() + tagSize / Byte.SIZE);
encryptedNumberDateAndMessage.put(randomNonce);
Cipher gcm = Cipher.getInstance("AES/GCM/NoPadding");
gcm.init(Cipher.ENCRYPT_MODE, aesKey, gcmSpec);
gcm.doFinal(encodedNumberDateAndMessage, encryptedNumberDateAndMessage);
// not required, we'll be using array() method
// encryptedNumberDateAndMessage.flip();
// we can use the full array as there
String base64Ciphertext = Base64.getEncoder().encodeToString(encryptedNumberDateAndMessage.array());
if (i != 0) {
out.write('\n');
}
out.write(base64Ciphertext);
}
cts = stringWriter.toString();
}
System.out.println(cts);
// TODO decrypt ciphertexts in cts
// hint use BufferedReader to read lines and don't forget to strip off the IV/Nonce first
}
private static byte[] createRandomIV(int sizeInBits) {
if (sizeInBits % Byte.SIZE != 0) {
throw new IllegalArgumentException("Invalid IV size, must be a multiple of 8 bits");
}
byte[] randomNonce = new byte[sizeInBits / Byte.SIZE];
SecureRandom rbg = new SecureRandom();
rbg.nextBytes(randomNonce);
return randomNonce;
}
Which produces the seemingly random output:
LHMsZPgZOz7nEcN5adB03+twTG2/ITfPnUUy4DxdgFEBAxm3HNDg8eXVnuvo80i4WMjY
eRJuw1ynrD3GeMmFTYiQc6VxelJuz8wHZtbl+7cepteKdtzcsdIDcDHBqvfjyzZp6WXd
MOkTLt4pk+sFm6I+CH4c90lxrRmwFKmS1wbX5eRSZYy6xqEjSz6iGC1vBXkPbl3k1C5r
cB5hKbpiAeNmbZYy1vdK5vissWYlkL6h6XJEYEFZaK7M097LkVAB01nu5GtCBUjPMjrK
LHzr/iudU3BPYmrimAIugjSckzXrzm03Ucgyb8laKktbh/Um4K2nyAGE2+T1aLH6JaYX
dg9SmcPl+dolHSIQPyvMUEPyu3VLSNPbN7ErPY93sjfKVyZsaGgft/cP4kUzNWEyRgAo
PiLHu4TKZMfBlFXst1867hEywST3RBbSSQ1g9D4DOkqh3oPkvsXP5INIEANZr2BHta38
4pJITAvij26NphYf9/ry5yGm+qPAaNG0Hqrk5ruVa60+V7k0jqDozjsST8OygyvkLrgY
HI6I3UHgzBNjskSJeo9fS3Cw3oKY8tneFbChtLz35DbcASOjpi7U9LKTL39lBTOBaZkG
jRycn4uSfT6JlDk3jn64wTL07I7bHvTSPSbWVG7XdKeSgOibW7FiCtTXojDPi8iywD58
which consists of the nonce, the ciphertext of an integer, a long value representing the date and the "hello world" string and finally the authentication tag, which is considered part of the ciphertext in Java.
The behavior you see is the result of you using the no-mode ECB mode in combination with similarity in the plaintext. You will have the same issue with all block ciphers (AES, Blowfish, DES).
When using CBC all that goes away if you provide IVs as needed:
public class Main {
static Random rand = new SecureRandom();
public static IvParameterSpec generateIv() {
byte[] ivBytes = new byte[16];
rand.nextBytes(ivBytes);
return new IvParameterSpec(ivBytes);
}
public static void main(String[] args) throws Exception {
for (Integer i = 0; i < 10; i++) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput oos = new ObjectOutputStream(bos);
oos.writeObject(i);
oos.flush();
byte[] data = bos.toByteArray();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = generateIv();
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec("&E(H+MbQeThWmZq4".getBytes("UTF-8"), "AES"), iv);
String base64output = Base64.getEncoder().encodeToString(cipher.doFinal(data));
System.out.println(i + " - " + base64output);
}
}
}
Output:
0 - jt3Mk13pGjeaFf1oNq4LfmQ4z/31nRG4KtZ4H3RK6k/GA1anC3/lrzSXoLsQ6jMsVEpnxU13wAu6lkZJ3it1Ei4i4EsNFixc+YX4K6cIIv4ByY5Q246jd3H0m11C2FZJ
1 - Jqd0RB6lOITqifAaWluW6jx8F8gY4btZHx12CiXtZjfnehhtk64jva4eGTQd4EpvB/5Q/ORhZCNgF3ue0/Na1R9MCsK+mULAcyANdNcLyKbXo272G21z0LPCeweXdjhu
2 - xHdCG6rWNDyLTl8zruo8u+45V/RMXkrB7K+QU5r9lpc3FzvDwpl0wmy9Yj3FOyjMulmVT1zahH+wWVrmB9gNcXy7sGyCH/anJANC396OcDyQXqNIyvOPw9mpUmmRQcwR
3 - ygIDkLtQTupkbB35SzRflE3RAMmdYGSkdGZgRctFHdZCqGt+Arb3RbvhoAiiE9PwkyLmifyllQTTSutvV/ZtlGaGMX3v4bQUZDoaSyXQd9xn+pUSJk87NDVGi37xWw1O
4 - cJYSthCHHGeCqnuBJY8YdUbptKD3XNb2nt+pyIc94vRvjquYf7atu0+bDndFnePWvrlPzFIFXVB8CuANIsDhzRSNEOOU/wOkwcAN2AdavCqlZqN0Mtqdg4vqKGWx2oAE
5 - f7/gu8fJ8jkyhRAXJkLqdnJMLjCfFSjq8ovjhlNcuDPk8N/mYlA2845PGgi74Kb/zCG1WH8NtFK06xrpn15KyUxSANxoQ6C9QnzE9sc4aZj5rUatWeekvBfbqngq3JpG
6 - PitP2MuX4/Yysso8dCl1h2VK3MKoU2YpyzvLgZ3hZX/cBzSWp9O0Eafzj6GIMvAGVaL0x0V+K2Wv4eBOLIhDczhJXvHmKvTU7ZJnAwI37JXkOecN4HJdAXfFqg2WkT5f
7 - 1Mj8WnSqgLE08qfeYC1a3nZQ1jszxbT9J+ClUy8rCYusZHiArQcCgCwrNbWbI2yVfRjYOpsuTgyq31fnuHrkVfGu6RhiRhucR0a0Dign5fSU71STKksweHQ+oYQJibnQ
8 - TgGDGlOFWyfKO50xxPTPOmSpEsmpIVtWfnXkhhAoRsbZwo6z4oAuBJQs8EibsOr/r8KY5UHRbp+q3SlDhBE3mWszybMdOVRQKyJ1lZVXpmxmjXp/W2AqitsjCTKQaHi+
9 - 4xUnNjT8P0WiPtYg6ojrrQZnF0gU0wnndNQdLfPOMxoDvWjfe5OuEcY55yDRIosdpkeItTMVN1CRL4WecFgM8mBIVlnssE4Q1GM87PWNHipGZ91+MJwdsr0yUfCsJyRv
By the way you are using a 16 byte key and get AES-128 not AES-256.
As Mark pointed out, ObjectOutputStream creates an object header, so the common prefix is because of that and because you're not using a salt and you're using the same encryption key.
These weaknesses make the encryption solution (i.e. your code) susceptible to ciphertext-only attacks, even though the algorithm itself is perfectly fine. You've just implemented it in an unsecure way.

Why does my SHA-256 conversion not line up with this online tool?

I've written this simple Java snippet to SHA-256 a string:
public static void main(String[] args) throws NoSuchAlgorithmException {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
String input = "00010966776006953D5567439E5E39F86A0D273BEE";
byte[] output = sha256.digest(input.getBytes());
System.out.println(new String(output));
}
Running SHA-256 using this tool gives the output 3CC2243D50E87857A233965AA6B68B37563BFCC52B3C499FBB259B9AA87FFF40, but when I run it myself I get <�$=P�xW�3�Z���7V;��+<I��%����#. It looks like something is going wrong with the byte conversion, but I'm not exactly sure what.
You are correct that something was wrong when you tried to convert byte[] to string. Here is a code that works :)
public static void main(String[] args) throws NoSuchAlgorithmException {
final String input = "Nishit";
final MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(input.getBytes());
final byte[] data = md.digest();
StringBuilder sb = new StringBuilder(data.length * 2);
for (byte b : data) {
sb.append(String.format("%02x", b));
}
System.out.println(sb.toString());
}
What it is really happenning is that the SHA256 returns a 256-bit hash value. So what you're printing is those bytes as if they were characters and their respective character values is all that gibberish.
What the online tool is returning you is the representation of that value in hexadecimal format.
Notice that you're getting, (with the tool) 64 bytes IE 64 characters when 256-bit is equal to 32 bytes (32 charaters you may think).
That is because to represent a whole byte in hexadecimal format 2 characters are needed. 4 most significative bits take one character and the other less significative bits take another one.

How to convert byte[] to String in Java?

There are some SO quetions but no helped me. I would like to convert byte[] from org.apache.commons.codec.digest.HmacUtils to String. This code produces some weird output:
final String value = "value";
final String key = "key";
byte[] bytes = HmacUtils.hmacSha1(key, value);
String s = new String(bytes);
What am I doing wrong?
Try to use:
String st = HmacUtils.hmacSha1Hex(key, value);
First, the result of hmacSha1 would produce a digest, not not a clear String. Besides, you may have to specify an encoding format, for example
String s = new String(bytes, "US-ASCII");
or
String s = new String(bytes, "UTF-8");
For a more general solution, if you don't have HmacUtils available:
// Prepare a buffer for the string
StringBuilder builder = new StringBuilder(bytes.length*2);
// Iterate through all bytes in the array
for(byte b : bytes) {
// Convert them into a hex string
builder.append(String.format("%02x",b));
// builder.append(String.format("%02x",b).toUpperCase()); // for upper case characters
}
// Done
String s = builder.toString();
To explain your problem:
You are using a hash function. So a hash is usually an array of bytes which should look quite random.
If you use new String(bytes) you try to create a string from these bytes. But Java will try to convert the bytes to characters.
For example: The byte 65 (hex 0x41) becomes the letter 'A'. 66 (hex 0x42) the letter 'B' and so on. Some numbers can't be converted into readable characters. Thats why you see strange characters like '�'.
So new String(new byte[]{0x41, 0x42, 0x43}) will become 'ABC'.
You want something else: You want each byte converted into a 2 digit hex String (and append these strings).
Greetings!
You may need to have an encoding format. Check out this link here.
UTF-8 byte[] to String

Java ME MD5 string using bouncy castle - cannot hash multiple times

I've noticed that a lot of my Google searches took me here so i thought perhaps i could borrow your apt minds :)
I'm working on a One Time Password generator for a mobile device (as well as website to log in to) as part of my third year degree dissertation.
Using the org.bouncycastle.crypto.digests.MD5Digest library i am taking a byte array (from a string user input) then hashing it X number of times. This is also known as daisy chaining hash strings or lamports method of encryption.
My issue is that if the string is hashed once then it correctly hashes it, however if the new hash is hashed again the outcome is incorrect.
See code below:
private String generateHash(String OTP, int loopNum)
{
byte[] secretBytes = OTP.getBytes();
for (int x = 0; x < loopNum; x++)
{
byte[] tempStore = new byte[16];
tempStore = hash(secretBytes);
secretBytes = tempStore;
}
return convertToHex(secretBytes);
}
public byte[] hash(byte[] secretBytes)
{
org.bouncycastle.crypto.digests.MD5Digest digest = new org.bouncycastle.crypto.digests.MD5Digest();
digest.reset();
// Update MD5 digest with user secret in byte format
digest.update(secretBytes, 0, secretBytes.length);
// get length of digest to initialise new md5 byte array
int length = digest.getDigestSize();
// create md5 byte array using length
byte[] md5 = new byte[length];
// calculate MD5 hash, using md5 byte array, 0 for buffer offset
digest.doFinal(md5, 0);
return md5;
}
private static String convertToHex(byte[] data) {
StringBuffer buf = new StringBuffer();
String Hex;
String formattedHex;
for (int i = 0; i < data.length; i++) {
int halfbyte = (data[i] <<< 4) & 0x0F;
int two_halfs = 0;
do {
if ((0 <= halfbyte) && (halfbyte <= 9))
buf.append((char) ('0' + halfbyte));
else
buf.append((char) ('a'+ (halfbyte - 10)));
halfbyte = data[i] & 0x0F;
} while(two_halfs++ < 1);
}
Hex = buf.toString();
formattedHex = "\n" + Hex.substring(0, 4) + " " + Hex.substring(4, 8) + " " + Hex.substring(8, 12) + " "
+ Hex.substring(12, 16) + " " + Hex.substring(16, 20) + " " +Hex.substring(20, 24) + " "
+ Hex.substring(24, 28) + " " + Hex.substring(28, 32);
return formattedHex;
}
I think it is either;
The digest does not return a correct byte array
The Hex converter incorrectly converts this
Im testing using the secret of: A
which has the following MD5 Outputs:
7fc56270e7a70fa81a5935b72eacbe29
8f28f2e7231860115d2a8cacba019dbe (this should be 4cbd6d53280de25e04712c7434a70642)
Many thanks for your help in advance :)
p.s. I'm checking it against a PHP md5 could this also be an issue?
MD5, when applied to an input consisting in a single byte of value 0x41 (an 'A'), yields a 16-byte output which, when printed in hexadecimal, is 7fc56270e7a70fa81a5935b72eacbe29.
If you apply MD5 on those 16 bytes, you should get 8f28f2e7231860115d2a8cacba019dbe, and that's what you get.
Now, if you consider MD5 applied on a 32-byte string which is the ASCII encoding of the character string "7fc56270e7a70fa81a5935b72eacbe29", then this yields 4cbd6d53280de25e04712c7434a70642. So I think that your Java code is fine (for that) and that your confusion comes from how you give the input data to your PHP-based test code. You write 7fc562... and you think about it as "one byte of value 0x7f, then one byte of value 0xc5, then..." but the PHP code takes it as "one byte of value 0x37 (ASCII code for a '7'), then one byte of value 0x66 (ASCII code for an 'f'), then...".
On a Linux system, try this:
$ printf A | md5sum
7fc56270e7a70fa81a5935b72eacbe29 -
$ printf 7fc56270e7a70fa81a5935b72eacbe29 | md5sum
4cbd6d53280de25e04712c7434a70642 -
$ printf "\x7f\xc5\x62\x70\xe7\xa7\x0f\xa8\x1a\x59\x35\xb7\x2e\xac\xbe\x29" | md5sum
8f28f2e7231860115d2a8cacba019dbe -
As side notes:
Be wary of OTP.getBytes(). It converts a string into bytes by using the locale-dependent charset. This will use UTF-8, UTF-16, ISO-8859-1,... depending on the system configuration, usually tied to the "system language". Your code will act differently on the same string, which is rarely a good idea. Instead, use OTP.getBytes("UTF-8") which will compute the same bytes regardless of the local configuration.
Your hash loop includes useless mantras. For instance, you allocate a 16-byte array, which you never use.
It is considered bad coding-style in Java to have a variable name beginning with an uppercase letter. If you plan on showing your code in a school context then you should rename Hex into hex.
When halfByte is obtained as the result of a "& 0x0F" then it necessarily contains a value between 0 and 15. The "0 <= halfByte" test is unnecessary.
Thank you all very much for your help, the problem was that my java md5 did not behave like a php MD5.
I found a solution to the problem which basically (in Java) take the byte array and convert it into a hex string and THEN get the bytes for this string which is then MD5'd rather than using the un hex'd byte array. See solutions below
See the following for the result: http://forums.sun.com/thread.jspa?forumID=9&threadID=718781
static String byteArrayToHexString(byte byteValues[]) {
byte singleChar = 0;
if (byteValues == null || byteValues.length <= 0)
return null;
String entries[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"a", "b", "c", "d", "e", "f" };
StringBuffer out = new StringBuffer(byteValues.length * 2);
for (int i = 0; i < byteValues.length; i++) {
singleChar = (byte) (byteValues[i] & 0xF0);
singleChar = (byte) (singleChar >>> 4);
// shift the bits down
singleChar = (byte) (singleChar & 0x0F);
out.append(entries[(int) singleChar]);
singleChar = (byte) (byteValues[i] & 0x0F);
out.append(entries[(int) singleChar]);
}
String rslt = new String(out);
return rslt;
}
Thanks a lot for all those who posted, cant thank you enough!

Categories

Resources