/*
* Copyright (C) 2010-2011 Ciaran Gultnieks <ciaran@ciarang.com>
* Copyright (C) 2011 Henrik Tunedal <tunedal@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
package org.fdroid.fdroid;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.util.Locale;
public class Hasher {
private MessageDigest digest;
private File file;
private byte[] array;
private String hashCache;
public Hasher(String type, File f) throws NoSuchAlgorithmException {
init(type);
this.file = f;
}
public Hasher(String type, byte[] a) throws NoSuchAlgorithmException {
init(type);
this.array = a;
}
private void init(String type) throws NoSuchAlgorithmException {
try {
digest = MessageDigest.getInstance(type);
} catch (Exception e) {
throw new NoSuchAlgorithmException(e);
}
}
// Calculate hash (as lowercase hexadecimal string) for the file
// specified in the constructor. This will return a cached value
// on subsequent invocations. Returns the empty string on failure.
public String getHash() {
if (hashCache != null) {
return hashCache;
}
if (file != null) {
byte[] buffer = new byte[1024];
int read;
InputStream input = null;
try {
input = new BufferedInputStream(new FileInputStream(file));
while ((read = input.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
} catch (Exception e) {
hashCache = "";
return hashCache;
} finally {
Utils.closeQuietly(input);
}
} else {
digest.update(array);
}
hashCache = hex(digest.digest());
return hashCache;
}
// Compare the calculated hash to another string, ignoring case,
// returning true if they are equal. The empty string and null are
// considered non-matching.
public boolean match(String otherHash) {
if (otherHash == null) {
return false;
}
if (hashCache == null) {
getHash();
}
return hashCache.equals(otherHash.toLowerCase(Locale.ENGLISH));
}
/**
* Checks the file against the provided hash, returning whether it is a match.
*/
public static boolean isFileMatchingHash(File file, String hash, String hashType) {
if (!file.exists()) {
return false;
}
try {
Hasher hasher = new Hasher(hashType, file);
return hasher.match(hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static String hex(Certificate cert) {
byte[] encoded;
try {
encoded = cert.getEncoded();
} catch (CertificateEncodingException e) {
encoded = new byte[0];
}
return hex(encoded);
}
private static String hex(byte[] sig) {
byte[] csig = new byte[sig.length * 2];
for (int j = 0; j < sig.length; j++) {
byte v = sig[j];
int d = (v >> 4) & 0xf;
csig[j * 2] = (byte) (d >= 10 ? ('a' + d - 10) : ('0' + d));
d = v & 0xf;
csig[j * 2 + 1] = (byte) (d >= 10 ? ('a' + d - 10) : ('0' + d));
}
return new String(csig);
}
public static byte[] unhex(String data) {
byte[] rawdata = new byte[data.length() / 2];
for (int i = 0; i < data.length(); i++) {
char halfbyte = data.charAt(i);
int value;
if ('0' <= halfbyte && halfbyte <= '9') {
value = halfbyte - '0';
} else if ('a' <= halfbyte && halfbyte <= 'f') {
value = halfbyte - 'a' + 10;
} else if ('A' <= halfbyte && halfbyte <= 'F') {
value = halfbyte - 'A' + 10;
} else {
throw new IllegalArgumentException("Bad hex digit");
}
rawdata[i / 2] += (byte) (i % 2 == 0 ? value << 4 : value);
}
return rawdata;
}
}