Attribute VB_Name = "basMySecret" Option Explicit Option Base 0 ' $Id: basMySecret $ '**************************************************************************** ' Copyright (C)2007 DI Management Services Pty Limited, All Rights Reserved. '**************************************************************************** ' Distribution: You can freely use this code in your own applications, but ' you may not reproduce or publish this code on any web site, online service, ' or distribute as source on any media without express permission. ' Terms: Use at your own risk. Provided "as is" with no warranties. ' Use of CryptoSys API requires a licence for non-personal use. ' Contact: <www.di-mgt.com.au> <www.cryptosys.net> '**************************************************************************** ' This file last updated: ' $Date: 2007-05-20 20:35:00 $ ' $Author: dai $ '**************************************************************************** ' This code shows how you can use CryptoSys API to create and read MySecret ' data. This code is provided merely to DEMONSTRATE the principles involved. ' It is not meant as an example of production code. ' At the very minimum: ' * Remove all the debugging statments ' * Add proper error handling ' MySecret Reference: <http://www.di-mgt.com.au/mysecret.html#technical> ' This code requires the CryptoSys API library to be installed on your system ' and the `basCryptoSys.bas' module to be included in your project. ' Download CryptoSys API from <http://www.cryptosys.net/#api> ' Commercial use requires a licence: <http://www.cryptosys.net/purchase.html> ' ************************* ' INTERNAL CONSTANTS WE USE ' ************************* Private Const BLOCK_LEN As Long = 8 Private Const FRAME_LEN As Long = 60 Private Const STRETCH_COUNT As Long = 1024 Private Const FRAME_BEGIN As String = "-----BEGIN MYSECRET-----" Private Const FRAME_END As String = "-----END MYSECRET-----" Private Const FRAME_STUB As String = "-----BEGIN MYSECRET" ' ****** ' TESTS ' ****** ' To test a freshly-encrypted-and-so-different output, try this: ' ? MySecret_Decrypt(Test_MySecret_Encrypt(), "password") Public Function Test_MySecret_Encrypt() As String Dim strPlain As String Dim strPassword As String Dim strOutput As String ' INPUT PASSWORD strPassword = "password" ' INPUT PLAINTEXT strPlain = "KING RICHARD. A horse! a horse! my kingdom for a horse!" & vbCrLf & _ "CATESBY. Withdraw, my lord! I'll help you to a horse." & vbCrLf & _ "KING RICHARD. Slave, I have set my life upon a cast" & vbCrLf & _ "And I Will stand the hazard of the die." & vbCrLf & _ "I think there be six Richmonds in the field;" & vbCrLf & _ "Five have I slain to-day instead of him." & vbCrLf & _ "A horse! a horse! my kingdom for a horse! Exeunt" ' Do the business strOutput = MySecret_Encrypt(strPlain, strPassword) Debug.Print strOutput ' Clear the password Call WIPE_String(strPassword, Len(strPassword)) Test_MySecret_Encrypt = strOutput End Function Public Function Test_MySecret_Decrypt() As String Dim strInput As String Dim strPassword As String Dim strOutput As String ' INPUT PASSWORD strPassword = "password" ' INPUT MYSECRET-DATA strInput = "-----BEGIN MYSECRET-----" & vbCrLf & _ "TVn8AEFK9uRT91snxkz5y2TguL39hcNxEjRctQzipRaagPLTe0tYQikxIPlQ" & vbCrLf & _ "UPHGKDJVK3c9UTcNOqCa2eDmdJaUIJoAXITLeCdLMRfe+I0QYuBVDAqHpQwz" & vbCrLf & _ "2LscsDWlNHiqbGYeP0IyYiv0tk7mzKwj9PSSiKK5xtdiXo29BJR2+JtiOQTc" & vbCrLf & _ "QgZzURnAWIpc5/INeSJ/6rm4BZRU2Db/sMgdMLrNSIBeKEP+FKJ58/wAUems" & vbCrLf & _ "2q03DMsIcs2Scp78JabqF6RbzYaHTDH2tV8fmAGVXnCTXKaor7dk1ZuWD0Ny" & vbCrLf & _ "D0azfzNqGZm/hgp15HGDrXqkJdEq/XlMulOnxKyzqx0pJkYqNwxDHCDFE8MN" & vbCrLf & _ "KOqyeUhqm+LOjq6I6iUhaLoMVAtIew==" & vbCrLf & _ "-----END MYSECRET-----" ' Do the business strOutput = MySecret_Decrypt(strInput, strPassword) Debug.Print strOutput ' Clear the password Call WIPE_String(strPassword, Len(strPassword)) Test_MySecret_Decrypt = strOutput End Function ' *********************** ' MAIN EXPORTED FUNCTIONS ' *********************** Public Function MySecret_Encrypt(strPlain As String, strPassword As String) As String ' INPUT: plaintext string, password ' OUTPUT: string of MySecret v3 base64-encoded ciphertext (with framing and CR-LFs) Dim strOutput As String Dim abPlain() As Byte Dim nPlain As Long Dim abCompr() As Byte Dim nCompr As Long Dim abBlock() As Byte Dim nBlock As Long Dim abPad() As Byte Dim nPad As Long Dim abKey() As Byte Dim nKey As Long Dim abEncode() As Byte Dim nEncode As Long Dim abInitVec() As Byte Dim abNumber() As Byte Dim strBase64 As String Dim nCrc24 As Long Dim k As Long Dim i As Long Dim iOffset As Long Dim iCount As Long Dim nChars As Long Dim nRet As Long If Len(strPlain) = 0 Then Debug.Print "ERROR: nothing to encrypt!" Exit Function End If Debug.Print "PLAINTEXT: [" & strPlain & "]" ' CONVERT input string to a byte array abPlain = StrConv(strPlain, vbFromUnicode) nPlain = UBound(abPlain) - LBound(abPlain) + 1 Debug.Print "Plaintext length = " & nPlain & " bytes" ' COMPUTE THE CRC-24 CHECKSUM of the original plaintext nCrc24 = CRC24_Bytes(abPlain) Debug.Print "CRC-24 = " & Hex(nCrc24) ' COMPRESS using ZLIB_Deflate function ' -- first get the compressed length nCompr = ZLIB_Deflate(vbNull, 0, abPlain(0), nPlain) If nCompr <= 0 Then Debug.Print "ERROR: compression failed." GoTo Clean_Up End If Debug.Print "Compressed length = " & nCompr & " bytes" ' -- then do the compression ReDim abCompr(nCompr - 1) nCompr = ZLIB_Deflate(abCompr(0), nCompr, abPlain(0), nPlain) Debug.Print "Compressed data (hex)=" & cnvHexStrFromBytes(abCompr) ' CREATE A PADDING STRING OF LENGTH NPAD (actually a Byte array) '|<--------------NPAD--------------->| '|<---8--->|<----(8*k)---->|<-[1,8]->| '+---------+---------------+---------+ '| GUARD |Random bytes...| FINAL | '+---------+---------------+---------+ ' GUARD = 8 bytes of value NPAD ' FINAL = [1,8] bytes of value NPAD ' k = random number [0,7] ' Min(NPAD) = 8 + 8*0 + 1 = 9 ' Max(NPAD) = 8 + 8*7 + 8 = 72 ' Compute final number of odd bytes [1,8] to make length an exact multiple of 8 nPad = BLOCK_LEN - (10 + nCompr + 3) Mod BLOCK_LEN ' -- pick a random number between 0 and 7 k = RNG_Long(0, 7, "") Debug.Print "Random blocks = " & k ' -- increase size for extra blocks of 8 nPad = nPad + (k + 1) * BLOCK_LEN Debug.Print "Padding string = " & nPad & " bytes = 0x" & Hex(nPad) ' Fill with random bytes ' (it's easier to fill the whole with random and then overwrite) ReDim abPad(nPad - 1) Call RNG_NonceData(abPad(0), nPad) ' Set value of bytes in guard and final blocks equal to NPAD For i = 0 To 7 abPad(i) = CByte(nPad) Next For i = ((k + 1) * BLOCK_LEN) To nPad - 1 abPad(i) = CByte(nPad) Next Debug.Print "Padding string (hex)=" & cnvHexStrFromBytes(abPad) ' COMPOSE the encryption block '|<------------------------nBlock--------------------------->| '|<--------10--------->|<------nCompr------>|<-3-->|<-nPad-->| '+-----+-------+-------+--------------------+------+---------+ '|ZS(2)|CLEN(4)|ULEN(4)| Compressed data... |CRC(3)|PAD(9-72)| '+-----+-------+-------+--------------------+------+---------+ nBlock = 10 + nCompr + 3 + nPad Debug.Print "Encryption block = " & nBlock & " bytes = " & nBlock / BLOCK_LEN & " blocks" ReDim abBlock(nBlock - 1) ' -- Two-byte signature 0x5A04 abBlock(0) = &H5A ' =Asc("Z") abBlock(1) = &H4 ' =0x04 iOffset = 2 ' -- convert CLEN and ULEN integers into 4-byte arrays in big-endian order ' -- and add to block abNumber = BytesFromLong(nCompr) For i = 0 To 3 abBlock(iOffset) = abNumber(i) iOffset = iOffset + 1 Next abNumber = BytesFromLong(nPlain) For i = 0 To 3 abBlock(iOffset) = abNumber(i) iOffset = iOffset + 1 Next ' -- add the compressed data For i = 0 To nCompr - 1 abBlock(iOffset) = abCompr(i) iOffset = iOffset + 1 Next ' -- convert the CRC-24 value into an array and add the last THREE bytes abNumber = BytesFromLong(nCrc24) For i = 1 To 3 ' NOTE: we only add three bytes here abBlock(iOffset) = abNumber(i) iOffset = iOffset + 1 Next ' -- add the padding string For i = 0 To nPad - 1 abBlock(iOffset) = abPad(i) iOffset = iOffset + 1 Next Debug.Print "Encr block (before)= " & cnvHexStrFromBytes(abBlock) ' GENERATE a random 8-byte initialization vector ReDim abInitVec(BLOCK_LEN - 1) Call RNG_NonceData(abInitVec(0), BLOCK_LEN) Debug.Print "IV (hex)=" & cnvHexStrFromBytes(abInitVec) ' CREATE the 128-bit key using stretching with the IV as a salt abKey = MakeStretchedKey(strPassword, abInitVec, BLOCK_LEN, STRETCH_COUNT) nKey = UBound(abKey) + 1 Debug.Print "KEY (hex)=" & cnvHexStrFromBytes(abKey) ' ENCRYPT THE BLOCK using Blowfish in CBC mode using the key and IV nRet = BLF_BytesMode(abBlock(0), abBlock(0), nBlock, abKey(0), nKey, ENCRYPT, "CBC", abInitVec(0)) Debug.Print "BLF_BytesMode returns " & nRet & " (expecting 0)" If nRet <> 0 Then Debug.Print "ERROR: encryption operation failed!" GoTo Clean_Up End If Debug.Print "Encr block (after) = " & cnvHexStrFromBytes(abBlock) ' ADD THE MAIN HEADER to the ciphertext block ReDim abEncode(4 + BLOCK_LEN + nBlock - 1) ' -- 4-byte header abEncode(0) = &H4D ' "M" abEncode(1) = &H59 ' "Y" abEncode(2) = &HFC abEncode(3) = &H0 ' -- 8-byte IV iOffset = 4 For i = 0 To BLOCK_LEN - 1 abEncode(iOffset + i) = abInitVec(i) Next ' -- ciphertext block iOffset = 4 + BLOCK_LEN For i = 0 To nBlock - 1 abEncode(iOffset + i) = abBlock(i) Next nEncode = 4 + BLOCK_LEN + nBlock Debug.Print "To encode (hex) =" Debug.Print "<-------HEADER---------><--ENCR BLOCK-->>" Debug.Print cnvHexStrFromBytes(abEncode) ' ENCODE TO BASE 64 strBase64 = cnvB64StrFromBytes(abEncode) Call WIPE_Data(abEncode(0), nEncode) Debug.Print "Base64=" & strBase64 ' ADD FRAMING AND LINE-BREAKS every 60 chars strOutput = FRAME_BEGIN & vbCrLf nChars = Len(strBase64) iOffset = 1 Do While nChars > FRAME_LEN strOutput = strOutput & Mid$(strBase64, iOffset, FRAME_LEN) & vbCrLf nChars = nChars - FRAME_LEN iOffset = iOffset + FRAME_LEN Loop ' -- append final line, if any If nChars > 0 Then strOutput = strOutput & Mid$(strBase64, iOffset, nChars) & vbCrLf End If strOutput = strOutput & FRAME_END & vbCrLf ' OUTPUT the MySecret-formatted string MySecret_Encrypt = strOutput Clean_Up: Call WIPE_Data(abPlain(0), nPlain) Call WIPE_Data(abCompr(0), nCompr) Call WIPE_Data(abPad(0), nPad) Call WIPE_Data(abBlock(0), nBlock) Call WIPE_Data(abKey(0), nKey) Call WIPE_Data(abEncode(0), nEncode) Call WIPE_String(strBase64, Len(strBase64)) Call WIPE_String(strOutput, Len(strOutput)) End Function Public Function MySecret_Decrypt(strInput As String, strPassword As String) As String ' INPUT: password, string of MySecret v3 base64-encoded ciphertext ' OUTPUT: decrypted plaintext string or empty string on error Dim strBase64 As String Dim nBeg As Long Dim nEnd As Long Dim i As Long Dim iOffset As Long Dim nRet As Long Dim abEncode() As Byte Dim nEncode As Long Dim abInitVec() As Byte Dim abKey() As Byte Dim nKey As Long Dim abBlock() As Byte Dim nBlock As Long Dim abCompr() As Byte Dim nCompr As Long Dim abPlain() As Byte Dim nPlain As Long Dim nPad As Long Dim nCrc24 As Long Dim nCrcChk As Long If Len(strInput) = 0 Then Debug.Print "ERROR: nothing to decrypt!" Exit Function End If ' Pre-set byte arrays in case we fail - see Clean_Up abEncode = StrConv("", vbFromUnicode) abKey = StrConv("", vbFromUnicode) abBlock = StrConv("", vbFromUnicode) abCompr = StrConv("", vbFromUnicode) abPlain = StrConv("", vbFromUnicode) Debug.Print "Input = " & Len(strInput) & " bytes" ' REMOVE FRAMING, get base64 data nBeg = InStr(1, strInput, FRAME_STUB) If nBeg <= 0 Then Debug.Print "ERROR: Not valid MySecret data!" Exit Function End If nBeg = nBeg + Len(FRAME_STUB) ' -- skip to start of base64 data (old-style headers may be "BEGIN MYSECRETxxx-----") nBeg = InStr(nBeg, strInput, "-") Do While nBeg > 0 And nBeg < Len(strInput) And Mid(strInput, nBeg, 1) = "-" nBeg = nBeg + 1 Loop If nBeg >= Len(strInput) Then Debug.Print "ERROR: Not valid MySecret data!" Exit Function End If nEnd = InStr(nBeg, strInput, FRAME_END) If nEnd <= 0 Or nEnd <= nBeg Then Debug.Print "ERROR: Not valid MySecret data!" Exit Function End If strBase64 = Mid$(strInput, nBeg, nEnd - nBeg) Debug.Print "Base64=" & strBase64 ' DECODE BASE64 to byte array (any non-base-64 chars, including CR-LFs, are ignored) abEncode = cnvBytesFromB64Str(strBase64) nEncode = UBound(abEncode) + 1 Debug.Print "Encoded data = " & nEncode & " bytes" Debug.Print cnvHexStrFromBytes(abEncode) '+------+-------+-----------------------------------------------------------+ '|SIG(4)| IV(8) | Ciphertext... | '+------+-------+-----------------------------------------------------------+ ' We expect 4 signature bytes 0x4D59FC00 and at least 15 bytes of data If nEncode < 15 Or abEncode(0) <> &H4D Or abEncode(1) <> &H59 Or abEncode(3) <> &H0 Then Debug.Print "ERROR: Not MySecret data." GoTo Clean_Up End If ' -- the third byte gives us the version: 0xFC=v3, 0xFD=v2 If abEncode(2) <> &HFC Then Debug.Print "ERROR: Sorry, we only decrypt version 3." GoTo Clean_Up End If Debug.Print "Found MYSECRET signature for version 3" ' Copy the 8-byte IV ReDim abInitVec(BLOCK_LEN - 1) iOffset = 4 For i = 0 To BLOCK_LEN - 1 abInitVec(i) = abEncode(iOffset + i) Next Debug.Print "IV=" & cnvHexStrFromBytes(abInitVec) ' RE-CREATE THE KEY ' CREATE the 128-bit key using stretching with the IV as a salt abKey = MakeStretchedKey(strPassword, abInitVec, BLOCK_LEN, STRETCH_COUNT) Debug.Print "KEY=" & cnvHexStrFromBytes(abKey) nKey = UBound(abKey) + 1 ' Copy the decryption block iOffset = BLOCK_LEN + 4 nBlock = nEncode - iOffset ReDim abBlock(nBlock - 1) For i = 0 To nBlock - 1 abBlock(i) = abEncode(iOffset + i) Next Debug.Print "Encr block (before)=" & cnvHexStrFromBytes(abBlock) ' DECRYPT the entire block nRet = BLF_BytesMode(abBlock(0), abBlock(0), nBlock, abKey(0), nKey, DECRYPT, "CBC", abInitVec(0)) Debug.Print "BLF_BytesMode returns " & nRet & " (expecting 0)" If nRet <> 0 Then Debug.Print "ERROR: Decryption error." GoTo Clean_Up End If Debug.Print "Encr block (after) =" & cnvHexStrFromBytes(abBlock) ' PARSE the v3 block '|<------------------------nBlock--------------------------->| '|<--------10--------->|<------nCompr------>|<--3->|<-nPad-->| '+-----+-------+-------+--------------------+------+---------+ '|ZS(2)|CLEN(4)|ULEN(4)| Compressed data... |CRC(3)|PAD(9-72)| '+-----+-------+-------+--------------------+------+---------+ ' EXAMINE the decrypted block for 2-byte signature If abBlock(0) <> &H5A Or abBlock(1) <> &H4 Then Debug.Print "ERROR: Decryption error." GoTo Clean_Up End If ' EXTRACT lengths from big-endian-encoded values iOffset = 2 nCompr = uwJoin(abBlock(iOffset + 0), abBlock(iOffset + 1), abBlock(iOffset + 2), abBlock(iOffset + 3)) iOffset = 6 nPlain = uwJoin(abBlock(iOffset + 0), abBlock(iOffset + 1), abBlock(iOffset + 2), abBlock(iOffset + 3)) Debug.Print "Compressed length=" & nCompr & ", Uncompressed length=" & nPlain ' CHECK for reasonableness If nCompr < 0 Or nPlain < 0 Or nCompr > nBlock Then Debug.Print "ERROR: Decryption error." GoTo Clean_Up End If ' EXTRACT length of padding string: length is given by the very last byte. nPad = CLng(abBlock(nBlock - 1)) Debug.Print "Padding string is " & nPad & " bytes" ' CONFIRM all lengths now match If nBlock <> 10 + nCompr + 3 + nPad Then Debug.Print "ERROR: Decryption error." GoTo Clean_Up End If ' CHECK GUARD BYTES: we expect 8 bytes of value NPAD at the start of the padding string iOffset = nBlock - nPad For i = 0 To 7 If abBlock(iOffset + i) <> CByte(nPad) Then Debug.Print "ERROR: Decryption error." GoTo Clean_Up End If Next ' EXTRACT the 3-byte CRC-24 value immediately before iOffset = nBlock - nPad - 3 ' -- note this is only 3-bytes long with a zero most-significant byte nCrcChk = uwJoin(&H0, abBlock(iOffset + 0), abBlock(iOffset + 1), abBlock(iOffset + 2)) Debug.Print "CRC-24 value found = " & Hex(nCrcChk) ' DECOMPRESS the plaintext (careful to use exact lengths here) ReDim abCompr(nCompr - 1) ReDim abPlain(nPlain - 1) iOffset = 10 For i = 0 To nCompr - 1 abCompr(i) = abBlock(iOffset + i) Next nRet = ZLIB_Inflate(abPlain(0), nPlain, abCompr(0), nCompr) Debug.Print "ZLIB_Inflate returns " & nRet & " (expecting " & nPlain & ")" If nRet < 0 Then Debug.Print "ERROR: Decryption error (inflate failed)." GoTo Clean_Up End If Debug.Print "Decompressed data (hex)=" & cnvHexStrFromBytes(abPlain) ' COMPUTE the CRC-24 checksum for the recovered plaintext nCrc24 = CRC24_Bytes(abPlain) Debug.Print "CRC-24 = " & Hex(nCrc24) ' VERIFY that the checksums match If nCrc24 <> nCrcChk Then Debug.Print "ERROR: Decryption error (CRC checksum failed)." GoTo Clean_Up End If ' DECODE the byte array to an output string MySecret_Decrypt = StrConv(abPlain, vbUnicode) Clean_Up: If UBound(abEncode) > 0 Then Call WIPE_Data(abEncode(0), nEncode) If UBound(abKey) > 0 Then Call WIPE_Data(abKey(0), nKey) If UBound(abBlock) > 0 Then Call WIPE_Data(abBlock(0), nBlock) If UBound(abCompr) > 0 Then Call WIPE_Data(abCompr(0), nCompr) If UBound(abPlain) > 0 Then Call WIPE_Data(abPlain(0), nPlain) End Function ' ****************** ' INTERNAL FUNCTIONS ' ****************** Private Function MakeStretchedKey(strPassword As String, abSalt() As Byte, nSalt As Long, _ nStretchCount As Long) As Variant ' Returns a 128-bit/16-byte key in a byte array passed back as a VARIANT Dim abPassword() As Byte Dim abTemp() As Byte Dim abDigest() As Byte Dim nPassword As Long Dim nTemp As Long Dim nDigest As Long Dim i As Long Dim iCount As Long Dim iOffset As Long Dim abKey() As Byte Dim nKey As Long Dim nRet As Long ' INPUT: password (p) and salt (s). ' OUTPUT: 16-byte key ' Set X(1) = MD5 (p || s) ' For i = 2 to 1024, set X(i) = MD5 (X(i-1) || p || s) ' Set the key as the final value of X(i). ' where || denotes ordered concatenation of two strings. ' COMMENT: this method is messy to do in VB. In retrospect, the PBKDF1 method from PKCS#5 ' would have been easier and just as secure, but we used this variant (from Schneier, ' Applied Cryptography, p??) in Version 1 and so we stick with it. nKey = 16 nDigest = API_MAX_MD5_BYTES ReDim abDigest(nDigest - 1) abPassword = StrConv(strPassword, vbFromUnicode) nPassword = UBound(abPassword) - LBound(abPassword) + 1 ' Set X(1) = MD5 (p || s) nTemp = nPassword + nSalt ReDim abTemp(nTemp - 1) iOffset = 0 For i = 0 To nPassword - 1 abTemp(iOffset + i) = abPassword(i) Next iOffset = nPassword For i = 0 To nSalt - 1 abTemp(iOffset + i) = abSalt(i) Next nRet = MD5_BytesHash(abDigest(0), abTemp(0), nTemp) ' For i = 2 to 1024, set X(i) = MD5 (X(i-1) || p || s) nTemp = nDigest + nPassword + nSalt ReDim abTemp(nTemp - 1) For iCount = 2 To nStretchCount iOffset = 0 For i = 0 To nDigest - 1 abTemp(iOffset + i) = abDigest(i) Next iOffset = iOffset + nDigest For i = 0 To nPassword - 1 abTemp(iOffset + i) = abPassword(i) Next iOffset = iOffset + nPassword For i = 0 To nSalt - 1 abTemp(iOffset + i) = abSalt(i) Next nRet = MD5_BytesHash(abDigest(0), abTemp(0), nTemp) Next iCount ' Set the key as the final value of X(i). If nKey > nDigest Then nKey = nDigest ReDim abKey(nKey - 1) For i = 0 To nKey - 1 abKey(i) = abDigest(i) Next ' Return key as a Byte array in a Variant MakeStretchedKey = abKey ' Clean up Call WIPE_Data(abTemp(0), nTemp) Call WIPE_Data(abDigest(0), nDigest) Call WIPE_Data(abPassword(0), nPassword) Call WIPE_Data(abKey(0), nKey) End Function ' ************************************************************************ ' FUNCTIONS TO CONVERT BETWEEN 32-BIT INTEGERS AND BIG-ENDIAN BYTES ARRAYS ' ************************************************************************ Private Function BytesFromLong(ByVal w As Long) As Variant ' Returns a byte array but as a VARIANT type Dim abBytes() As Byte ReDim abBytes(3) Call uwSplit(w, abBytes(0), abBytes(1), abBytes(2), abBytes(3)) BytesFromLong = abBytes End Function Private Function uwJoin(a As Byte, b As Byte, c As Byte, d As Byte) As Long ' Join 4 x 8-bit bytes into one 32-bit word a.b.c.d uwJoin = ((a And &H7F) * &H1000000) Or (b * &H10000) Or (CLng(c) * &H100) Or d If a And &H80 Then uwJoin = uwJoin Or &H80000000 End If End Function Private Sub uwSplit(ByVal w As Long, a As Byte, b As Byte, c As Byte, d As Byte) ' Split 32-bit word w into 4 x 8-bit bytes a = CByte(((w And &HFF000000) \ &H1000000) And &HFF) b = CByte(((w And &HFF0000) \ &H10000) And &HFF) c = CByte(((w And &HFF00) \ &H100) And &HFF) d = CByte((w And &HFF) And &HFF) End Sub ' ************************************************** ' FUNCTIONS TO CARRY OUT CRC-24 CHECKSUM COMPUTATION ' ************************************************** ' basCRC24: Calculates CRC-24 checksum for a given message string ' Version 1. Published 4 June 2003. '************************* COPYRIGHT NOTICE************************* ' This code was originally written in Visual Basic by David Ireland ' and is copyright (c) 2003-6 D.I. Management Services Pty Limited, ' all rights reserved. ' For more details, see <http://www.di-mgt.com.au/src/basCRC24.txt> '******************************************************************* Public Function CRC24_Bytes(abMessage() As Byte) As Long Const CRC24_INIT As Long = &HB704CE Const CRC24_POLY As Long = &H1864CFB Dim i As Long Dim j As Integer Dim ulCRC As Long ulCRC = CRC24_INIT For i = LBound(abMessage) To UBound(abMessage) ulCRC = ulCRC Xor ulShiftLeftBy16(abMessage(i)) For j = 0 To 7 ulCRC = ulShiftLeftByOne(ulCRC) If (ulCRC And &H1000000) <> 0 Then ulCRC = ulCRC Xor CRC24_POLY End If Next Next CRC24_Bytes = ulCRC And &HFFFFFF End Function Public Function CRC24_String(sMessage As String) As Long Dim abMessage() As Byte ' Use proper VB function to get an array of bytes ' thus avoiding problems with Unicode/ANSI/DBCS character sets abMessage = StrConv(sMessage, vbFromUnicode) CRC24_String = CRC24_Bytes(abMessage) End Function Private Function ulShiftLeftBy16(ByVal wordX As Long) As Long ' Shift 32-bit long value to left by 16 bits ' i.e. VB equivalent of "wordX << 16" in C ' Avoiding problem with sign bit ' Copyright (C) 2000-03 DI Management Services Pty Ltd ulShiftLeftBy16 = (wordX And &H7FFF&) * &H10000 If (wordX And &H8000&) <> 0 Then ulShiftLeftBy16 = ulShiftLeftBy16 Or &H80000000 End If End Function Private Function ulShiftLeftByOne(ByVal wordX As Long) As Long ' Shift 32-bit long value to left by 1 bits ' i.e. VB equivalent of "wordX << 1" in C ' Avoiding problem with sign bit ' Copyright (C) 2000-03 DI Management Services Pty Ltd ulShiftLeftByOne = (wordX And &H7FFFFFFF) * &H2 If (wordX And &H8000000) <> 0 Then ulShiftLeftByOne = ulShiftLeftByOne Or &H80000000 End If End Function