Search code examples
.netvb.netencryptionencryption-symmetric

Can ChaCha20 symmetric encryption be implemented in VB.NET even though it lacks circular bit rotation?


After reading up on encryption algorithms and getting some really good advice from Maarten, I see that the ChaCha20 encryption algorithm has the potential to provide strong symmetric encryption AND good performance on older computers that lack specialized AES encryption/decryption instructions.

Is there a good example of VB.NET code that implements the ChaCha20 encryption algorithm in VB.NET?

VB.NET may not be ideal because it lacks a lot of specific instructions, for instance for circular bit rotation on unsigned integers (which are a key part of ChaCha20), so the question is can this algorithm still be implemented well in VB.NET?


Solution

  • After trying to write the ChaCha20 encryption algorithm in VB.NET, it seems that it will work quite well. Please see the code below.

    There is no circular bit rotation for integers in VB.NET, but in the code you will find a solid workaround for achieving circular bit rotation.

    I have added a test subroutine that tests the code against some official ChaCha20 test vectors you can find in the final spec here.

    PLEASE TAKE IN TO ACCOUNT THESE GUIDELINES BEFORE USING THIS CODE:

    • It has not been assessed by encryption experts and may contain security issues, especially on older CPUs that are chock-full of Meltdown and Spectre-type security flaws as well as a whole bunch more security flaws.

    • Feedback to improve the code and performance is more than welcome, you can use this code freely and unrestricted.

    • Until this code is assessed by experience encryption algorithm developers, use it where you are looking for obfuscation rather than strong encryption. (it is likely to offer this already, but zero guarantees)

    • Make sure you protect the key you use, never store it unencrypted.

    • Never ever (ever ever ever) use the same Nonce + Counter combination twice. You need these to decrypt the information, no need to protect these from an adversary put all your efforts in to making sure your key is safe.

    • The subs for Encrypting and Decrypting are exactly the same, as this is symmetric encryption (that is why the sub names contain EncDec in the name).

    Don't forget to check the Remove integer overflow checks-checkbox, see here how to do so.

    Enjoy and let me know what needs to be improved!

    Sub ValidateChaCha20AlgorithmAgainstStandard()
    
        'See final spec for ChaCha20 with test vectors here: https://datatracker.ietf.org/doc/rfc7539/
    
        Dim Key As String
        Dim Nonce As String
        Dim Counter As UInt32
        Dim HowMany As UInt32
    
        'Final spec for two blocks!
        Key = "03020100-07060504-0b0a0908-0f0e0d0c-13121110-17161514-1b1a1918-1f1e1d1c"
        Nonce = "00000000-4a000000-00000000"
        Counter = 1
        HowMany = 2
    
        Dim KeyStream() As Byte
    
        KeyStream = ChaCha20_GenerateEncDecByteStream(Key, Nonce, Counter, HowMany)
    
        Dim KeyStreamOutputHexString As String
    
        KeyStreamOutputHexString = ByteArrayToString(KeyStream)
    
        Dim ValidatedKeyStreamOutput As String
    
        ValidatedKeyStreamOutput = "224f51f3401bd9e12fde276fb8631ded8c131f823d2c06e27e4fcaec9ef3cf788a3b0aa372600a92b57974cded2b9334794cba40c63e34cdea212c4cf07d41b769a6749f3f630f4122cafe28ec4dc47e26d4346d70b98c73f3e9c53ac40c5945398b6eda1a832c89c167eacd901d7e2bf363740373201aa188fbbce83991c4ed"
    
        Dim StringCompareThatMustFail As String
    
        StringCompareThatMustFail = "I will fail for sure!"
    
        If KeyStreamOutputHexString.Equals(StringCompareThatMustFail) = False Then Debug.Print("String compare works properly")
        If KeyStreamOutputHexString.Equals(StringCompareThatMustFail) = True Then Debug.Print("String compare has FAILED DO NOT TRUST this verification!!!")
    
        If KeyStreamOutputHexString.Equals(ValidatedKeyStreamOutput) = True Then Debug.Print("ChaCha20 algorithm has passed the validation check, output is 100% correct")
        If KeyStreamOutputHexString.Equals(ValidatedKeyStreamOutput) = False Then Debug.Print("ChaCha20 algorithm has FAILED the validation check do NOT USE the algorithm!!!")
    
        'Now we test the bytestream encoding!
    
        Dim PlainText As String
        Dim PlainByteStream As Byte()
    
        PlainText = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, suncreen would be it."
    
        PlainByteStream = UTF8StringToBytes(PlainText)
    
        Dim EncryptedStream As Byte()
        Dim DecryptedStream As Byte()
        Dim DecryptedString As String
    
        EncryptedStream = ChaCha20_EncDecByteStream(PlainByteStream, KeyStream)
        DecryptedStream = ChaCha20_EncDecByteStream(EncryptedStream, KeyStream)
        DecryptedString = UTF8BytesToString(DecryptedStream)
    
        If DecryptedString.Equals(PlainText) = True Then Debug.Print("2dn ChaCha20 validation check passed, algorithm has SUCCESFULLY performed a full encryption/decryption round trip yielding exactly the source data!")
    
    End Sub
    
    Function ChaCha20_EncDecByteStream(StreamToEncDec() As Byte, KeyStream() As Byte) As Byte()
    
        Dim b As Byte
        Dim ProcessedStream As Byte()
        Dim LoopCounter As Integer
    
    
        ReDim ProcessedStream(StreamToEncDec.Length - 1)
    
        For LoopCounter = 0 To (StreamToEncDec.Length - 1)
    
            ProcessedStream(LoopCounter) = StreamToEncDec(LoopCounter) Xor KeyStream(LoopCounter)
    
        Next
    
        Return ProcessedStream
    
    End Function
    
    Function ChaCha20_GenerateEncDecByteStream(Key As String, Nonce As String, CounterStartPosition As UInt32, HowManyBlocks As UInt32) As Byte()
    
        'This function creates a bytestream with the ChaCha20 encryption algorithm that can be used for further processing
        'The stream is always in units of 64 bytes (512 bits).
    
        'The key and the nonce are Hexadecimal strings with every 8 Hex characters (32 bits) separated by a - for readability 'coz that is just very convenient to use :)
    
        Dim KeyStringArray() As String
        Dim NonceStringArray() As String
    
        KeyStringArray = Split(Key, "-")
        NonceStringArray = Split(Nonce, "-")
    
        Dim ChaChaStartBlock(15) As UInt32
    
        'See specified under https://datatracker.ietf.org/doc/html/rfc7539
        ' The first four words (0-3) are constants: 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574.
    
        'Here we set the constants
        ChaChaStartBlock(0) = &H61707865
        ChaChaStartBlock(1) = &H3320646E
        ChaChaStartBlock(2) = &H79622D32
        ChaChaStartBlock(3) = &H6B206574
    
        'Here we set the key
        ChaChaStartBlock(4) = CUInt("&H" & KeyStringArray(0))
        ChaChaStartBlock(5) = CUInt("&H" & KeyStringArray(1))
        ChaChaStartBlock(6) = CUInt("&H" & KeyStringArray(2))
        ChaChaStartBlock(7) = CUInt("&H" & KeyStringArray(3))
        ChaChaStartBlock(8) = CUInt("&H" & KeyStringArray(4))
        ChaChaStartBlock(9) = CUInt("&H" & KeyStringArray(5))
        ChaChaStartBlock(10) = CUInt("&H" & KeyStringArray(6))
        ChaChaStartBlock(11) = CUInt("&H" & KeyStringArray(7))
    
        'Here we set the initial counter position
        ChaChaStartBlock(12) = CounterStartPosition
    
        'Here we set the Nonce
        ChaChaStartBlock(13) = CUInt("&H" & NonceStringArray(0))
        ChaChaStartBlock(14) = CUInt("&H" & NonceStringArray(1))
        ChaChaStartBlock(15) = CUInt("&H" & NonceStringArray(2))
    
        Debug.Print("Start block:")
        Debug.Print(Hex(ChaChaStartBlock(0)) & " " & Hex(ChaChaStartBlock(1)) & " " & Hex(ChaChaStartBlock(2)) & " " & Hex(ChaChaStartBlock(3)))
        Debug.Print(Hex(ChaChaStartBlock(4)) & " " & Hex(ChaChaStartBlock(5)) & " " & Hex(ChaChaStartBlock(6)) & " " & Hex(ChaChaStartBlock(7)))
        Debug.Print(Hex(ChaChaStartBlock(8)) & " " & Hex(ChaChaStartBlock(9)) & " " & Hex(ChaChaStartBlock(10)) & " " & Hex(ChaChaStartBlock(11)))
        Debug.Print(Hex(ChaChaStartBlock(12)) & " " & Hex(ChaChaStartBlock(13)) & " " & Hex(ChaChaStartBlock(14)) & " " & Hex(ChaChaStartBlock(15)))
    
        'Now we create a byte array of the size we will need
        Dim ChaChaByteArray() As Byte
    
        ReDim ChaChaByteArray(HowManyBlocks * 64 - 1)
    
        Dim ByteArrayFillLoop As Integer
        Dim ChaChaResultBlock(15) As UInt32
        Dim InnerLoop As Integer
        Dim FourByteArray(3) As Byte
        Dim FourByteArrayReverseOrder(3) As Byte
    
        For ByteArrayFillLoop = 0 To (HowManyBlocks - 1)
    
            ChaChaStartBlock(12) = CounterStartPosition + ByteArrayFillLoop
    
            ChaChaResultBlock = ChaCha20_GenerateEncDecBlock(ChaChaStartBlock)
    
            Debug.Print("Result block:")
            Debug.Print(Hex(ChaChaResultBlock(0)) & " " & Hex(ChaChaResultBlock(1)) & " " & Hex(ChaChaResultBlock(2)) & " " & Hex(ChaChaResultBlock(3)))
            Debug.Print(Hex(ChaChaResultBlock(4)) & " " & Hex(ChaChaResultBlock(5)) & " " & Hex(ChaChaResultBlock(6)) & " " & Hex(ChaChaResultBlock(7)))
            Debug.Print(Hex(ChaChaResultBlock(8)) & " " & Hex(ChaChaResultBlock(9)) & " " & Hex(ChaChaResultBlock(10)) & " " & Hex(ChaChaResultBlock(11)))
            Debug.Print(Hex(ChaChaResultBlock(12)) & " " & Hex(ChaChaResultBlock(13)) & " " & Hex(ChaChaResultBlock(14)) & " " & Hex(ChaChaResultBlock(15)))
    
            'Here we copy the block into the byte array
            For InnerLoop = 0 To 15
    
                FourByteArray = BitConverter.GetBytes(ChaChaResultBlock(InnerLoop))
    
                If BitConverter.IsLittleEndian = False Then
                    FourByteArrayReverseOrder(0) = FourByteArray(3)
                    FourByteArrayReverseOrder(1) = FourByteArray(2)
                    FourByteArrayReverseOrder(2) = FourByteArray(1)
                    FourByteArrayReverseOrder(3) = FourByteArray(0)
    
                    FourByteArray = FourByteArrayReverseOrder
                End If
    
                Buffer.BlockCopy(FourByteArray, 0, ChaChaByteArray, ByteArrayFillLoop * 64 + InnerLoop * 4, 4)
    
            Next
    
        Next
    
        Return ChaChaByteArray
    
    End Function
    
    Function ChaCha20_GenerateEncDecBlock(CC20M As UInt32()) As UInt32()
    
        Dim M(15) As UInt32
    
        Array.Copy(CC20M, M, M.Length)
    
        Dim R10CTR As Integer
    
        For R10CTR = 1 To 10
    
            Call ChaCha20_QR(M(0), M(4), M(8), M(12))
            Call ChaCha20_QR(M(1), M(5), M(9), M(13))
            Call ChaCha20_QR(M(2), M(6), M(10), M(14))
            Call ChaCha20_QR(M(3), M(7), M(11), M(15))
    
            Call ChaCha20_QR(M(0), M(5), M(10), M(15))
            Call ChaCha20_QR(M(1), M(6), M(11), M(12))
            Call ChaCha20_QR(M(2), M(7), M(8), M(13))
            Call ChaCha20_QR(M(3), M(4), M(9), M(14))
    
        Next
    
        Dim CTR As Integer
    
        For CTR = 0 To 15
            M(CTR) = M(CTR) + CC20M(CTR)
        Next
    
    
        Return M
    
    End Function
    
    Sub ChaCha20_QR(ByRef a As UInt32, ByRef b As UInt32, ByRef c As UInt32, ByRef d As UInt32)
    
        a = a + b
        d = d Xor a
        d = d << 16 Or d >> 16
    
    
        c = c + d
        b = b Xor c
        b = b << 12 Or b >> 20
    
        a = a + b
        d = d Xor a
        d = d << 8 Or d >> 24
    
        c = c + d
        b = b Xor c
        b = b << 7 Or b >> 25
    
    End Sub
    
    Function ByteArrayToString(ba() As Byte) As String
    
        Dim hex As New StringBuilder(ba.Length * 2)
        Dim b As Byte
    
        For Each b In ba
            hex.AppendFormat("{0:x2}", b)
        Next
    
        Return hex.ToString()
    
    End Function
    
    Private Function UTF8StringToBytes(
    ByVal str As String) As Byte()
    
        Return System.Text.Encoding.UTF8.GetBytes(str)
    End Function
    
    Private Function UTF8BytesToString(
    ByVal bytes() As Byte) As String
    
        Return System.Text.Encoding.UTF8.GetString(bytes)
    End Function