Directly load the pre-processed bloom filters

No more conversion, much quicker loading, much smaller downloads

Signed-off-by: Tad <tad@spotco.us>
This commit is contained in:
Tad 2023-12-21 23:26:19 -05:00
parent a004d22c07
commit 1c29038125
No known key found for this signature in database
GPG key ID: B286E9F57A07424B
13 changed files with 184 additions and 160 deletions

1
.idea/misc.xml generated
View file

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">

View file

@ -6,8 +6,8 @@ android {
applicationId "us.spotco.malwarescanner"
minSdkVersion 19
targetSdkVersion 32
versionCode 110
versionName "2.34"
versionCode 112
versionName "2.35"
resConfigs 'en', 'af', 'de', 'el', 'es', 'fi', 'fr', 'it', 'pl', 'pt', 'ru', 'tr', 'zh-rCN'
}
buildTypes {

View file

@ -26,12 +26,9 @@ import android.widget.TextView;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
@ -40,7 +37,6 @@ import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.util.Date;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.zip.GZIPInputStream;
class Database {
@ -52,11 +48,8 @@ class Database {
public final static ConcurrentLinkedQueue<SignatureDatabase> signatureDatabases = new ConcurrentLinkedQueue<>();
public static BloomFilter<String> signaturesMD5 = null;
public static int amtSignaturesMD5 = 0;
public static BloomFilter<String> signaturesSHA1 = null;
public static int amtSignaturesSHA1 = 0;
public static BloomFilter<String> signaturesSHA256 = null;
public static int amtSignaturesSHA256 = 0;
private static final DateFormat dateFormat = DateFormat.getDateInstance();
@ -69,14 +62,9 @@ class Database {
}
public static boolean isDatabaseLoaded() {
return areDatabasesAvailable() && databaseFullyLoaded && (amtSignaturesMD5 > 0 || amtSignaturesSHA1 > 0 || amtSignaturesSHA256 > 0);
return areDatabasesAvailable() && databaseFullyLoaded;
}
public static int getSignatureCount() {
return amtSignaturesMD5 + amtSignaturesSHA1 + amtSignaturesSHA256;
}
public static void updateDatabase(Context context, ConcurrentLinkedQueue<SignatureDatabase> signatureDatabases) {
initDatabase(context);
log.append(context.getString(R.string.main_database_updating, String.valueOf(signatureDatabases.size())) + "\n");
@ -97,37 +85,11 @@ class Database {
databasePath.mkdir();
signatureDatabases.clear();
prefs = context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
String baseURL = Utils.getDatabaseURL(context);
if (prefs.getBoolean("SIGNATURES_TARGETEDTHREATS", true)) {
signatureDatabases.add(new SignatureDatabase(baseURL, "targetedthreats.hdb.gz"));
signatureDatabases.add(new SignatureDatabase(baseURL, "targetedthreats.hsb.gz"));
}
if (prefs.getBoolean("SIGNATURES_AMNESTY", true)) {
signatureDatabases.add(new SignatureDatabase(baseURL, "amnesty.hsb.gz"));
}
if (prefs.getBoolean("SIGNATURES_STALKERWARE", true)) {
signatureDatabases.add(new SignatureDatabase(baseURL, "stalkerware.hsb.gz"));
}
if (prefs.getBoolean("SIGNATURES_ESET", true)) {
signatureDatabases.add(new SignatureDatabase(baseURL, "eset.hdb.gz"));
signatureDatabases.add(new SignatureDatabase(baseURL, "eset.hsb.gz"));
}
if (prefs.getBoolean("SIGNATURES_MALWAREBAZAAR", false)) {
signatureDatabases.add(new SignatureDatabase(baseURL, "malware_bazaar-full.hsb.gz"));
} else if (prefs.getBoolean("SIGNATURES_MALWAREBAZAAR-ANDROID", true)) {
signatureDatabases.add(new SignatureDatabase(baseURL, "malware_bazaar.hsb.gz"));
}
if (prefs.getBoolean("SIGNATURES_CLAMAV", false)) {
signatureDatabases.add(new SignatureDatabase(baseURL, "main.hdb.gz"));
signatureDatabases.add(new SignatureDatabase(baseURL, "main.hsb.gz"));
signatureDatabases.add(new SignatureDatabase(baseURL, "daily.hdb.gz"));
signatureDatabases.add(new SignatureDatabase(baseURL, "daily.hsb.gz"));
} else if (prefs.getBoolean("SIGNATURES_CLAMAV-ANDROID", true)) {
signatureDatabases.add(new SignatureDatabase(baseURL, "Android.hdb.gz"));
signatureDatabases.add(new SignatureDatabase(baseURL, "Android.hsb.gz"));
}
signatureDatabases.add(new SignatureDatabase(baseURL, "hypatia-md5-bloom.bin"));
signatureDatabases.add(new SignatureDatabase(baseURL, "hypatia-sha1-bloom.bin"));
signatureDatabases.add(new SignatureDatabase(baseURL, "hypatia-sha256-bloom.bin"));
}
public static void loadDatabase(Context context, boolean forceReload, ConcurrentLinkedQueue<SignatureDatabase> signatureDatabases) {
@ -135,14 +97,6 @@ class Database {
databaseFullyLoaded = false;
databaseCurrentlyLoading = true;
initDatabase(context);
//XXX: These must be kept in sync with the databases
signaturesMD5 = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8), 200000, 0.001); //200k
amtSignaturesMD5 = 0;
signaturesSHA1 = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8), 5000000, 0.001); //5m
amtSignaturesSHA1 = 0;
signaturesSHA256 = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8), 1000000, 0.001); //1m
amtSignaturesSHA256 = 0;
System.gc();
File publicKey = new File(databasePath + "/gpg.key");
GPGDetachedSignatureVerifier verifier = new GPGDetachedSignatureVerifier(Utils.getSigningKey(context));
for (SignatureDatabase database : signatureDatabases) {
@ -153,41 +107,22 @@ class Database {
boolean validated = verifier.verify(databaseLocation, databaseSigLocation, publicKey);
if (validated) {
Log.d("Hypatia", "Successfully validated database");
BufferedReader reader;
if (databaseLocation.getName().endsWith(".gz")) {
reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(databaseLocation))));
} else {
reader = new BufferedReader(new FileReader(databaseLocation));
FileInputStream databaseLoading = new FileInputStream(databaseLocation);
switch (databaseLocation.getName()) {
case "hypatia-md5-bloom.bin":
Log.d("Hypatia", "Processing md5");
signaturesMD5 = BloomFilter.readFrom(databaseLoading, Funnels.stringFunnel(StandardCharsets.US_ASCII));
break;
case "hypatia-sha1-bloom.bin":
Log.d("Hypatia", "Processing sha1");
signaturesSHA1 = BloomFilter.readFrom(databaseLoading, Funnels.stringFunnel(StandardCharsets.US_ASCII));
break;
case "hypatia-sha256-bloom.bin":
Log.d("Hypatia", "Processing sha256");
signaturesSHA256 = BloomFilter.readFrom(databaseLoading, Funnels.stringFunnel(StandardCharsets.US_ASCII));
break;
}
String line;
if (database.getName().contains(".hdb")) {//.hdb format: md5, size, name
while ((line = reader.readLine()) != null) {
if (line.length() > 0) {
String[] lineS = line.split(":");
if (lineS[0].length() > 0) {
if (signaturesMD5.put(lineS[0])) {
amtSignaturesMD5++;
}
}
}
}
} else if (database.getName().contains(".hsb")) {//.hsb format: sha256, size, name
while ((line = reader.readLine()) != null) {
if (line.length() > 0) {
String[] lineS = line.split(":");
if (lineS[0].length() == 32) {
if (signaturesSHA1.put(lineS[0])) {
amtSignaturesSHA1++;
}
} else if (lineS[0].length() > 0) {
if (signaturesSHA256.put(lineS[0])) {
amtSignaturesSHA256++;
}
}
}
}
}
reader.close();
databaseLoading.close();
} else {
Log.w("Hypatia", "Failed to validate database");
}
@ -197,10 +132,6 @@ class Database {
}
System.gc();
}
Log.w("Hypatia", "Loaded database - md5: " + amtSignaturesMD5 + ", sha1: " + amtSignaturesSHA1 + ", sha256: " + amtSignaturesSHA256);
signaturesMD5.put("44d88612fea8a8f36de82e1278abb02f");
signaturesSHA256.put("6a0b4866f143c32e651662cebf7f380d27b0db809db3b6a34cf34c7436ab6bbf");
System.gc();
databaseFullyLoaded = true;
databaseCurrentlyLoading = false;

