Looking for a simple text encryption/decryption VB6 code. Ideally, the solution should accept (text, password) arguments and produce readable output (without any special characters), so it can be used anywhere without encoding issues.
There are lots of code available for .NET, but not really much I can find for legacy VB6. Only this I've found so far: http://www.devx.com/vb2themax/Tip/19211
I'm using RC4 implementation like this
Option Explicit
Private Sub Command1_Click()
Dim sSecret As String
sSecret = ToHexDump(CryptRC4("a message here", "password"))
Debug.Print sSecret
Debug.Print CryptRC4(FromHexDump(sSecret), "password")
End Sub
Public Function CryptRC4(sText As String, sKey As String) As String
Dim baS(0 To 255) As Byte
Dim baK(0 To 255) As Byte
Dim bytSwap As Byte
Dim lI As Long
Dim lJ As Long
Dim lIdx As Long
For lIdx = 0 To 255
baS(lIdx) = lIdx
baK(lIdx) = Asc(Mid$(sKey, 1 + (lIdx Mod Len(sKey)), 1))
Next
For lI = 0 To 255
lJ = (lJ + baS(lI) + baK(lI)) Mod 256
bytSwap = baS(lI)
baS(lI) = baS(lJ)
baS(lJ) = bytSwap
Next
lI = 0
lJ = 0
For lIdx = 1 To Len(sText)
lI = (lI + 1) Mod 256
lJ = (lJ + baS(lI)) Mod 256
bytSwap = baS(lI)
baS(lI) = baS(lJ)
baS(lJ) = bytSwap
CryptRC4 = CryptRC4 & Chr$((pvCryptXor(baS((CLng(baS(lI)) + baS(lJ)) Mod 256), Asc(Mid$(sText, lIdx, 1)))))
Next
End Function
Private Function pvCryptXor(ByVal lI As Long, ByVal lJ As Long) As Long
If lI = lJ Then
pvCryptXor = lJ
Else
pvCryptXor = lI Xor lJ
End If
End Function
Public Function ToHexDump(sText As String) As String
Dim lIdx As Long
For lIdx = 1 To Len(sText)
ToHexDump = ToHexDump & Right$("0" & Hex(Asc(Mid(sText, lIdx, 1))), 2)
Next
End Function
Public Function FromHexDump(sText As String) As String
Dim lIdx As Long
For lIdx = 1 To Len(sText) Step 2
FromHexDump = FromHexDump & Chr$(CLng("&H" & Mid(sText, lIdx, 2)))
Next
End Function
Command1 outputs this:
9ED5556B3F4DD5C90471C319402E
a message here
You might need better error handling on FromHexDump though.
Update (2018-05-04)
For much stronger AES 256-bit encryption (in ECB mode) and proper handling of unicode texts/passwords you can check out Simple AES 256-bit password protected encryption as implemented in mdAesEcb.bas module (~380 LOC).
MD5sum the the text and password together as a one way hash (and then to check, you encrypt again and compare with the stored hash. (This won't work if you MUST decrypt it again though)
Here's my encryption class. I use several constants to define the encryption key because in my mind it's a little more secure from someone trying to decompile the code to find it. Cryptography isn't my thing so maybe I'm kidding myself. Anyway, I used this class in an ActiveX dll called from other programs to do encryption and the reverse in a separate dll for decryption. I did it this way so people who shouldn't be seeing encrypted data don't even have the dll to do the decrypting. Change the key constants to what you want (5 long). I use a mix including unprintable characters and it has worked well for me so far. The CAPICOM is part of Windows® so you don't have to distribute.
Option Explicit
Private m_oENData As CAPICOM.EncryptedData
'combine these constants to build the encryption key
Private Const KEY1 = "12345"
Private Const KEY2 = "67890"
Private Const KEY3 = "abcde"
Private Const KEY4 = "fghij"
Private Const KEY5 = "klmno"
Private Sub Class_Initialize()
On Error Resume Next
Set m_oENData = New CAPICOM.EncryptedData
If Err.Number <> 0 Then
If Err.Number = 429 Then
Err.Raise Err.Number, App.EXEName, "Failed to create the capi com object. " & _
"Check that the capicom.dll file is installed and properly registered."
Else
Err.Raise Err.Number, Err.Source, Err.Description
End If
End If
End Sub
Private Sub Class_Terminate()
Set m_oENData = Nothing
End Sub
Public Function EncryptAsBase64(ByVal RawString As String) As String
EncryptAsBase64 = Encrypt(RawString, CAPICOM_ENCODE_BASE64)
End Function
Public Function EncryptAsBinary(ByVal RawString As String) As String
EncryptAsBinary = Encrypt(RawString, CAPICOM_ENCODE_BINARY)
End Function
Private Function Encrypt(ByVal s As String, ByVal EncryptionType As CAPICOM.CAPICOM_ENCODING_TYPE) As String
Dim oEN As New CAPICOM.EncryptedData
Dim intENCType As CAPICOM.CAPICOM_ENCRYPTION_ALGORITHM
Dim strSecret As String
Dim intTries As Integer
On Error GoTo errEncrypt
intENCType = CAPICOM_ENCRYPTION_ALGORITHM_AES ' try this first and fall back if not supported
With oEN
startEncryption:
.Algorithm = intENCType
strSecret = KEY2 & KEY5 & KEY4 & KEY1 & KEY3
.SetSecret strSecret
strSecret = ""
.Content = s
' the first encryption type needs to be base64 as the .content property
' can loose information if I try to manipulate a binary string
.Content = StrReverse(.Encrypt(CAPICOM_ENCODE_BASE64))
strSecret = KEY1 & KEY4 & KEY3 & KEY2 & KEY5
.SetSecret strSecret
strSecret = ""
Encrypt = .Encrypt(EncryptionType)
End With
Set oEN = Nothing
Exit Function
errEncrypt:
If Err.Number = -2138568448 Then
' if this is the first time the step the encryption back and try again
If intTries < 1 Then
intTries = intTries + 1
intENCType = CAPICOM_ENCRYPTION_ALGORITHM_3DES
Resume startEncryption
End If
End If
Err.Raise Err.Number, Err.Source & ":Encrypt", Err.Description
strSecret = ""
Set oEN = Nothing
End Function
Related
I have strings (they are actually part numbers) in text files that have not been entered correctly (in full). I need to split and then concatenate them to represent the full part number.
For example:
String ZVN-798-100A/B/C should have been entered as:
ZVN-798-100A
ZVN-798-100B
ZVN-798-100C
String XPD-279-100 to 103 should have been entered as:
XPD-279-100
XPD-279-101
XPD-279-102
XPD-279-103
My code splits these correctly:
AA-10-100A/B/C
BB-20-100A to C
DD-40-100 / 110 / 120
EE-50-100A~H
But not these:
CC-30-100 thru 105
FF-60-110 to 15
For simplicity of posting to SO I have created a single sub of my code:
Private Sub btnRun_Click(sender As System.Object, e As System.EventArgs) Handles btnRun.Click
Dim arrSplitEach(2) As String
arrSplitEach(0) = "\"
arrSplitEach(1) = "/"
arrSplitEach(2) = ","
Dim arrSplitAll(2) As String
arrSplitAll(0) = " to "
arrSplitAll(1) = " thru "
arrSplitAll(2) = "~"
Dim strFromFile(5) As String
strFromFile(0) = "AA-10-100A/B/C"
strFromFile(1) = "BB-20-100A to C"
strFromFile(2) = "CC-30-100 thru 15"
strFromFile(3) = "DD-40-100 / 110 / 120"
strFromFile(4) = "EE-50-100A~H"
strFromFile(5) = "FF-60-100 to 115"
Dim arrOutput As New ArrayList
Dim iSplitEach As Integer
Dim iSplitAll As Integer
Dim strSplitter As String
rtbOutput.Clear()
rtbOutput.Update()
For iString As Integer = LBound(strFromFile) To UBound(strFromFile)
Dim s As String = strFromFile(iString).ToString.Trim
If s <> "" Then
For iSplitEach = LBound(arrSplitEach) To UBound(arrSplitEach)
strSplitter = arrSplitEach(iSplitEach).ToString
If s.Contains(strSplitter) Then
Dim parts As Array = Replace(s, " ", "").Split(strSplitter)
Dim derived As New List(Of String)
derived.Add(parts(0))
Dim intLoopParts As Integer
For intLoopParts = 1 To parts.Length - 1
If Not Len(parts(intLoopParts)) = 0 And Not parts(0).Length < Len(parts(intLoopParts)) Then
derived.Add(parts(0).Remove(parts(0).Length - Len(parts(intLoopParts))) & parts(intLoopParts))
End If
Next
For Each strPart As String In derived
'If strNotVerifiedSplit.Contains(strPart.ToLower.Trim) = False Then
If Not arrOutput.Contains(strPart.Trim) Then
arrOutput.Add(Replace(strPart.Trim, " ", ""))
strFromFile(iString).Equals(strFromFile(iString) & " | Split")
End If
Next
derived.Clear()
End If
Next iSplitEach
For iSplitAll = LBound(arrSplitAll) To UBound(arrSplitAll)
strSplitter = arrSplitAll(iSplitAll).ToString
If s.Contains(strSplitter) Then
Dim strMain As String = Replace(Strings.Left(s, InStr(s, strSplitter) - 1), " ", "")
Dim strStart As String = Mid(s, InStr(s, strSplitter) - 1, 1)
Dim strEnd As String = Strings.Right(s, 1)
Dim strToPlace As String
For Each c As Char In "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray()
strToPlace = Strings.Left(strMain, Len(strMain) - 1) & c
If Not strToPlace = "" Then
If Not arrOutput.Contains(strToPlace.Trim) Then
arrOutput.Add(Replace(strToPlace, " ", ""))
strFromFile(iString).Equals(strFromFile(iString) & " | Split")
End If
End If
If c = strEnd Then
Exit For
End If
Next c
End If
Next iSplitAll
End If
s = ""
Next iString
For iOutput As Integer = 0 To arrOutput.Count - 1
rtbOutput.SelectionStart = rtbOutput.TextLength
rtbOutput.SelectionLength = 0
If Not arrOutput(iOutput) = "" Then
rtbOutput.AppendText(arrOutput(iOutput).Trim & vbCrLf)
End If
Next
End Sub
I have found many articles about splitting strings, but do not see a duplicate to this specific case.
It seems like overkill to have to add another chunk of code just to deal with the number ranges and I hope someone can offer some wise advice to improve my existing code.
I would do it like this and avoid the VB6 code style:
Private fList() As String = {"\", "/", ","}
Private fRange() As String = {" to ", " thru ", "~"}
Private Const Letters As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Private Function SplitParts(Part As String) As IEnumerable(Of String)
Dim S, Vals() As String
For Each S In fList
Vals = Split(Part, S)
If Vals.Length > 1 Then Return FixList(Vals)
Next
For Each S In fRange
Vals = Split(Part, S)
If Vals.Length > 1 Then Return FixRange(Vals)
Next
Return {Part}
End Function
Private Function FixList(Vals() As String) As List(Of String)
Dim Ret As New List(Of String), First, Suffix As String
First = Vals.First.Trim
Ret.Add(First)
For i As Integer = 1 To Vals.Length - 1
Suffix = Vals(i).Trim
Ret.Add(First.Substring(0, First.Length - Suffix.Length) & Suffix)
Next
Return Ret
End Function
Private Function FixRange(Vals() As String) As IEnumerable(Of String)
Dim Range As New List(Of String), First, Last, Format As String, i, iMin, iMax As Integer
First = Vals.First.Trim : Last = Vals.Last.Trim
If Integer.TryParse(Last, iMax) AndAlso Integer.TryParse(First.Substring(First.Length - Last.Length), iMin) Then
Format = New String("0"c, Last.Length)
For i = iMin To iMax
Range.Add(i.ToString(Format))
Next
ElseIf Last.Length = 1 Then
iMin = Letters.IndexOf(First.Last) : iMax = Letters.IndexOf(Last)
If iMin >= 0 AndAlso iMax >= 0 Then
For i = iMin To iMax
Range.Add(Letters(i))
Next
End If
End If
First = First.Substring(0, First.Length - Vals.Last.Trim.Length) 'Prefix
Return Range.Select(Function(X) First & X)
End Function
I've got a list of about 20,000 strictly alpha/text character strings outputted as a CSV file to Excel, but it's quite a mess.
What I want to do is query a separate, reference file of English dictionary words so that I can essentially create a lookup and return the dictionary word, minus a load of the text noise that is either prepended or appended to the string. Example below.
xyzbuildingcontractor = Building Contractor
upholsteryabcdef = Upholstery
lmnoengineer = Engineer
As a relative n00b programmer I just want to gauge opinion as to the best way to do this and whether Excel is the best platform to use.
Any guidance would be very gratefully recieved, thanks in advance.
Jim
Ok, this is a very rough draft which you might have to tweak, but the general idea is this:
A Trie is used to build a dictionary of words
A clsTrieIterator class allows tracking multiple words at a time within the Trie
The string to be tested is parsed one character at a time, each one starting a new clsTrieIterator
All the existing active clsTrieIterators consume each next character, and if the resulting combination of characters is not possible given the dictionary, it stops being tracked
Here is a short example of the use:
Public Sub Main()
Dim wf As clsWordFinder
Set wf = New clsWordFinder
wf.Add "Building"
wf.Add "Contractor"
wf.Add "Upholstery"
wf.Add "Engineer"
Debug.Print wf.getWordsFromString("xyzbuildingcontractor")
Debug.Print wf.getWordsFromString("upholsteryabcdef")
Debug.Print wf.getWordsFromString("lmnoengineer")
End Sub
Which outputs the following to the immediate window in VBA:
Building Contractor
Upholstery
Engineer
...and below are the classes.
clsTrieNode is each individual node of the tree. It represents a single letter and it may have up to 26 children, assuming they form valid words in the dictionary. If the combination of characters, node by node down the tree from the root to this point forms a word, the Trie will set "isWord".
Option Compare Database
Option Explicit
Public KeyChar As String
Public isWord As Boolean
Private m_Children(0 To 25) As clsTrieNode
Public Property Get Child(strChar As String) As clsTrieNode
'better be ONE char
Set Child = m_Children(charToIndex(strChar))
End Property
Public Property Set Child(strChar As String, oNode As clsTrieNode)
Set m_Children(charToIndex(strChar)) = oNode
End Property
Private Function charToIndex(strChar As String) As Long
charToIndex = Asc(strChar) - 97 'asc("a")
End Function
clsTrie is the public facing interface to interact with the tree of nodes that forms the trie. It contains an Add method to put words into the dictionary and an isWord method which allows testing a string against the trie dictionary to see if it is a valid word. Remove is a method that is nice to have, but probably not necessary for your problem, so I haven't implemented it.
Option Compare Database
Option Explicit
Private m_Head As clsTrieNode
Private Sub Class_Initialize()
Set m_Head = New clsTrieNode
End Sub
Public Sub Add(strKey As String)
Dim currNode As clsTrieNode
Dim tempNode As clsTrieNode
Set currNode = m_Head
Dim strLCaseKey As String
strLCaseKey = LCase(strKey)
Dim i As Long
For i = 1 To Len(strLCaseKey)
If Not currNode.Child(Mid(strLCaseKey, i, 1)) Is Nothing Then
Set currNode = currNode.Child(Mid(strLCaseKey, i, 1))
Else
Exit For
End If
Next
For i = i To Len(strLCaseKey)
Set tempNode = New clsTrieNode
tempNode.KeyChar = Mid(strLCaseKey, i, 1)
Set currNode.Child(Mid(strLCaseKey, i, 1)) = tempNode
Set currNode = tempNode
Next
currNode.isWord = True
End Sub
Public Sub Remove(strKey As String)
'Might be nice to have
End Sub
Public Function isWord(strKey As String)
Dim currNode As clsTrieNode
Set currNode = m_Head
Dim strLCaseKey As String
strLCaseKey = LCase(strKey)
Dim i As Long
For i = 1 To Len(strLCaseKey)
If Not currNode.Child(Mid(strLCaseKey, i, 1)) Is Nothing Then
Set currNode = currNode.Child(Mid(strLCaseKey, i, 1))
Else
isWord = False
Exit Function
End If
Next
If currNode.isWord Then
isWord = True
Else
isWord = False
End If
End Function
Public Function getIterator() As clsTrieIterator
Dim oIterator As clsTrieIterator
Set oIterator = New clsTrieIterator
oIterator.Init m_Head
Set getIterator = oIterator
End Function
clsTrieIterator is a special class returned by clsTrie which allows parsing of a string to be done character by character with consumeChar instead of all at once as with clsTrie.isWord. This allows some freedom in parsing the string without backtracking or reading the same character more than once and it allows finding words when you are not sure how long they will be.
Option Compare Database
Option Explicit
Private m_currNode As clsTrieNode
Private m_currString As String
Public Property Get getCurrentString() As String
getCurrentString = m_currString
End Property
Public Sub Init(oNode As clsTrieNode)
Set m_currNode = oNode
End Sub
Public Function consumeChar(strChar As String) As Boolean
Dim strLCaseChar As String
strLCaseChar = LCase(strChar)
If Not m_currNode.Child(strLCaseChar) Is Nothing Then
consumeChar = True
Set m_currNode = m_currNode.Child(strLCaseChar)
m_currString = m_currString & strChar
Else
consumeChar = False
Set m_currNode = Nothing
End If
End Function
Public Function isWord() As Boolean
isWord = m_currNode.isWord
End Function
clsWordFinder puts everything together in a simple api tailored to your specific problem. It might be worth adding some logic to handle different behavior, like "greedy" matching vs "lazy" matching and overlapping vs nonoverlapping word parsing.
Option Compare Database
Option Explicit
Private m_Trie As clsTrie
Private Sub Class_Initialize()
Set m_Trie = New clsTrie
End Sub
Public Sub Add(strWord As String)
m_Trie.Add strWord
End Sub
Public Function getWordsFromString(strString As String) As String
Dim colIterators As Collection
Set colIterators = New Collection
Dim colMatches As Collection
Set colMatches = New Collection
Dim oIterator As clsTrieIterator
Dim strMatch As String
Dim i As Long
Dim iter
For i = 1 To Len(strString)
Set oIterator = m_Trie.getIterator
colIterators.Add oIterator, CStr(ObjPtr(oIterator))
For Each iter In colIterators
If Not iter.consumeChar(Mid(strString, i, 1)) Then
colIterators.Remove CStr(ObjPtr(iter))
ElseIf iter.isWord() Then
strMatch = iter.getCurrentString
Mid(strMatch, 1, 1) = UCase(Mid(strMatch, 1, 1))
colMatches.Add strMatch
colIterators.Remove CStr(ObjPtr(iter))
End If
Next
Next
getWordsFromString = JoinCollection(colMatches)
End Function
Public Function getWordsCollectionFromString(strString As String) As Collection
Dim colIterators As Collection
Set colIterators = New Collection
Dim colMatches As Collection
Set colMatches = New Collection
Dim oIterator As clsTrieIterator
Dim strMatch As String
Dim i As Long
Dim iter
For i = 1 To Len(strString)
Set oIterator = m_Trie.getIterator
colIterators.Add oIterator, CStr(ObjPtr(oIterator))
For Each iter In colIterators
If Not iter.consumeChar(Mid(strString, i, 1)) Then
colIterators.Remove CStr(ObjPtr(iter))
ElseIf iter.isWord() Then
strMatch = iter.getCurrentString
Mid(strMatch, 1, 1) = UCase(Mid(strMatch, 1, 1))
colMatches.Add strMatch
colIterators.Remove CStr(ObjPtr(iter))
End If
Next
Next
Set getWordsCollectionFromString = colMatches
End Function
Private Function JoinCollection(colStrings As Collection, Optional strDelimiter = " ") As String
Dim strOut As String
Dim i As Long
If colStrings.Count > 0 Then
strOut = colStrings.Item(1)
For i = 2 To colStrings.Count
strOut = strOut & strDelimiter & colStrings.Item(i)
Next
JoinCollection = strOut
End If
End Function
For some protocol testing I need to randomize the case of each character in a lot of strings.
The strings are commands, created by my application, which will be sent via a winsock control to a client.
As it involves a lot of strings I want each part to be as fast as possible.
Right now I have:
Private Function RandomCaps(strText As String) As String
Dim lngChar As Long
Dim strLower As String, strUpper As String
Dim strRandom As String
strRandom = ""
strLower = LCase$(strText)
strUpper = UCase$(strText)
For lngChar = 1 To Len(strText)
If Int(2 * Rnd) = 0 Then
strRandom = strRandom & Mid$(strLower, lngChar, 1)
Else
strRandom = strRandom & Mid$(strUpper, lngChar, 1)
End If
Next lngChar
RandomCaps = strRandom
End Function
This is pretty straightforward, but probably not the fastest way.
What could I do to improve its speed?
Instead of concatenating strings together, use Mid to change the string in-place:
Private Function RandomCaps(s As String) As String
Dim uc As String
Dim i As Long
RandomCaps = LCase$(s)
uc = UCase$(s)
For i = 1 To Len(s)
If Rnd < 0.5 Then
Mid(RandomCaps, i, 1) = Mid(uc, i, 1)
End If
Next i
End Function
You can try using MidB, but it hardly makes any difference – and since it works with individual bytes, you're in for some nasty surprises if you don't know how VB6 stores strings.
Use MidB() instead of Mid.
MidB is a bit faster.
The other solution could be to copy the stringpointer into an Array of integer.
for example:
Public Type TUDTPtr
pSA As Long
Reserved As Long ' z.B. für vbVarType oder IRecordInfo
cDims As Integer
fFeatures As Integer
cbElements As Long
cLocks As Long
pvData As Long
cElements As Long
lLBound As Long
End Type
Public Type TCharPointer
pudt As TUDTPtr
Chars() As Integer
End Type
Public Enum SAFeature
FADF_AUTO = &H1
FADF_STATIC = &H2
FADF_EMBEDDED = &H4
FADF_FIXEDSIZE = &H10
FADF_RECORD = &H20
FADF_HAVEIID = &H40
FADF_HAVEVARTYPE = &H80
FADF_BSTR = &H100
FADF_UNKNOWN = &H200
FADF_DISPATCH = &H400
FADF_VARIANT = &H800
FADF_RESERVED = &HF008
End Enum
Public Declare Sub RtlMoveMemory Lib "kernel32" ( _
ByRef pDst As Any, _
ByRef pSrc As Any, _
ByVal bLength As Long)
Public Declare Sub RtlZeroMemory Lib "kernel32" ( _
ByRef pDst As Any, _
ByVal bLength As Long)
Public Declare Function ArrPtr Lib "msvbvm60" _
Alias "VarPtr" ( _
ByRef pArr() As Any) As Long
Public Sub New_UDTPtr(ByRef this As TUDTPtr, _
ByVal Feature As SAFeature, _
ByVal bytesPerElement As Long, _
Optional ByVal CountElements As Long = 1, _
Optional ByVal lLBound As Long = 0)
With this
.pSA = VarPtr(.cDims)
.cDims = 1
.cbElements = bytesPerElement
.fFeatures = CInt(Feature)
.cElements = CountElements
.lLBound = lLBound
End With
End Sub
Public Sub New_CharPointer(ByRef this As TCharPointer, ByRef StrVal As String)
With this
Call New_UDTPtr(.pudt, FADF_AUTO Or FADF_FIXEDSIZE, 2, Len(StrVal), 1)
With .pudt
.pvData = StrPtr(StrVal)
End With
Call RtlMoveMemory(ByVal ArrPtr(.Chars), ByVal VarPtr(.pudt), 4)
End With
End Sub
Public Sub DeleteCharPointer(ByRef this As TCharPointer)
With this
Call RtlZeroMemory(ByVal ArrPtr(.Chars), 4)
End With
End Sub
your function then could look like:
Private Sub RandomCapsX(strText As String) 'As String
Dim i As Long
Dim p As TCharPointer: Call MCharPointer.New_CharPointer(p, strText)
For i = 1 To p.pudt.cElements
Select Case p.Chars(i)
Case 65 To 90
'Uppercase
p.Chars(i) = p.Chars(i) + Int(2 * Rnd) * 32
Case 97 To 122
'lowercase
p.Chars(i) = p.Chars(i) - Int(2 * Rnd) * 32
End Select
Next
Call MCharPointer.DeleteCharPointer(p)
End Sub
To optimize code by RDHS, you don't really need to keep an uppercase version of the string. I think this is as optimized as you can get.
CODE 1:
Private Function RandomCaps(s As String) As String
Dim i As Long
RandomCaps = LCase$(s)
For i = 1 To Len(s)
If Rnd < 0.5 Then
Mid(RandomCaps, i, 1) = UCase(Mid(RandomCaps, i, 1))
End If
Next i
End Function
The code above is good, however, in the case of really really large strings, you might wanna try this (not tested for performance vs RDHS's code):
CODE 2:
Private Function RandomCaps(s As String) As String
Dim b() As Byte
b = StrConv(Text1.Text, vbFromUnicode)
Dim i As Long
For i = 0 To UBound(b) - 1
If Rnd < 0.5 Then
If UCase(Chr(b(i))) = Chr(b(i)) Then
'original char is uppercase, make it lowercase
b(i) = Asc(LCase(Chr(b(i))))
Else
'original char is lowercase, make it uppercase
b(i) = Asc(UCase(Chr(b(i))))
End If
End If
Next i
RandomCaps = StrConv(b, vbUnicode)
End Function
EDIT:
I did some performance testing and the difference between the two codes above is negligible: the 2nd code block is only about 1% faster then the first one.
EDIT 2:
Disregard my previous edit. Code 2 is approximately 50% less efficient as Code 1. however, as RDHS suggested, I adjusted code 2 to compare the values instead of going back and forth from CHR to ASC and it is more efficient starting with input strings that are approximately 40 characters long. The longer the input string, the better code 3 performance. With an input string that is 944640 characters long, Code 3 is 57% faster then Code 1.
Statistics:
First column is the length of the input string (in chars)
Second column is Code 3 efficiency compared to Code 2.
As you can see, with string length of 5 chars, Code 2 is 46% more efficient. Starting with string length around 40, Code 3 becomes more and more efficient.
5 -46.80%
50 6.22%
100 21.50%
500 38.54%
1000 41.11%
10000 44.87%
100000 43.25%
1260000 43.02%
CODE 3:
Private Function RandomCaps(s As String) As String
Dim b() As Byte
b = StrConv(Text1.Text, vbFromUnicode)
Dim i As Long
For i = 0 To UBound(b) - 1
If Rnd < 0.5 Then
If b(i) >= 64 And b(i) <= 90 Then
'A to Z
b(i) = b(i) + 32
ElseIf b(i) >= 97 And b(i) <= 122 Then
'a to z
b(i) = b(i) - 32
Else
'everything else
End If
End If
Next i
RandomCaps = StrConv(b, vbUnicode)
End Function
I have a problem with the split function I have currently. I am able to either split with 1 delimited only (split()) or split with many single characters (custom()). Is there a way to split this? Keep in mind that these delimiters are not in order.
"MY!!DATA##IS!!LOCATED##HERE!!IN!!BETWEEN##THE##ATS!!AND!!MARKS"
I need your help to get the following result
"MY" , "DATA" , "IS" , "LOCATED" , "HERE" , "IN" , "BETWEEN","THE", "ATS" , "AND", "MARKS"
thanks
Create a new VB6 EXE project and add a button to the form you will be given, and use the following code for the Button1_Click event:
Private Sub Command1_Click()
Dim myText As String
Dim myArray() As String
Dim InBetweenAWord As Boolean
Dim tmpString As String
Dim CurrentCount As Integer
CurrentCount = 0
myText = "MY!!DATA##IS!!LOCATED##HERE!!IN!!BETWEEN##THE##ATS!!AND!!MARKS"
For i = 1 To Len(myText)
If (Mid(myText, i, 1) = "#" Or Mid(myText, i, 1) = "!") And InBetweenAWord = True Then
CurrentCount = CurrentCount + 1
ReDim Preserve myArray(CurrentCount)
myArray(CurrentCount) = tmpString
tmpString = ""
InBetweenAWord = False
Else
If (Mid(myText, i, 1) <> "#" And Mid(myText, i, 1) <> "!") Then
tmpString = tmpString & Mid(myText, i, 1)
InBetweenAWord = True
End If
End If
Next
For i = 1 To CurrentCount
MsgBox myArray(i) 'This will iterate through all of your words
Next
End Sub
Notice that once the first For-Next loop is finished, the [myArray] will contain all of your words without the un-desired characters, so you can use them anywhere you like. I just displayed them as MsgBox to the user to make sure my code worked.
Character handling is really awkward in VB6. I would prefer using built-in functions like this
Private Function MultiSplit(ByVal sText As String, vDelims As Variant) As Variant
Const LNG_PRIVATE As Long = &HE1B6 '-- U+E000 to U+F8FF - Private Use Area (PUA)
Dim vElem As Variant
For Each vElem In vDelims
sText = Replace(sText, vElem, ChrW$(LNG_PRIVATE))
Next
MultiSplit = Split(sText, ChrW$(LNG_PRIVATE))
End Function
Use MultiSplit like this
Private Sub Command1_Click()
Dim vElem As Variant
For Each vElem In MultiSplit("MY!!DATA##IS!!LOCATED##HERE!!IN!!BETWEEN##THE##ATS!!AND!!MARKS", Array("!!", "##"))
Debug.Print vElem
Next
End Sub
I am interested if it's possible to do string encryption/decryption using Excel Visual Basic and some cryptographic service provider.
I have found a walk-through Encrypting and Decrypting Strings in Visual Basic, but it seems it's valid for standalone Visual Basic only.
So would you suggest me another encryption method or show how the walk-through could be adopted for Excel Visual Basic?
The link you provide shows how to perform string encryption and decryption using VB.NET, and thus, using the .NET Framework.
Currently, Microsoft Office products cannot yet use the Visual Studio Tools for Applications component which will enable Office products to access the .NET framework's BCL (base class libraries) which, in turn, access the underlying Windows CSP (cryptographic server provider) and provide a nice wrapper around those encryption/decryption functions.
For the time being, Office products are stuck with the old VBA (Visual Basic for Applications) which is based on the old VB6 (and earlier) versions of visual Basic which are based upon COM, rather than the .NET Framework.
Because of all of this, you will either need to call out to the Win32 API to access the CSP functions, or you will have to "roll-your-own" encryption method in pure VB6/VBA code, although this is likely to be less secure. It all depends upon how "secure" you'd like your encryption to be.
If you want to "roll-your-own" basic string encryption/decryption routine, take a look at these link to get you started:
Encrypt a String Easily
Better XOR Encryption with a readable string
vb6 - encryption function
Visual Basic 6 / VBA String Encryption/Decryption Function
If you want to access the Win32 API and use the underlying Windows CSP (a much more secure option), see these links for detailed information on how to achieve this:
How to encrypt a string in Visual Basic 6.0
Access to CryptEncrypt (CryptoAPI/WinAPI) functions in VBA
That last link is likely the one you'll want and includes a complete VBA Class module to "wrap" the Windows CSP functions.
This code works well for me (3DES Encryption/Decryption):
I store INITIALIZATION_VECTOR and TRIPLE_DES_KEY as environment variables (obviously different values than those posted here) and get them using VBA Environ() function, so all sensitive data (passwords) in VBA code is encrypted.
Option Explicit
Public Const INITIALIZATION_VECTOR = "zlrs$5kd" 'Always 8 characters
Public Const TRIPLE_DES_KEY = ">tlF8adk=35K{dsa" 'Always 16 characters
Sub TestEncrypt()
MsgBox "This is an encrypted string: -> " & EncryptStringTripleDES("This is an encrypted string:")
Debug.Print EncryptStringTripleDES("This is an encrypted string:")
End Sub
Sub TestDecrypt()
MsgBox "u99CVItCGiMQEVYHf8+S22QbJ5CPQGDXuS5n1jvEIgU= -> " & DecryptStringTripleDES("u99CVItCGiMQEVYHf8+S22QbJ5CPQGDXuS5n1jvEIgU=")
End Sub
Function EncryptStringTripleDES(plain_string As String) As Variant
Dim encryption_object As Object
Dim plain_byte_data() As Byte
Dim encrypted_byte_data() As Byte
Dim encrypted_base64_string As String
EncryptStringTripleDES = Null
On Error GoTo FunctionError
plain_byte_data = CreateObject("System.Text.UTF8Encoding").GetBytes_4(plain_string)
Set encryption_object = CreateObject("System.Security.Cryptography.TripleDESCryptoServiceProvider")
encryption_object.Padding = 3
encryption_object.key = CreateObject("System.Text.UTF8Encoding").GetBytes_4(TRIPLE_DES_KEY)
encryption_object.IV = CreateObject("System.Text.UTF8Encoding").GetBytes_4(INITIALIZATION_VECTOR)
encrypted_byte_data = _
encryption_object.CreateEncryptor().TransformFinalBlock(plain_byte_data, 0, UBound(plain_byte_data) + 1)
encrypted_base64_string = BytesToBase64(encrypted_byte_data)
EncryptStringTripleDES = encrypted_base64_string
Exit Function
FunctionError:
MsgBox "TripleDES encryption failed"
End Function
Function DecryptStringTripleDES(encrypted_string As String) As Variant
Dim encryption_object As Object
Dim encrypted_byte_data() As Byte
Dim plain_byte_data() As Byte
Dim plain_string As String
DecryptStringTripleDES = Null
On Error GoTo FunctionError
encrypted_byte_data = Base64toBytes(encrypted_string)
Set encryption_object = CreateObject("System.Security.Cryptography.TripleDESCryptoServiceProvider")
encryption_object.Padding = 3
encryption_object.key = CreateObject("System.Text.UTF8Encoding").GetBytes_4(TRIPLE_DES_KEY)
encryption_object.IV = CreateObject("System.Text.UTF8Encoding").GetBytes_4(INITIALIZATION_VECTOR)
plain_byte_data = encryption_object.CreateDecryptor().TransformFinalBlock(encrypted_byte_data, 0, UBound(encrypted_byte_data) + 1)
plain_string = CreateObject("System.Text.UTF8Encoding").GetString(plain_byte_data)
DecryptStringTripleDES = plain_string
Exit Function
FunctionError:
MsgBox "TripleDES decryption failed"
End Function
Function BytesToBase64(varBytes() As Byte) As String
With CreateObject("MSXML2.DomDocument").createElement("b64")
.DataType = "bin.base64"
.nodeTypedValue = varBytes
BytesToBase64 = Replace(.Text, vbLf, "")
End With
End Function
Function Base64toBytes(varStr As String) As Byte()
With CreateObject("MSXML2.DOMDocument").createElement("b64")
.DataType = "bin.base64"
.Text = varStr
Base64toBytes = .nodeTypedValue
End With
End Function
Source code taken from here: https://gist.github.com/motoraku/97ad730891e59159d86c
Note the difference between the original code and my code, that is additional option encryption_object.Padding = 3 which forces VBA to not perform padding. With padding option set to 3 I get result exactly as in C++ implementation of DES_ede3_cbc_encrypt algorithm and which is in agreement with what is produced by this online tool.
This code works fine in VBA and can easily be moved to VB.NET
Avoids dealing with not "normal" characters. You decide in AllowedChars what characters to allow.
Public Function CleanEncryptSTR(MyString As String, MyPassword As String, Encrypt As Boolean) As String
'Encrypts strings chars contained in Allowedchars
'MyString = String to decrypt
'MyPassword = Password
'Encrypt True: Encrypy False: Decrypt
Dim i As Integer
Dim ASCToAdd As Integer
Dim ThisChar As String
Dim ThisASC As Integer
Dim NewASC As Integer
Dim MyStringEncrypted As String
Dim AllowedChars As String
AllowedChars = "&0123456789;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
If Len(MyPassword) > 0 Then
For i = 1 To Len(MyString)
' ThisASC = Asc(Mid(MyString, i, 1))
' ThisASC = IntFromArray(Asc(Mid(MyString, i, 1)), MyVector())
ThisChar = Mid(MyString, i, 1)
ThisASC = InStr(AllowedChars, ThisChar)
If ThisASC > 0 Then
ASCToAdd = Asc(Mid(MyPassword, i Mod Len(MyPassword) + 1, 1))
If Encrypt Then
NewASC = ThisASC + ASCToAdd
Else
NewASC = ThisASC - ASCToAdd
End If
NewASC = NewASC Mod Len(AllowedChars)
If NewASC <= 0 Then
NewASC = NewASC + Len(AllowedChars)
End If
MyStringEncrypted = MyStringEncrypted & Mid(AllowedChars, NewASC, 1)
Else
MyStringEncrypted = MyStringEncrypted & ThisChar
End If
Next i
Else
MyStringEncrypted = MyString
End If
CleanEncryptSTR = MyStringEncrypted
End Function
Create a Class Module called clsCifrado:
Option Explicit
Option Compare Binary
Private clsClave As String
Property Get Clave() As String
Clave = clsClave
End Property
Property Let Clave(value As String)
clsClave = value
End Property
Function Cifrar(Frase As String) As String
Dim Cachos() As Byte
Dim LaClave() As Byte
Dim i As Integer
Dim Largo As Integer
If Frase <> "" Then
Cachos() = StrConv(Frase, vbFromUnicode)
LaClave() = StrConv(clsClave, vbFromUnicode)
Largo = Len(clsClave)
For i = LBound(Cachos) To UBound(Cachos)
Cachos(i) = (Cachos(i) Xor LaClave(i Mod Largo)) + 34
Next i
Cifrar = StrConv(Cachos(), vbUnicode)
Else
Cifrar = ""
End If
End Function
Function Descifrar(Frase As String) As String
Dim Cachos() As Byte
Dim LaClave() As Byte
Dim i As Integer
Dim Largo As Integer
If Frase <> "" Then
Cachos() = StrConv(Frase, vbFromUnicode)
LaClave() = StrConv(clsClave, vbFromUnicode)
Largo = Len(clsClave)
For i = LBound(Cachos) To UBound(Cachos)
Cachos(i) = Cachos(i) - 34
Cachos(i) = (Cachos(i) Xor LaClave(i Mod Largo))
Next i
Descifrar = StrConv(Cachos(), vbUnicode)
Else
Descifrar = ""
End If
End Function
Now you can use it in your code:
to cipher
Private Sub btnCifrar_Click()
Dim Texto As String
Dim cCifrado As clsCifrado
Set cCifrado = New clsCifrado
'---poner la contraseña
If tbxClave.Text = "" Then
MsgBox "The Password is missing"
End Sub
Else
cCifrado.Clave = tbxClave.Text
End If
'---Sacar los datos
Texto = tbxFrase.Text
'---cifrar el texto
Texto = cCifrado.Cifrar(Texto)
tbxFrase.Text = Texto
End Sub
To descipher
Private Sub btnDescifrar_Click()
Dim Texto As String
Dim cCifrado As clsCifrado
Set cCifrado = New clsCifrado
'---poner la contraseña
If tbxClave.Text = "" Then
MsgBox "The Password is missing"
End Sub
Else
cCifrado.Clave = tbxClave.Text
End If
'---Sacar los datos
Texto = tbxFrase.Text
'---cifrar el texto
Texto = cCifrado.Descifrar(Texto)
tbxFrase.Text = Texto
End Sub
You can call pipe excel cell data through any shell script.
Install the GPL Bert (http://bert-toolkit.com/) R language interface for Excel.
Use the R script below in Excel to pipe cell data to Bash / perl / gpg / openssl.
c:\> cat c:\R322\callable_from_excel.R
CRYPTIT <- function( PLAINTEXT, MASTER_PASS ) {
system(
sprintf("bash -c 'echo '%s' |
gpg --symmetric --cipher-algo blowfish --force-mdc --passphrase '%s' -q |
base64 -w 0'",
PLAINTEXT, MASTER_PASS),
intern=TRUE)
}
DECRYPTIT <- function( CRYPTTEXT, MASTER_PASS ) {
system(
sprintf("bash -c 'echo '%s'|
base64 -d |
gpg --passphrase '%s' -q |
putclip | getclip' ",CRYPTTEXT,MASTER_PASS),
intern=TRUE)
}
In Excel, you can try: C1=CRYPTIT(A1,A2) and C2=DECRYPTIT(C1,A2)
Optional: putclip saves decrypted text in clipboard.
Both functions types are: String -> String.
Usual caveats about escaping single-quotes in single-quoted strings.
Here is a basic symmetric encryption/decryption example:
Sub testit()
Dim inputStr As String
inputStr = "Hello world!"
Dim encrypted As String, decrypted As String
encrypted = scramble(inputStr)
decrypted = scramble(encrypted)
Debug.Print encrypted
Debug.Print decrypted
End Sub
Function stringToByteArray(str As String) As Variant
Dim bytes() As Byte
bytes = str
stringToByteArray = bytes
End Function
Function byteArrayToString(bytes() As Byte) As String
Dim str As String
str = bytes
byteArrayToString = str
End Function
Function scramble(str As String) As String
Const SECRET_PASSWORD As String = "K*4HD%f#nwS%sdf032#gfl!HLKN*pq7"
Dim stringBytes() As Byte, passwordBytes() As Byte
stringBytes = stringToByteArray(str)
passwordBytes = stringToByteArray(SECRET_PASSWORD)
Dim upperLim As Long
upperLim = UBound(stringBytes)
ReDim scrambledBytes(0 To upperLim) As Byte
Dim idx As Long
For idx = LBound(stringBytes) To upperLim
scrambledBytes(idx) = stringBytes(idx) Xor passwordBytes(idx)
Next idx
scramble = byteArrayToString(scrambledBytes)
End Function
Be aware that this will crash if your given input string is longer than the SECRET_PASSWORD. This is just an example to get started with.