Wikipedia: The first byte is placed in the most significant eight bits of a 24-bit buffer,
the next in the middle eight, and the third in the least significant eight bits.
If there are fewer than three bytes to encode, the corresponding buffer bits will be zero.
The buffer is then used, six bits at a time, most significant first, as indices into the string
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" and the indicated character output.
If there were only one or two input bytes, only the first two or three characters of the output
are used and are padded with two or one "=" characters respectively.
The process then repeats on the remaining input data.
Code:
In =
( Join
Man is distinguished, not only by his reason, but by this singular passion from other animals,
which is a lust of the mind, that by a perseverance of delight in the continued and
indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.
)
Out =
( Join
TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0
aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1
c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0
aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdl
LCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=
)
StringCaseSense On
Chars = ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
If Base64(In) = Out
MsgBox OK
If InvBase64(Out) = In
MsgBox OK
Base64(string)
{
Loop Parse, string
{
If Mod(A_Index,3) = 1
buffer := Asc(A_LoopField) << 16
Else If Mod(A_Index,3) = 2
buffer += Asc(A_LoopField) << 8
Else {
buffer += Asc(A_LoopField)
out := out . Code(buffer>>18) . Code(buffer>>12) . Code(buffer>>6) . Code(buffer)
}
}
If Mod(StrLen(string),3) = 0
Return out
If Mod(StrLen(string),3) = 1
Return out . Code(buffer>>18) . Code(buffer>>12) "=="
Return out . Code(buffer>>18) . Code(buffer>>12) . Code(buffer>>6) "="
}
InvBase64(code)
{
StringReplace code, code, =,,All
Loop Parse, code
{
If Mod(A_Index,4) = 1
buffer := DeCode(A_LoopField) << 18
Else If Mod(A_Index,4) = 2
buffer += DeCode(A_LoopField) << 12
Else If Mod(A_Index,4) = 3
buffer += DeCode(A_LoopField) << 6
Else {
buffer += DeCode(A_LoopField)
out := out . Chr(buffer>>16) . Chr(255 & buffer>>8) . Chr(255 & buffer)
}
}
If Mod(StrLen(code),4) = 0
Return out
If Mod(StrLen(code),4) = 2
Return out . Chr(buffer>>16)
Return out . Chr(buffer>>16) . Chr(255 & buffer>>8)
}
Code(i) ; <== Chars[i & 63], 0-base index
{
Global Chars
StringMid i, Chars, (i&63)+1, 1
Return i
}
DeCode(c) ; c = a char in Chars ==> position [0,63]
{
Global Chars
Return InStr(Chars,c,1) - 1
}
And here is a version, which handles binary buffers, not just AHK strings:
Code:
Base64Encode(ByRef bin, n=0) {
m := VarSetCapacity(bin)
If n not between 1 and %m%
n = %m%
Loop %n% {
A := *(&bin+A_Index-1)
m := Mod(A_Index,3)
IfEqual m,1, SetEnv buffer,% A << 16
Else IfEqual m,2, EnvAdd buffer,% A << 8
Else {
buffer += A
out := out Code(buffer>>18) Code(buffer>>12) Code(buffer>>6) Code(buffer)
}
}
IfEqual m,0, Return out
IfEqual m,1, Return out Code(buffer>>18) Code(buffer>>12) "=="
Return out Code(buffer>>18) Code(buffer>>12) Code(buffer>>6) "="
}
Base64Decode(ByRef bin, code) {
StringReplace code, code, =,,All
VarSetCapacity(bin, 3*StrLen(code)//4, 0)
Loop Parse, code
{
m := A_Index & 3 ; mod 4
IfEqual m,0, {
buffer += DeCode(A_LoopField)
Append(bin, pos, buffer>>16, 255 & buffer>>8, 255 & buffer)
}
Else IfEqual m,1, SetEnv buffer, % DeCode(A_LoopField) << 18
Else buffer += DeCode(A_LoopField) << 24-6*m
}
IfEqual m,0, Return
IfEqual m,2
Append(bin, pos, buffer>>16)
Else Append(bin, pos, buffer>>16, 255 & buffer>>8)
}
Append(ByRef bin, ByRef pos, c1, c2="", c3="", c4="") {
pos += 0
Loop 4 {
IfEqual c%A_Index%,, Break
DllCall("RtlFillMemory",UInt,&bin+pos, UInt,1, UChar,c%A_Index%)
pos++
}
}
Code(i) { ; <== Chars[i & 63], 0-base index
Global Chars
StringMid i, Chars, (i&63)+1, 1
Return i
}
DeCode(c) { ; c = a char in Chars ==> position [0,63]
Global Chars
Return InStr(Chars,c,1) - 1
}
The same binary en/decoder is below, in compact form, with tests:
Code:
A = 123-aB. ; TEST
Loop 8 {
C := Base64Encode(A,A_Index)
Base64Decode(D,C)
VarSetCapacity(D,-1) ; use when D is string (instead of taking binary info)
MsgBox % SubStr(A,1,A_Index) "`n" C "`n" D
}
Base64Encode(ByRef bin, n=0) {
m := VarSetCapacity(bin)
Loop % n<1 || n>m ? m : n
A := *(&bin+A_Index-1)
,m := Mod(A_Index,3)
,b := m=1 ? A << 16 : m=2 ? b+(A<<8) : b+A
,out .= m ? "" : Code(b>>18) Code(b>>12) Code(b>>6) Code(b)
Return out (m ? Code(b>>18) Code(b>>12) (m=1 ? "==" : Code(b>>6) "=") : "")
}
Code(i) { ; <== Chars[i & 63], 0-base index
Static Chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
Return SubStr(Chars,(i&63)+1,1)
}
Base64Decode(ByRef bin, code) {
Static Chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
StringReplace code, code, =,, All
VarSetCapacity(bin, 3*StrLen(code)//4, 0)
pos = 0
Loop Parse, code
m := A_Index&3, d := InStr(Chars,A_LoopField,1) - 1
,b := m ? (m=1 ? d<<18 : b+(d<<24-6*m)) : b+d
,Append(bin, pos, 3*!m, b>>16, 255 & b>>8, 255 & b)
Append(bin, pos, !!m+(m&1), b>>16, 255 & b>>8, 0)
}
Append(ByRef bin, ByRef pos, k, c1,c2,c3) {
Loop %k%
DllCall("RtlFillMemory",UInt,&bin+pos++, UInt,1, UChar,c%A_Index%)
}
Edit 2006.07.09: added version to handle binary buffers.
Edit 2010.05.22: bugfix in binary version, compact code added