To convert a base64 String to raw data, it's not sufficient to just convert the characters into their UTF equivalents you to get the character index from the place-value array (usually MIME's base64 implementation):
let MIME64: [UnicodeScalar] = [
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"+", "/"
]
Now each character in your base64 string represents 6 bits of data. To convert to bytes you can group your characters in clusters of 4 to extract 3 bytes of data apiece. For example, your sample string kc/j converts to indices [36, 28, 63, 35] in the MIME64 array. We take those indices and build a 24 bit Int by shifting the bits to their proper place: var int24 = (36 << 18)|(28 << 12)|(63 << 6)|35 or 100100011100111111100011. That binary can be divided into 3 distinct bytes: 10010001, 11001111, 11100011, which we then append to our buffer. This is why your original method was returning a 48-byte instead of a 35-byte array. Here's the formula in action:
func fromBase64(baseString: String) -> [UInt8] {
var buff = [UInt8]()
let mask8 = 0b11111111
var i = 3
var byte24 = 0
for a in baseString.unicodeScalars {
if let aInt = find(MIME64, a) {
var shiftAmount = 6 * (i % 4)
println(a, aInt)
byte24 |= aInt << shiftAmount
if i > 0 {
--i
} else {
i = 3
var bytes = [
(byte24 & (mask8 << 16)) >> 16,
(byte24 & (mask8 << 8)) >> 8,
byte24 & mask8
]
buff.append(UInt8(bytes[0]))
buff.append(UInt8(bytes[1]))
buff.append(UInt8(bytes[2]))
byte24 = 0
}
}
}
switch i {
case 0:
var bytes = [
(byte24 & (mask8 << 16)) >> 16,
(byte24 & (mask8 << 8)) >> 8,
]
buff.append(UInt8(bytes[0]))
buff.append(UInt8(bytes[1]))
case 1:
var bytes = [
(byte24 & (mask8 << 16)) >> 16,
]
buff.append(UInt8(bytes[0]))
default:
break;
}
return buff
}
Note that after we're done aggregating and resplitting the bits, there may be a remainder that were not divisible by three. This is where = comes in. These are known as 'padding' and represent placeholder bytes in the encoded string. We simply grab whatever's left and add these bytes to the end of the buffer.
For = we grab the two leftmost bytes
byte24 & (mask8 << 16)) >> 16
byte24 & (mask8 << 8)) >> 8
and for == we grab only the leftmost byte:
byte24 & (mask8 << 16)) >> 16
Of course, as Martin and Libor already mentioned, the most straightforward way to do this in practice is to just use NSData to convert your string:
NSData(base64EncodedString: base64String, options: nil)
And then extract the bytes using NSData's getBytes method.