diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java
index 91a180e9bc787b6812b109652ba1e19a87929481..06e5fb7fcd3cca27be8700b73864020cef16a51c 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java
@@ -200,6 +200,7 @@ public <O> O doPost(String urlPath, Map<String, String> urlReqParams, Object jso
     public void doFileUploadPost(String urlPath, String fileName, InputStream fileIs) throws CTCloudException {
          
         try (CloseableHttpClient httpclient = createConnection(getProxySettings(), sslContext)) {
+            LOGGER.log(Level.INFO, "initiating http post request to ctcloud server " + urlPath);
             HttpPost post = new HttpPost(urlPath);
             configureRequestTimeout(post);
             
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java
index 56bfde735610b41a63fdc73e3b9d579a2ec39dfd..5cbbbed372a69a11d109b90d53e15c3b295ebeab 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java
@@ -27,10 +27,14 @@
 import com.basistech.df.cybertriage.autopsy.ctapi.json.MalwareResultBean.Status;
 import com.basistech.df.cybertriage.autopsy.ctapi.json.MetadataUploadRequest;
 import com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud.CTLicensePersistence;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HexFormat;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -133,6 +137,7 @@ private static class SharedProcessing {
 
         private final CTLicensePersistence ctSettingsPersistence = CTLicensePersistence.getInstance();
         private final CTApiDAO ctApiDAO = CTApiDAO.getInstance();
+        private final UsernameAnonymizer usernameAnonymizer = new UsernameAnonymizer();
 
         private IngestJobState ingestJobState = null;
 
@@ -258,31 +263,86 @@ private static long remaining(Long limit, Long used) {
          * @param af The abstract file.
          * @return The md5 hash (or null if could not be determined).
          */
-        private static String getOrCalcHash(AbstractFile af) {
-            if (StringUtils.isNotBlank(af.getMd5Hash())) {
-                return af.getMd5Hash();
+        private static String getOrCalcHash(AbstractFile af, HashType hashType) {
+            switch (hashType) {
+                case MD5:
+                    if (StringUtils.isNotBlank(af.getMd5Hash())) {
+                        return af.getMd5Hash();
+                    }
+                    break;
+                case SHA256:
+                    if (StringUtils.isNotBlank(af.getSha256Hash())) {
+                        return af.getSha256Hash();
+                    }
             }
 
             try {
-                List<HashResult> hashResults = HashUtility.calculateHashes(af, Collections.singletonList(HashType.MD5));
+                List<HashResult> hashResults = HashUtility.calculateHashes(af, Collections.singletonList(hashType));
                 if (CollectionUtils.isNotEmpty(hashResults)) {
                     for (HashResult hashResult : hashResults) {
-                        if (hashResult.getType() == HashType.MD5) {
+                        if (hashResult.getType() == hashType) {
                             return hashResult.getValue();
                         }
                     }
                 }
             } catch (TskCoreException ex) {
                 logger.log(Level.WARNING,
-                        MessageFormat.format("An error occurred while processing file name: {0} and obj id: {1}.",
+                        MessageFormat.format("An error occurred while processing hash for file name: {0} and obj id: {1} and hash type {2}.",
                                 af.getName(),
-                                af.getId()),
+                                af.getId(),
+                                hashType.name()),
                         ex);
             }
 
             return null;
         }
 
+        /**
+         * Gets or calculates the md5 for a file.
+         *
+         * @param af The file.
+         * @return The hash.
+         */
+        private static String getOrCalcMd5(AbstractFile af) {
+            return getOrCalcHash(af, HashType.MD5);
+        }
+
+        /**
+         * Gets or calculates the sha256 for a file.
+         *
+         * @param af The file.
+         * @return The hash.
+         */
+        private static String getOrCalcSha256(AbstractFile af) {
+            return getOrCalcHash(af, HashType.SHA256);
+        }
+
+        /**
+         * Gets or calculates the sha1 for a file.
+         *
+         * @param af The file.
+         * @return The hash.
+         */
+        private static String getOrCalcSha1(AbstractFile af) throws NoSuchAlgorithmException, ReadContentInputStream.ReadContentInputStreamException {
+            if (StringUtils.isNotBlank(af.getSha1Hash())) {
+                return af.getSha1Hash();
+            }
+            // taken from https://stackoverflow.com/questions/6293713/java-how-to-create-sha-1-for-a-file
+            MessageDigest digest = MessageDigest.getInstance("SHA-1");
+            ReadContentInputStream afStream = new ReadContentInputStream(af);
+            int n = 0;
+            byte[] buffer = new byte[8192];
+            while (n != -1) {
+                n = afStream.read(buffer);
+                if (n > 0) {
+                    digest.update(buffer, 0, n);
+                }
+            }
+            byte[] hashBytes = digest.digest();
+            String hashString = HexFormat.of().formatHex(hashBytes);
+            return hashString;
+        }
+
         /**
          * Processes a file. The file goes through the lookup process if the
          * file meets acceptable criteria: 1) not FileKnown.KNOWN 2) is
@@ -305,7 +365,7 @@ IngestModule.ProcessResult process(AbstractFile af) {
                         && EXECUTABLE_MIME_TYPES.contains(StringUtils.defaultString(ingestJobState.getFileTypeDetector().getMIMEType(af)).trim().toLowerCase())
                         && CollectionUtils.isEmpty(af.getAnalysisResults(ingestJobState.getMalwareType()))) {
 
-                    String md5 = getOrCalcHash(af);
+                    String md5 = getOrCalcMd5(af);
                     if (StringUtils.isNotBlank(md5)) {
                         batchProcessor.add(new FileRecord(af.getId(), md5));
                     }
@@ -393,12 +453,11 @@ private void handleBatch(IngestJobState ingestJobState, List<FileRecord> fileRec
          * @param repResult The ct cloud results.
          * @throws org.sleuthkit.datamodel.Blackboard.BlackboardException
          * @throws TskCoreException
-         * @throws TskCoreException
          */
         @Messages({
             "MalwareScanIngestModule_SharedProcessing_exhaustedResultsHashLookups_title=Some Lookup Results Not Processed",
             "MalwareScanIngestModule_SharedProcessing_exhaustedResultsHashLookups_desc=Some lookup results were not processed due to exceeding limits.  Please try again later.",})
-        private void handleLookupResults(IngestJobState ingestJobState, Map<String, List<Long>> md5ToObjId, List<CTCloudBean> repResult) throws Blackboard.BlackboardException, TskCoreException, TskCoreException, CTCloudException {
+        private void handleLookupResults(IngestJobState ingestJobState, Map<String, List<Long>> md5ToObjId, List<CTCloudBean> repResult) throws Blackboard.BlackboardException, TskCoreException, TskCoreException, CTCloudException, NoSuchAlgorithmException, ReadContentInputStream.ReadContentInputStreamException {
             if (CollectionUtils.isEmpty(repResult)) {
                 return;
             }
@@ -445,7 +504,7 @@ private void handleLookupResults(IngestJobState ingestJobState, Map<String, List
          * @param performFileUpload True if the class of results warrants file
          * upload (i.e. NOT_FOUND)
          */
-        private void handleNonFoundResults(IngestJobState ingestJobState, Map<String, List<Long>> md5ToObjId, List<CTCloudBean> results, boolean performFileUpload) throws CTCloudException, TskCoreException {
+        private void handleNonFoundResults(IngestJobState ingestJobState, Map<String, List<Long>> md5ToObjId, List<CTCloudBean> results, boolean performFileUpload) throws CTCloudException, TskCoreException, NoSuchAlgorithmException, ReadContentInputStream.ReadContentInputStreamException {
             if (CollectionUtils.isNotEmpty(results)
                     && ingestJobState.isDoFileLookups()
                     && ((performFileUpload && ingestJobState.isUploadUnknownFiles()) || (!performFileUpload && ingestJobState.isQueryForMissing()))) {
@@ -539,7 +598,7 @@ private static boolean isUploadable(AbstractFile af) {
          * @throws CTCloudException
          * @throws TskCoreException
          */
-        private boolean uploadFile(IngestJobState ingestJobState, String md5, long objId) throws CTCloudException, TskCoreException {
+        private boolean uploadFile(IngestJobState ingestJobState, String md5, long objId) throws CTCloudException, TskCoreException, NoSuchAlgorithmException, ReadContentInputStream.ReadContentInputStreamException {
             if (!ingestJobState.isUploadUnknownFiles() || ingestJobState.getIngestJobContext().fileIngestIsCancelled()) {
                 return false;
             }
@@ -568,13 +627,12 @@ private boolean uploadFile(IngestJobState ingestJobState, String md5, long objId
             // upload metadata
             MetadataUploadRequest metaRequest = new MetadataUploadRequest()
                     .setCreatedDate(af.getCrtime() == 0 ? null : af.getCrtime())
-                    .setFilePath(af.getUniquePath())
+                    .setFilePath(usernameAnonymizer.anonymousUsername(af.getUniquePath()))
                     .setFileSizeBytes(af.getSize())
                     .setFileUploadUrl(authTokenResponse.getFileUploadUrl())
                     .setMd5(md5)
-                    // these may be missing, but that's fine
-                    .setSha1(af.getSha1Hash())
-                    .setSha256(af.getSha256Hash());
+                    .setSha1(getOrCalcSha1(af))
+                    .setSha256(getOrCalcSha256(af));
 
             ctApiDAO.uploadMeta(new AuthenticatedRequestData(ingestJobState.getLicenseInfo().getDecryptedLicense(), authTokenResponse), metaRequest);
             return true;
@@ -627,7 +685,7 @@ private void longPollForNotFound(IngestJobState ingestJobState) throws Interrupt
                     createAnalysisResults(ingestJobState, found, remaining);
 
                     // remove any found items from the list of items to long poll for
-                    for (CTCloudBean foundItem : found) {
+                    for (CTCloudBean foundItem : CollectionUtils.emptyIfNull(found)) {
                         String normalizedMd5 = normalizedMd5(foundItem.getMd5HashValue());
                         remaining.remove(normalizedMd5);
                     }
@@ -768,8 +826,8 @@ synchronized void shutDown() {
 
             // flush any remaining items
             try {
-                longPollForNotFound(ingestJobState);
                 batchProcessor.flushAndReset();
+                longPollForNotFound(ingestJobState);
             } catch (InterruptedException ex) {
                 notifyWarning(
                         Bundle.MalwareScanIngestModule_SharedProcessing_flushTimeout_title(),