Google OTP Generation Java - java

I am currently trying to recreate a Google One Time Password generator. I use a shared secret generated when I setup Google Authenticator.
I tried looking into the Google Authenticator sources and all around the internet really and I find a lot of similarities with my code but I can't really find where i'm wrong.
The first part seems correct. As for the hmac, I don't think I could mess up here but I might be wrong. The truncating part is still a bit blurry for me and I tried a lot of different implementations but I just cannot get a working OTP. (I'm using Google Authenticator to compare the results)
private String truncateHash(byte[] hash) {
int offset = hash[hash.length - 1] & 0xF;
long truncatedHash = 0;
for (int i = 0; i < 4; ++i) {
truncatedHash <<= 8;
truncatedHash |= (hash[offset + i] & 0xFF);
}
truncatedHash &= 0x7FFFFFFF;
truncatedHash %= 1000000;
int code = (int) truncatedHash;
String result = Integer.toString(code);
for (int i = result.length(); i < 6; i++) {
result = "0" + result;
}
return result;
}
private byte[] hmacSha1(byte[] value, byte[] keyBytes) {
try {
Mac mac = HmacUtils.getHmacSha1(keyBytes);
byte[] rawHmac = mac.doFinal(value);
return new Hex().encode(rawHmac);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String GoogleAuthenticatorCode(String secret) throws UnsupportedEncodingException {
Base32 base = new Base32();
byte[] key = base.decode(secret);
//Update from Andrew Rueckert's response
long value = new Date().getTime() / TimeUnit.SECONDS.toMillis(30);
byte[] data = new byte[8];
for (int i = 8; i-- > 0; value >>>= 8) {
data[i] = (byte) value;
}
//
System.out.println("Time remaining : " + new Date().getTime() / 1000 % 30);
byte[] hash = hmacSha1(data, key);
return truncateHash(hash);
}
UPDATE :
I tried copying and pasting the code from Andrew Rueckert's response's link as well as this one https://github.com/wstrange/GoogleAuth/blob/master/src/main/java/com/warrenstrange/googleauth/GoogleAuthenticator.java and the one from RFC 4226. Neither of these give me a correct OTP
Can anyone enlighten me please?

Your byte value[] needs to be the byte representation of the time as a long, and it looks like it's currently the byte representation of that number as a String of digit characters. Instead of
Double time = floor(new Date().getTime() / 1000 / 30);
String message = String.valueOf(time.intValue());
byte[] value = message.getBytes("UTF-8");
byte[] hash = hmacSha1(value, key);
You'd want something like:
// decimal truncation is free when dealing with int/long
long value = new Date().getTime() / 1000 / 30;
byte[] data = new byte[8];
for (int i = 8; i-- > 0; value >>>= 8) {
data[i] = (byte) value;
}
byte[] hash = hmacSha1(data, key);
I managed to get a Google TOTP implementation set up by following this guide, if you want one more resource to look into.

I solved my problem so I thought I would post it there in case someone needs it.
It was partialy due to the Base32 class I was using which didn't return a correct key. The truncating wasn't correct either.
It's compatible with Google Authenticator app.
import org.apache.commons.codec.binary.Hex;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class Authentication {
Authentication() {};
private String truncateHash(byte[] hash) {
String hashString = new String(hash);
int offset = Integer.parseInt(hashString.substring(hashString.length() - 1, hashString.length()), 16);
String truncatedHash = hashString.substring(offset * 2, offset * 2 + 8);
int val = Integer.parseUnsignedInt(truncatedHash, 16) & 0x7FFFFFFF;
String finalHash = String.valueOf(val);
finalHash = finalHash.substring(finalHash.length() - 6, finalHash.length());
return finalHash;
}
private byte[] hmacSha1(byte[] value, byte[] keyBytes) {
SecretKeySpec signKey = new SecretKeySpec(keyBytes, "HmacSHA1");
try {
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signKey);
byte[] rawHmac = mac.doFinal(value);
return new Hex().encode(rawHmac);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String GoogleAuthenticatorCode(String secret) throws Exception {
if (secret == null || secret == "") {
throw new Exception("Secret key does not exist.");
}
long value = new Date().getTime() / TimeUnit.SECONDS.toMillis(30);
Base32 base = new Base32(Base32.Alphabet.BASE32, false, true);
byte[] key = base.fromString(secret);
byte[] data = new byte[8];
for (int i = 8; i-- > 0; value >>>= 8) {
data[i] = (byte) value;
}
byte[] hash = hmacSha1(data, key);
return truncateHash(hash);
}
}
The Base32 I used is available here if needed along with the rest of the project : https://github.com/Poncholay/OTPGenerator/blob/master/src/main/java/com/requireris/app/web/rest/Base32.java

Related

Java 256-bit AES Encryption

I need to implement 256 bit AES encryption for cash flow i have c# answer but the answer is not the same,for a newbie, I am not sure if my direction is correct.
this is my code
public static void main(String[] args) {
String key = "12345678901234567890123456789012";
String hashIv = "1234567890123456";
String value = "MerchantID=3430112RespondType=JSONTimeStamp=1485232229Version=1.4MerchantOrderNo=S_1485232229Amt=40ItemDesc=UnitTest";
String result = encrypt(key, hashIv, value);
System.out.println(result);
System.out.println();
String sha256 = encrySha256("HashKey=" + key + "&" + result + "&HashIV=" + hashIv);
System.out.println(sha256.trim());
}
public static String encrypt(String hashKey, String hashIv, String text) {
try {
SecretKeySpec skeySpec = new SecretKeySpec(hashKey.getBytes("UTF-8"), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(hashIv.getBytes("UTF-8"));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivParameterSpec);
byte[] encrypted = cipher.doFinal((text.getBytes("UTF-8")));
String test = bytesToHex(encrypted);
return test.toLowerCase();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return null;
}
public static String bytesToHex(byte[] bytes) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
sb.append(hex.toUpperCase());
}
return sb.toString();
}
public static String encrySha256(String value) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(value.getBytes());
byte byteBuffer[] = messageDigest.digest();
StringBuffer strHexString = new StringBuffer();
for (int i = 0; i < byteBuffer.length; i++) {
String hex = Integer.toHexString(0xff & byteBuffer[i]);
if (hex.length() == 1) {
strHexString.append('0');
}
strHexString.append(hex);
}
return strHexString.toString().toUpperCase();
} catch (Exception e) {
}
return null;
}
sample encrypt answer :
ff91c8aa01379e4de621a44e5f11f72e4d25bdb1a18242db6cef9ef07d80b0165e476fd1d
9acaa53170272c82d122961e1a0700a7427cfa1cf90db7f6d6593bbc93102a4d4b9b66d9
974c13c31a7ab4bba1d4e0790f0cbbbd7ad64c6d3c8012a601ceaa808bff70f94a8efa5a4f
984b9d41304ffd879612177c622f75f4214fa
encryptSha256 answer : EA0A6CC37F40C1EA5692E7CBB8AE097653DF3E91365E6A9CD7E91312413C7BB8
this is c# code and this is sample data
[MerchantID] => 3430112 [RespondType] => JSON [TimeStamp] => 1485232229
[Version] => 1.4 [MerchantOrderNo] => S_1485232229 [Amt] => 40 [ItemDesc] =>
UnitTest
public string EncryptAES256(string source)//加密
{
string sSecretKey = "12345678901234567890123456789012";
string iv = "1234567890123456";
byte[] sourceBytes =
AddPKCS7Padding(Encoding.UTF8.GetBytes(source), 32);
var aes = new RijndaelManaged();
aes.Key = Encoding.UTF8.GetBytes(sSecretKey);
aes.IV = Encoding.UTF8.GetBytes(iv);
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.None;
ICryptoTransform transform = aes.CreateEncryptor();
return ByteArrayToHex(transform.TransformFinalBlock(sourceBytes, 0,
sourceBytes.Length)).ToLower();
}
private static byte[] AddPKCS7Padding(byte[] data, int iBlockSize)
{
int iLength = data.Length;
byte cPadding = (byte)(iBlockSize - (iLength % iBlockSize));
var output = new byte[iLength + cPadding];
Buffer.BlockCopy(data, 0, output, 0, iLength);
for (var i = iLength; i < output.Length; i++)
output[i] = (byte)cPadding;
return output;
}
private static string ByteArrayToHex(byte[] barray)
{
char[] c = new char[barray.Length * 2];
byte b;
for (int i = 0; i < barray.Length; ++i)
{
b = ((byte)(barray[i] >> 4));
c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
b = ((byte)(barray[i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
}
return new string(c);
}
The reason for the different encrypted data is that you compare different plain texts. In your Java code you encrypt the plain text
MerchantID=3430112RespondType=JSONTimeStamp=1485232229Version=1.4MerchantOrderNo=S_1485232229Amt=40ItemDesc=UnitTest
and you compare the encrypted data with your reference data
ff91c8aa01379e4de621a44e5f11f72e4d25bdb1a18242db6cef9ef07d80b0165e476fd1d9acaa53170272c82d122961e1a0700a7427cfa1cf90db7f6d6593bbc93102a4d4b9b66d9974c13c31a7ab4bba1d4e0790f0cbbbd7ad64c6d3c8012a601ceaa808bff70f94a8efa5a4f984b9d41304ffd879612177c622f75f4214fa
However, these reference data correspond to a different plain text. The latter you can easily derive by decrypting the reference data with the C# DecryptAES256-method which provides
MerchantID=3430112&RespondType=JSON&TimeStamp=1485232229&Version=1.4&MerchantOrderNo=S_1485232229&Amt=40&ItemDesc=UnitTest
Here, in contrast to the plain text in your Java code, a &-delimiter is used.
If you use the same plain text the Java encrypt- and the C# EncryptAES256-method provide the same encrypted data (same key, IV and padding supposed; for the latter see EDIT-section).
In the following testcase the plain text from your Java code is used:
encrypt("12345678901234567890123456789012", "1234567890123456", "MerchantID=3430112RespondType=JSONTimeStamp=1485232229Version=1.4MerchantOrderNo=S_1485232229Amt=40ItemDesc=UnitTest")
and
EncryptAES256("MerchantID=3430112RespondType=JSONTimeStamp=1485232229Version=1.4MerchantOrderNo=S_1485232229Amt=40ItemDesc=UnitTest")
both provide the encrypted data:
ff91c8aa01379e4de621a44e5f11f72ef45b7b9f9663d386da51af13f7f3b8f2b1ed4a3b7ac6b7783402193ea1d766e3046b6acf612d62568ccdbc475e5a14d114273735b069464dcc8281f4e5bf8486eb97d31602c3fe79cfe7140d2848413edad9d96fabf54d103f3d7a9b401c83fa5e4f17b10a280df10b3d61f23e69bbb8
which (as expected) differs from your reference data (with the exception of the first block).
EDIT
There is a second issue concerning the padding: Your C# EncryptAES256-method uses a custom padding provided by the C# AddPKCS7Padding-method which pads to a multiple of 32 bytes.
In contrast, your Java encrypt-method uses PKCS5Padding which pads to a multiple of 16 bytes.
Thus, the encrypted data of the Java encrypt- and the C# EncryptAES256-method differ if the length of the plain text is between 16 * n byte and 16 * (n + 1) - 1 byte with even n (0,2,4,...).
For odd n (1,3,5,...) the encrypted data are identical. In the example above the byte array of the plain text has 116 bytes that is n = 7 (112 <= 116 <= 127) and therefore the encrypted data are the same.
If the Java encrypt-method should use the same padding as the C# EncryptAES256-method you additionally have to implement an analogous Java-method e.g.:
private static byte[] addPKCS7Padding(byte[] data, int iBlockSize)
{
int iLength = data.length;
byte cPadding = (byte)(iBlockSize - (iLength % iBlockSize));
byte[] output = new byte[iLength + cPadding];
System.arraycopy(data, 0, output, 0, iLength);
for (int i = iLength; i < output.length; i++)
output[i] = (byte)cPadding;
return output;
}
and in the Java encrypt-method you have to replace:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
with
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
and also
byte[] encrypted = cipher.doFinal(text.getBytes("UTF-8"));
with
byte[] encrypted = cipher.doFinal(addPKCS7Padding(text.getBytes("UTF-8"), 32));
before:
MerchantID=3430112RespondType=JSONTimeStamp=1485232229Version=1.4MerchantOrderNo=S_1485232229Amt=40ItemDesc=UnitTest
After:
MerchantID=3430112&RespondType=JSON&TimeStamp=1485232229&Version=1.4&MerchantOrderNo=S_1485232229&Amt=40&ItemDesc=UnitTest
I only add "&" with each parameter, it's work!!!!
try it!!!
(我只加了&就成功拉,你的代碼沒問題,只是參數要加&而已)

How can I implement RSACryptoServiceProvider c# code into JAVA?

This is C# code. I want to implement it in JAVA exactly.
The result of encryption is as same as C#. If you know about it. Please keep me posted!!
public static byte[] HexStringToByteArray(string hex)
{
int hexLen = hex.Length;
byte[] ret = new byte[hexLen / 2];
for (int i = 0; i < hexLen; i += 2)
{
ret[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
}
return ret;
}
public string doEncryption(string password)
{
// If needed, working sample values for mod and exp can be provided
string mod = "9e5787bead673a363178b9437030edf4ea6e26640c1f318e447d5685ddcf582002d15f459b392abffe0fd6001cb1f289f14be8b67da21e7982edf72b15ff8f7859e4c6bbb26abc99eb58ead851b4eec9f7a2a9b0c3dca3339c03f1c1a9a6d9a458728f8eb129cb52dee0606e5e6ffbfbf0fa777f8b3ca04a63da66ce4ef32b5aad4ee11928b8c3a6af10fedc2b76b0d7afc3b5c53d8572ee04cd16fcc364aa8486d53d4b773a20a2628f3db4483eea00fc441755e061b1184f2a21bd8753fb33982330eb0fc94f30960640dfa3876ddf7d8ef907f9eff23b67a225d40d6c15dea90649f51fd113c9f39467961962f1b9992140e3c10b003f0afeb8c95eec7235";
string exp = "010001";
RNGCryptoServiceProvider secureRandom = new RNGCryptoServiceProvider(); // Import System.Security.Cryptography
byte[] encryptedPasswordBytes;
using (var rsaEncryptor = new RSACryptoServiceProvider())
{
var passwordBytes = Encoding.ASCII.GetBytes(password);
var rsaParameters = rsaEncryptor.ExportParameters(false);
rsaParameters.Exponent = HexStringToByteArray(exp);
rsaParameters.Modulus = HexStringToByteArray(mod);
rsaEncryptor.ImportParameters(rsaParameters);
encryptedPasswordBytes = rsaEncryptor.Encrypt(passwordBytes, false);
}
// Encrypted password should be identical in the C# and Java versions
string encryptedPassword = Convert.ToBase64String(encryptedPasswordBytes);
return encryptedPassword;
}

Android MD5 issue calculating MD5, missing characters

I've been developing an Android App and in certain part of the app I need to calculate the MD5 of a certain string. I've been using the following code, but every now and then the output string if the byte it has to convert to String is lower than 10, then it will miss a 0 in the two byte representation:
MessageDigest di = java.security.MessageDigest.getInstance("MD5");
di.update(cadena.getBytes());
byte mdi[] = di.digest();
StringBuffer md5= new StringBuffer();
for (byte b : mdi) {
md5.append(Integer.toHexString(0xFF & b));
}
For example, if I pass the string 109370 the MD5 it will have to return is 932ff0696b0434d7a83e1ff84fe298c5 but instead it calculates the 932ff0696b434d7a83e1ff84fe298c5.
That's because the byte array has a 4 and Integer.toHexString() is returning only 1 char array instead of two.
Any thought about how can I handle this?
Thanks!
below is the code that i am using:
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Encode {
private static String convertedToHex(byte[] data) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < data.length; i++) {
int halfOfByte = (data[i] >>> 4) & 0x0F;
int twoHalfBytes = 0;
do {
if ((0 <= halfOfByte) && (halfOfByte <= 9)) {
buf.append((char) ('0' + halfOfByte));
} else {
buf.append((char) ('a' + (halfOfByte - 10)));
}
halfOfByte = data[i] & 0x0F;
} while (twoHalfBytes++ < 1);
}
return buf.toString();
}
public static String MD5(String text) throws NoSuchAlgorithmException,
UnsupportedEncodingException {
MessageDigest md;
md = MessageDigest.getInstance("MD5");
byte[] md5 = new byte[64];
md.update(text.getBytes("iso-8859-1"), 0, text.length());
md5 = md.digest();
return convertedToHex(md5);
}
}
and use it by this way:
MD5Encode.MD5("your string here")
hope this will help you :)
You can use a java.util.Formatter:
Formatter fmt = new Formatter(md5);
for (byte b : mdi) {
fmt.format("%02x", b&0xff);
}
fmt.close();
Use this:
public String calculateMD5(String string) {
StringBuilder result = new StringBuilder();
try {
MessageDigest m = MessageDigest.getInstance("MD5");
m.update(string.getBytes("UTF8"));
byte s[] = m.digest();
for (int i = 0; i < s.length; i++) {
result.append(Integer.toHexString((0x000000ff & s[i]) | 0xffffff00).substring(6));
}
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Password hash is unsupported by device android implementation.", e);
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Password hash is unsupported by device android implementation.", e);
}
return result.toString();
}

PBEKeySpec with byte array argument instead of ASCII

I would like to know if there is a way to use PBEKeySpec with a byte array argument.
Please find a link to the documentation below:
http://docs.oracle.com/javase/1.7/docs/api/javax/crypto/spec/PBEKeySpec.html)
Here below is my solution: I got it googling around. Please consider I have to internally copy the password and the salt since they have another format when they come from the outside, but the result is the same. It seems it works and solves the problem of having a password as byte[] and not as char[] (it was driving me crazy)
I hope it helps!
Cheers, Soosta
public class Pbkdf2 {
public Pbkdf2() {
}
public void GenerateKey(final byte[] masterPassword, int masterPasswordLen,
final byte[] salt, int saltLen,
int iterationCount, int requestedKeyLen,
byte[] generatedKey) {
byte[] masterPasswordInternal = new byte[masterPasswordLen];
System.arraycopy(masterPassword, 0, masterPasswordInternal, 0, masterPasswordLen);
byte[] saltInternal = new byte[saltLen];
System.arraycopy(salt, 0, saltInternal, 0, saltLen);
SecretKeySpec keyspec = new SecretKeySpec(masterPasswordInternal, "HmacSHA1");
Mac prf = null;
try {
prf = Mac.getInstance("HmacSHA1");
prf.init(keyspec);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
int hLen = prf.getMacLength(); // 20 for SHA1
int l = Math.max(requestedKeyLen, hLen); // 1 for 128bit (16-byte) keys
int r = requestedKeyLen - (l - 1) * hLen; // 16 for 128bit (16-byte) keys
byte T[] = new byte[l * hLen];
int ti_offset = 0;
for (int i = 1; i <= l; i++) {
F(T, ti_offset, prf, saltInternal, iterationCount, i);
ti_offset += hLen;
}
System.arraycopy(T, 0, generatedKey, 0, requestedKeyLen);
}
private static void F(byte[] dest, int offset, Mac prf, byte[] S, int c, int blockIndex) {
final int hLen = prf.getMacLength();
byte U_r[] = new byte[hLen];
// U0 = S || INT (i);
byte U_i[] = new byte[S.length + 4];
System.arraycopy(S, 0, U_i, 0, S.length);
INT(U_i, S.length, blockIndex);
for (int i = 0; i < c; i++) {
U_i = prf.doFinal(U_i);
xor(U_r, U_i);
}
System.arraycopy(U_r, 0, dest, offset, hLen);
}
private static void xor(byte[] dest, byte[] src) {
for (int i = 0; i < dest.length; i++) {
dest[i] ^= src[i];
}
}
private static void INT(byte[] dest, int offset, int i) {
dest[offset + 0] = (byte) (i / (256 * 256 * 256));
dest[offset + 1] = (byte) (i / (256 * 256));
dest[offset + 2] = (byte) (i / (256));
dest[offset + 3] = (byte) (i);
}
}
I had to implement a two-phase pbkdf2 derivation (so the second pbkdf2 had bytes from the first as input). I ended up using BouncyCastle because I just couldn't get the byte array to char array gymnastics to work. Credit to Pasi from this other question: Reliable implementation of PBKDF2-HMAC-SHA256 for JAVA
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.digests.GeneralDigest;
import org.bouncycastle.crypto.params.KeyParameter;
GeneraDigest algorithm = new SHA256Digest();
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(algorithm);
gen.init(passwordBytes, salt, iterations);
byte[] dk = ((KeyParameter) gen.generateDerivedParameters(256)).getKey();
As the Java PKCS#5 KeyFactory has been specified to only use the lower 8 bits of the characters in the PBEKeySpec, you should be able to convert your byte array into a (16 bit) character array without issue. Just copy the value of each byte into the character array and you should be set.
Just to be sure, I would perform charArray[i] = byteArray[i] & 0xFF as assignment statement, otherwise you would get very high valued characters.
It's an ugly workaround, but I don't see any reason why it should not work.
Note that the above assumes Latin / Windows 1252 compatible encoding for values 0x80 and over. If you allow code points of 0x80 to 0xFF then you cannot use UTF-8 (or UTF-16 of course) as encoding.
I was able to do this using a 3rd party library and extending one of their classes.
Here is the RFC 2898 implementation library that I used:
http://www.rtner.de/software/PBKDF2.html
My code:
import de.rtner.security.auth.spi.PBKDF2Engine;
import de.rtner.security.auth.spi.PBKDF2Parameters;
public class PBKDF2Utils {
private static class PBKDF2EngineWithBinaryPassword extends PBKDF2Engine {
private PBKDF2EngineWithBinaryPassword(PBKDF2Parameters parameters) {
super(parameters);
}
public byte[] deriveKey(byte[] inputPassword, int dkLen) {
this.assertPRF(inputPassword);
return this.PBKDF2(prf, parameters.getSalt(), parameters.getIterationCount(), dkLen);
}
}
public static byte[] deriveKey(
byte[] password,
byte[] salt,
int iterationCount,
int dkLen) {
PBKDF2Parameters parameters = new PBKDF2Parameters("HmacSHA1", null, salt, iterationCount);
PBKDF2EngineWithBinaryPassword engine = new PBKDF2EngineWithBinaryPassword(parameters);
return engine.deriveKey(password, dkLen);
}
}

Make SHA1 encryption on Android?

Can you suggest me about how to encrypt string using SHA1 algorithm ?
I've searched about it. But no luck.
Thanks in advance.
binnyb's convertToHex method is not working properly. A more correct one that works for me is:
private static String convertToHex(byte[] data) {
StringBuffer buf = new StringBuffer();
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);
}
return buf.toString();
}
public static String SHA1(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] sha1hash = new byte[40];
md.update(text.getBytes("iso-8859-1"), 0, text.length());
sha1hash = md.digest();
return convertToHex(sha1hash);
}
use the SHA1 method to get your sha1 string.
Update: providing a complete answer
here are 2 methods i have found while searching for a sha1 algorithm implementation:
private static String convertToHex(byte[] data) {
StringBuffer buf = new StringBuffer();
int length = data.length;
for(int i = 0; i < 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);
}
return buf.toString();
}
public static String SHA1(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] sha1hash = new byte[40];
md.update(text.getBytes("iso-8859-1"), 0, text.length());
sha1hash = md.digest();
return convertToHex(sha1hash);
}
use the SHA1 method to get your sha1 string. I have not confirmed that this is indeed a sha1, but it works for my apps.
I've answered this before (How to SHA1 hash a string in Android?) but it fits here, as well:
Android comes with Apache's Commons Codec so you can simply use the following line to create a SHA-1 hexed String:
String myHexHash = DigestUtils.shaHex(myFancyInput);
That is the old deprecated method you get with Android 4 by default. The new versions of DigestUtils bring all flavors of shaHex() methods like sha256Hex() and also overload the methods with different argument types.
Of course, there is more functionality in DigestUtils and the rest of Commons Codec. Just have a look.
http://commons.apache.org/proper/commons-codec//javadocs/api-release/org/apache/commons/codec/digest/DigestUtils.html
EDIT:
If you get a ClassNotFoundError you will have to explicitly add commons-codec as dependency (even though it should come with Android as transitive dependency), in Maven e.g.:
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.7</version>
</dependency>
And also, you will have to change the call to:
String myHexHash = new String(Hex.encodeHex(DigestUtils.sha512(myFancyInput)));
(My humble guess is that this is probably due to a ClassLoader issue (class name collision) in the Android VM - which would actually prove that the commons-codec classes are already present...)
See also:
https://stackoverflow.com/a/9284092/621690
binnyb set me on the right track, but I found some more, easier to understand code here:
http://www.coderanch.com/t/526487/java/java/Java-Byte-Hex-String
private static String convertToHex(byte[] data) {
StringBuilder sb = new StringBuilder(data.length * 2);
Formatter fmt = new Formatter(sb);
for (byte b : data) {
fmt.format("%02x", b);
}
return sb.toString();
}

Categories