Convert a Visual Studio subroutine written in C# to a B4J subroutine

Ralph Parkhurst

Member
Licensed User
I need (paid) assistance to convert the following C# code written in Visual Studio to B4J.

This routine creates an SHA-1 hash, based on the filenames and contents on a small number of files located within a specified directory, with the exclusion of one particular file called ConfigKeys.json. I need to create this as part of a migration from Visual Studio to B4J. In order to match the code at the "other end", the method used in this C# code to make the SHA-1 hash must match, however I don't understand the use of TransformBlock and TransformFinalBlock as they apply to the iterative Hashing process.

Original C# code:
private string Encrypt_Folder(string path)
        {
            string HashString;

            var files = Directory.GetFiles(path, "*.*", SearchOption.TopDirectoryOnly).ToList();

            files.Sort(StringComparer.Ordinal);

            // Remove the configKeys.json if it is in the list
            files.RemoveAll(u => u.Contains("configKeys.json"));

            SHA1 Hash = SHA1.Create();
            Hash.Initialize();

            for (int i = 0; i < files.Count; i++)
            {
                string file = files[i];

                Console.WriteLine(file);

                string fileName = Path.GetFileName(file);
                byte[] pathBytes = Encoding.UTF8.GetBytes(fileName);
                Hash.TransformBlock(pathBytes, 0, pathBytes.Length, pathBytes, 0);

                byte[] contentBytes = File.ReadAllBytes(file);
                if (i == files.Count - 1)
                    Hash.TransformFinalBlock(contentBytes, 0, contentBytes.Length);
                else
                    Hash.TransformBlock(contentBytes, 0, contentBytes.Length, contentBytes, 0);
            }

            HashString = BitConverter.ToString(Hash.Hash).Replace("-", "").ToUpper();

            return HashString;
        }

Please let me know your "Donation" price - any offers considered :)

Kind regards,
Ralph Parkhurst
Melbourne, Australia
 

DonManfred

Expert
Licensed User
Longtime User

Ralph Parkhurst

Member
Licensed User
Hello DonManfred,

Thank you for your question.

I will post a sample tomorrow when I am at the office again. As I recall it returns a 20-byte string (160bit) corresponding with SHA-1 cyphering, and I will post an actual sample tomorrow. I do not believe it conforms to the way a regular CRC32 is calculated, but I may be wrong.
 

Ralph Parkhurst

Member
Licensed User
What does the Method return?

It calculates the SHA-1 20 bytes, like this example: 49-F3-E4-EB-07-DF-5D-D3-A2-4B-31-30-BD-2D-EB-0A-02-41-B6-20

The last line removes the hyphen, so the return is this string: 49F3E4EB07DF5DD3A24B3130BD2DEB0A0241B620

Best regards,
Ralph
 

emexes

Expert
Licensed User
It calculates the SHA-1 20 bytes, like this example: 49-F3-E4-EB-07-DF-5D-D3-A2-4B-31-30-BD-2D-EB-0A-02-41-B6-20
The last line removes the hyphen, so the return is this string: 49F3E4EB07DF5DD3A24B3130BD2DEB0A0241B620

Holy smokes, for a moment here I thought I was back in the days of APL conciseness:

B4X:
Log(bc.HexFromBytes(md.GetMessageDigest(File.ReadBytes("d:\b4j\testhash", "sample.txt"), "SHA-1")))

Log output:
Waiting for debugger to connect...
Program started.
D22D5214F6B25CC6CCF6A3CE5B0E5468A3086923
 
Last edited:

emexes

Expert
Licensed User
The last line removes the hyphen, so the return is this string: 49F3E4EB07DF5DD3A24B3130BD2DEB0A0241B620

I tried to match your string but heck, it's like they've gone out of their way to make it hard to do. Best I've gotten as at lunchtime is:

B4X:
Dim Food As String = "Ralph's Acheless Floccus"
Dim Digestion As String = bc.HexFromBytes(md.GetMessageDigest(bc.StringToBytes(Food, "UTF-8"), "SHA-1"))
Log(Food & " ==> " & Digestion)

Log output:
Waiting for debugger to connect...
Program started.
Ralph's Acheless Floccus ==> 49F3E4E23B43F10E9DBAA32A72DFF698CEA57F32
 

Ralph Parkhurst

Member
Licensed User
Hahaha - nice lunchtime work! I have also used messageDigest to determine the SHA-1 hash for each of the filenames, and each of the file contents. The trouble I am facing is there are a small number of files to perform this on (five in fact), and I don't have a clue how C# performs it using its .net Crypto library and the TransformBlock and TransformFinalBlock statements.
 

JordiCP