View file

@ -131,57 +131,6 @@ public class MainActivity extends Activity {
creditsDialog.show();
}
private String localizeDBDescription(String desc) {
return desc
.replaceAll("AUTHOR", getString(R.string.db_desc_author))
.replaceAll("LICENSE", getString(R.string.db_desc_license))
.replaceAll("SIZE_SMALL", getString(R.string.db_desc_size_small))
.replaceAll("SIZE_MEDIUM", getString(R.string.db_desc_size_medium))
.replaceAll("SIZE_LARGE", getString(R.string.db_desc_size_large))
.replaceAll("SIZE", getString(R.string.db_desc_size))
.replaceAll("SOURCE", getString(R.string.db_desc_source));
}
private void selectDatabases() {
final String[] databases = {
localizeDBDescription("ClamAV: Android Only\n • SIZE: SIZE_MEDIUM\n • LICENSE: GPL-2.0\n • AUTHOR: Cisco\n • SOURCE: https://clamav.net\n"),
localizeDBDescription("ClamAV: Full\n • SIZE: SIZE_LARGE\n • LICENSE: GPL-2.0\n • AUTHOR: Cisco\n • SOURCE: https://clamav.net\n"),
localizeDBDescription("ESET\n • SIZE: SIZE_SMALL\n • LICENSE: BSD 2-Clause\n • AUTHOR: ESET\n • SOURCE: https://github.com/eset/malware-ioc\n"),
localizeDBDescription("Targeted Threats\n • SIZE: SIZE_SMALL\n • LICENSE: CC BY-SA 4.0\n • AUTHOR: Nex\n • SOURCE: https://github.com/botherder/targetedthreats\n"),
localizeDBDescription("Amnesty Tech Investigations\n • SIZE: SIZE_SMALL\n • LICENSE: CC BY 2.0\n • AUTHOR: Amnesty International\n • SOURCE: https://github.com/amnestytech/investigations\n"),
localizeDBDescription("Stalkerware\n • SIZE: SIZE_SMALL\n • LICENSE: CC BY 4.0\n • AUTHOR: Echap\n • SOURCE: https://github.com/AssoEchap/stalkerware-indicators\n"),
localizeDBDescription("MalwareBazaar: Android Only\n • SIZE: SIZE_SMALL\n • LICENSE: CC0\n • AUTHOR: Abuse.ch\n • SOURCE: https://bazaar.abuse.ch\n"),
localizeDBDescription("MalwareBazaar: Full\n • SIZE: SIZE_LARGE\n • LICENSE: CC0\n • AUTHOR: Abuse.ch\n • SOURCE: https://bazaar.abuse.ch")};
final boolean[] databaseDefaults = {
prefs.getBoolean("SIGNATURES_CLAMAV-ANDROID", true),
prefs.getBoolean("SIGNATURES_CLAMAV", false),
prefs.getBoolean("SIGNATURES_ESET", true),
prefs.getBoolean("SIGNATURES_TARGETEDTHREATS", true),
prefs.getBoolean("SIGNATURES_AMNESTY", true),
prefs.getBoolean("SIGNATURES_STALKERWARE", true),
prefs.getBoolean("SIGNATURES_MALWAREBAZAAR-ANDROID", true),
prefs.getBoolean("SIGNATURES_MALWAREBAZAAR", false)};
Dialog databaseDialog;
AlertDialog.Builder databaseBuilder = new AlertDialog.Builder(this);
databaseBuilder.setTitle(R.string.lblSelectDatabasesTitle);
databaseBuilder.setMultiChoiceItems(databases, databaseDefaults, (dialogInterface, i, selected) -> databaseDefaults[i] = selected);
databaseBuilder.setPositiveButton("OK", (dialogInterface, i) -> {
prefs.edit().putBoolean("SIGNATURES_CLAMAV-ANDROID", databaseDefaults[0]).apply();
prefs.edit().putBoolean("SIGNATURES_CLAMAV", databaseDefaults[1]).apply();
prefs.edit().putBoolean("SIGNATURES_ESET", databaseDefaults[2]).apply();
prefs.edit().putBoolean("SIGNATURES_TARGETEDTHREATS", databaseDefaults[3]).apply();
prefs.edit().putBoolean("SIGNATURES_AMNESTY", databaseDefaults[4]).apply();
prefs.edit().putBoolean("SIGNATURES_STALKERWARE", databaseDefaults[5]).apply();
prefs.edit().putBoolean("SIGNATURES_MALWAREBAZAAR-ANDROID", databaseDefaults[6]).apply();
prefs.edit().putBoolean("SIGNATURES_MALWAREBAZAAR", databaseDefaults[7]).apply();
});
databaseDialog = databaseBuilder.create();
databaseDialog.show();
}
@Override
public final boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
@ -211,9 +160,6 @@ public class MainActivity extends Activity {
updateDatabase();
}
break;
case R.id.mnuSelectDatabases:
selectDatabases();
break;
case R.id.mnuDatabaseServer:
AlertDialog.Builder builderServerOverride = new AlertDialog.Builder(this);
builderServerOverride.setTitle(getString(R.string.lblDatabaseServer));
@ -314,7 +260,6 @@ public class MainActivity extends Activity {
filesToScan.add(new File("/storage"));
}
malwareScanner.executeOnExecutor(Utils.getThreadPoolExecutor(), filesToScan);
new Thread(() -> {
try {

View file

@ -135,7 +135,7 @@ class MalwareScanner extends AsyncTask<HashSet<File>, Object, String> {
}
}
if (Database.isDatabaseLoaded()) {
publishProgress("\t" + context.getString(R.string.main_database_loaded, NumberFormat.getInstance().format(Database.getSignatureCount())) + "\n", true);
publishProgress("\t" + context.getString(R.string.main_database_loaded, "?") + "\n", true);
//Get file hashes
publishProgress("\t" + context.getString(R.string.main_hashing_files), true);

View file

@ -108,6 +108,7 @@ public class MalwareScannerService extends Service {
.setContentTitle(getText(R.string.lblNotificationRealtimeTitle))
.setContentText(getText(R.string.lblNotificationRealtimeText))
.setPriority(Notification.PRIORITY_MIN)
.setOngoing(true)
.setShowWhen(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -119,7 +120,7 @@ public class MalwareScannerService extends Service {
private void updateForegroundNotification() {
foregroundNotification
.setSubText(NumberFormat.getInstance().format(Database.getSignatureCount()) + " sigs" + "" + getString(R.string.main_files_scanned_count, NumberFormat.getInstance().format(Utils.FILES_SCANNED)));
.setSubText(getString(R.string.main_files_scanned_count, NumberFormat.getInstance().format(Utils.FILES_SCANNED)));
notificationManager.notify(-1, foregroundNotification.build());
}

View file

@ -14,9 +14,6 @@
<item
android:id="@+id/mnuUpdateDatabase"
android:title="@string/lblUpdateDatabase" />
<item
android:id="@+id/mnuSelectDatabases"
android:title="@string/lblSelectDatabases" />
<item
android:id="@+id/mnuDatabaseServer"
android:title="@string/lblDatabaseServer" />

View file

@ -0,0 +1,2 @@
* Elimiates all local database conversion
* Server now serves up the optimized bloom filters directly for smaller downloads

View file

@ -16,6 +16,8 @@ sigtool -u daily.c*d
#MD5
grep "Andr\\." main.hdb >> Android.hdb
grep "Andr\\." daily.hdb >> Android.hdb
#grep "Java\\." main.hdb >> Android.hdb
#grep "Java\\." daily.hdb >> Android.hdb
grep "Unix\\." main.hdb >> Android.hdb
grep "Unix\\." daily.hdb >> Android.hdb
grep "Multios\\." main.hdb >> Android.hdb
@ -24,6 +26,8 @@ grep "Multios\\." daily.hdb >> Android.hdb
#SHA
grep "Andr\\." main.hsb >> Android.hsb
grep "Andr\\." daily.hsb >> Android.hsb
#grep "Java\\." main.hsb >> Android.hsb
#grep "Java\\." daily.hsb >> Android.hsb
grep "Unix\\." main.hsb >> Android.hsb
grep "Unix\\." daily.hsb >> Android.hsb
grep "Multios\\." main.hsb >> Android.hsb

View file

@ -8,7 +8,7 @@ processHashes() {
dos2unix $1/samples.$2
while IFS= read -r line
do
echo "$line":0:ESET."$name" >> ./eset.$3;
echo "$line" >> ./eset.$2;
done < "$1/samples.$2";
fi;
}
@ -17,5 +17,3 @@ export -f processHashes;
find . -maxdepth 2 -mindepth 1 -type d -exec bash -c 'processHashes "{}" md5 hdb' \;
find . -maxdepth 2 -mindepth 1 -type d -exec bash -c 'processHashes "{}" sha1 hsb' \;
find . -maxdepth 2 -mindepth 1 -type d -exec bash -c 'processHashes "{}" sha256 hsb' \;
gzip *.hdb;
gzip *.hsb;

View file

@ -1,6 +0,0 @@
#!/bin/bash
#License: GPLv3
#Description: Hypatia conversion script for https://bazaar.abuse.ch/export/csv/full/ (CC0)
grep -v "^#" full.csv | grep -e "\"apk\"" -e "\"application/x-executable\"" -e "\"application/x-sharedlib\"" | awk '{ print $3 } ' | sed 's/^"//' | sed 's/",$/:0:MB/' | sort -u > malware_bazaar.hsb;
grep -v "^#" full.csv | awk '{ print $3 } ' | sed 's/^"//' | sed 's/",$/:0:MB/' | sort -u > malware_bazaar-full.hsb;
gzip *.hsb;

14
scripts/0sign.sh Normal file
View file

@ -0,0 +1,14 @@
for database in *.bin
do
if [ -f "$database.sig" ]; then
#If it does exist sign if it doesn't match
if ! gpg --verify "$database.sig"; then
rm "$database.sig";
gpg --sign --local-user 6395FC9911EDCD6158712DF7BADFCABDDBF5B694 --detach-sign "$database";
fi;
else
#Sign it if it doesn't exist
gpg --sign --local-user 6395FC9911EDCD6158712DF7BADFCABDDBF5B694 --detach-sign "$database";
fi;
done

139
scripts/Main.java Normal file
View file

@ -0,0 +1,139 @@
/*
Copyright (c) 2023 Divested Computing Group
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, see <https://www.gnu.org/licenses/>.
*/
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.zip.GZIPInputStream;
public class Main {
public static int amtSignaturesRead = 0;
public static BloomFilter<String> signaturesMD5 = null;
public static int amtSignaturesMD5 = 0;
public static BloomFilter<String> signaturesSHA1 = null;
public static int amtSignaturesSHA1 = 0;
public static BloomFilter<String> signaturesSHA256 = null;
public static int amtSignaturesSHA256 = 0;
public static void main(String[] args) {
signaturesMD5 = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.US_ASCII), 150000, 0.00001); //150k
signaturesSHA1 = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.US_ASCII), 4500000, 0.00001); //4.5m
signaturesSHA256 = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.US_ASCII), 800000, 0.00001); //800k
for (File databaseLocation : new File(args[0]).listFiles()) {
System.out.println("Processing: " + databaseLocation);
try {
BufferedReader reader;
if (databaseLocation.getName().endsWith(".gz")) {
reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(databaseLocation))));
} else {
reader = new BufferedReader(new FileReader(databaseLocation));
}
String line;
if (databaseLocation.getName().contains(".hdb")) {//.hdb format: md5, size, name
while ((line = reader.readLine()) != null) {
if (line.length() > 0) {
String[] lineS = line.split(":");
if (lineS[0].length() > 0) {
if (signaturesMD5.put(lineS[0])) {
amtSignaturesMD5++;
}
amtSignaturesRead++;
}
}
}
} else if (databaseLocation.getName().contains(".hsb")) {//.hsb format: sha256, size, name
while ((line = reader.readLine()) != null) {
if (line.length() > 0) {
String[] lineS = line.split(":");
if (lineS[0].length() == 32) {
if (signaturesSHA1.put(lineS[0])) {
amtSignaturesSHA1++;
}
amtSignaturesRead++;
} else if (lineS[0].length() > 0) {
if (signaturesSHA256.put(lineS[0])) {
amtSignaturesSHA256++;
}
amtSignaturesRead++;
}
}
}
} else if (databaseLocation.getName().contains(".md5")) {//one signature per line
while ((line = reader.readLine()) != null) {
if (line.length() > 0) {
if (signaturesMD5.put(line)) {
amtSignaturesMD5++;
}
amtSignaturesRead++;
}
}
} else if (databaseLocation.getName().contains(".sha1")) {//one signature per line
while ((line = reader.readLine()) != null) {
if (line.length() > 0) {
if (signaturesSHA1.put(line)) {
amtSignaturesSHA1++;
}
amtSignaturesRead++;
}
}
} else if (databaseLocation.getName().contains(".sha256")) {//one signature per line
while ((line = reader.readLine()) != null) {
if (line.length() > 0) {
if (signaturesSHA256.put(line)) {
amtSignaturesSHA256++;
}
amtSignaturesRead++;
}
}
}
reader.close();
System.out.println("\tmd5: " + amtSignaturesMD5 + ", sha1: " + amtSignaturesSHA1 + ", sha256: " + amtSignaturesSHA256);
} catch (Exception e) {
e.printStackTrace();
}
}
signaturesMD5.put("44d88612fea8a8f36de82e1278abb02f"); //Eicar test file
signaturesSHA256.put("6a0b4866f143c32e651662cebf7f380d27b0db809db3b6a34cf34c7436ab6bbf"); //Hypatia test file
System.out.println("Total: " + amtSignaturesRead);
System.out.println("Mismatch: " + (amtSignaturesRead-amtSignaturesMD5-amtSignaturesSHA1-amtSignaturesSHA256));
System.out.println("Loaded all databases - md5: " + amtSignaturesMD5 + ", sha1: " + amtSignaturesSHA1 + ", sha256: " + amtSignaturesSHA256);
System.out.println("Expected false postive rate - md5: " + signaturesMD5.expectedFpp() + ", sha1: " + signaturesSHA1.expectedFpp() + ", sha256: " + signaturesSHA256.expectedFpp());
try {
FileOutputStream fileSignaturesMD5 = new FileOutputStream(new File(args[0]) + "/hypatia-md5-bloom.bin");
signaturesMD5.writeTo(fileSignaturesMD5);
fileSignaturesMD5.close();
FileOutputStream fileSignaturesSHA1 = new FileOutputStream(new File(args[0]) + "/hypatia-sha1-bloom.bin");
signaturesSHA1.writeTo(fileSignaturesSHA1);
fileSignaturesSHA1.close();
FileOutputStream fileSignaturesSHA256 = new FileOutputStream(new File(args[0]) + "/hypatia-sha256-bloom.bin");
signaturesSHA256.writeTo(fileSignaturesSHA256);
fileSignaturesSHA256.close();
} catch (Exception e) { e.printStackTrace(); }
}
}