Expert
Licensed User
Longtime User
Seems that (there's a good example HERE) TransformBLock and TransformFinalBlock are used to do the process step by step when the information comes into chunks, but the results have to be the same as if you just send the whole data to TransformFinalBlock

So, I guess that in your case you'll get the expected the results if you build a single string made of all the 'small pieces' (filenames + file contents).

Something similar to (based on @emexes code and totally untested)
B4X:
' We are assuming that 'files' is an already sorted List containing the filenames.
Dim Food as String     ' Should work with StringBuilder if number of files is large.
for k=0 to files.size-1
    food = food & files.get(k)  
    food = food & File.ReadString(File.DirApp, files.Get(k))      ' File.DirApp, or therever the files are
Next
Dim Digestion As String = bc.HexFromBytes(md.GetMessageDigest(bc.StringToBytes(Food, "UTF-8"), "SHA-1"))
Log(Food & " ==> " & Digestion)
 

DonManfred

Expert
Licensed User
Longtime User
Add to your App

B4X:
#AdditionalJar: commons-codec-1.15
and copy the attached file together with the library jar and xml to your additional library folder.

B4X:
    Dim sha As Hashing
    sha.Initialize
  
    Try
        Dim sha1hash As String = sha.HashFolder(File.DirInternal,True)
        Log($"SHA-1: ${sha1hash} ${sha1hash.Length}"$)
      
    Catch
        Log(LastException)
    End Try

SHA-1: 7a3c357d39125a51208c76833acd3169ac0ef241

Basically it is this java-code
B4X:
    public String hashDirectory(String directoryPath, boolean includeHiddenFiles) throws IOException {
        File directory = new File(directoryPath);

        if (!directory.isDirectory()) {
            throw new IllegalArgumentException("Not a directory");
        }

        Vector<FileInputStream> fileStreams = new Vector<>();
        collectFiles(directory, fileStreams, includeHiddenFiles);

        try (SequenceInputStream sequenceInputStream = new SequenceInputStream(fileStreams.elements())) {
            return DigestUtils.sha1Hex(sequenceInputStream);
        }
    }

    private void collectFiles(File directory, List<FileInputStream> fileInputStreams, boolean includeHiddenFiles)
            throws IOException {
        File[] files = directory.listFiles();

        if (files != null) {
            Arrays.sort(files);
            for (File file : files) {
                if (includeHiddenFiles || !Files.isHidden(file.toPath())) {
                    if (file.isDirectory()) {
                        collectFiles(file, fileInputStreams, includeHiddenFiles);
                    } else {
                        fileInputStreams.add(new FileInputStream(file));
                    }
                }
            }
        }
    }

@Ralph Parkhurst Job finished
 

Attachments

  • commons-codec-1.15.jar
    345.5 KB · Views: 99
  • ShaFolder.zip
    2.7 KB · Views: 103
  • SHA1Folder.zip
    9.1 KB · Views: 97
Last edited:

Ralph Parkhurst

Member
Licensed User
Thanks to those who kindly replied on this forum, and also directly.

The solution that was delivered first was from Emexes, and I thank eveyone for your interest

In case someone reading this in the future has a similar question, here is the B4J code that replicates the C# method perfectly, which I share with Emexes permission:

Working code in B4J:
Sub Encrypt_Folder(WhereTheFiledThingsAre As String) As String
    Dim md As MessageDigest
    Dim bc As ByteConverter

    Dim RawFileList As List = File.ListFiles(WhereTheFiledThingsAre)
   
    Dim FilesToHash As List
    FilesToHash.Initialize
   
    For Each F As String In RawFileList
        If File.IsDirectory(WhereTheFiledThingsAre, F) = False Then    'if not directory, then must be file
            If F.Contains("configKeys.json") = False Then    'Remove the configKeys.json if it is in the list
                FilesToHash.Add(F)
            End If
        End If
    Next
   
    FilesToHash.Sort(True)    'equivalent of files.Sort(StringComparer.Ordinal) (as best I can determine)

    Dim EverythingToHash As B4XBytesBuilder
    EverythingToHash.Initialize
   
    For I = 0 To FilesToHash.Size - 1
        Dim F As String = FilesToHash.Get(I)
        EverythingToHash.Append(bc.StringToBytes(F, "UTF-8"))    'hash file name
        EverythingToHash.Append(File.ReadBytes(WhereTheFiledThingsAre, F))    'and contents too
    Next

    Dim HashBytes() As Byte = md.GetMessageDigest(EverythingToHash.ToArray, "SHA-1")
    Dim HashString As String = bc.HexFromBytes(HashBytes)

    Return HashString
End Sub
 
Last edited:

Computersmith64

Well-Known Member
Licensed User
Longtime User
What does the Method return? A CRC32?
Given that:
B4X:
HashString = BitConverter.ToString(Hash.Hash).Replace("-", "").ToUpper();
appears to create an upper case string - I'd say it returns a string.
 
Top