2

I have this example method in C#:

public static byte[] FromBase64Url(string data)
{
    data = data.Replace('-', '+').Replace('_', '/');
    return Convert.FromBase64String(data);
}

and I would like to implement it in Swift. I tried this:

class func fromBase64(baseString: String) -> [UInt8] {
    let data = baseString.replace("-", withString: "+").replace("_", withString: "/")
    var buff = [UInt8]()
    buff += data.utf8
    return buff
}

but it returns different results. I am little confused with what returns C# method. Input for these methods is this string:

kc_jYgaSXyZ0c7oAAACQAQAAAO560uWYfkmUGkgU7Gbn7Cs=

C# methods returns byte array with count 35 items. I get in my swift method 48 items with different values. I know it's not long as Base64 should be but I must work with this.

Why is .NET method returns less items? How can I implement my swift method to have same result as C#?

2
  • NSData has methods to convert from and to Base64. I cannot see in your Swift code that you do any Base64 conversion (apart from substituting "-" and "_"). This might be helpful: stackoverflow.com/a/26827014/1187415. Commented Mar 6, 2015 at 16:05
  • I have base64 in my input (almost, I need to substitute some chars) and I needed convert Base64 to byte array and I wasn't sure what I was doing wrong. Thanks to you I know now. Commented Mar 6, 2015 at 16:36

3 Answers 3

5

As Martin R mention in his comment I tried options from other topic and I was doing conversion byte array bad way. This is correct method in swift:

class func base64ToByteArray(base64String: String) -> [UInt8]? {
      if let nsdata = NSData(base64EncodedString: base64String, options: nil) {
          var bytes = [UInt8](count: nsdata.length, repeatedValue: 0)
          nsdata.getBytes(&bytes)
          return bytes
      }
      return nil // Invalid input
}

Now I am gettings same results as in C#.

In Swift 2 it should be:

if let nsdata = NSData(base64EncodedString: base64String, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) {
...
Sign up to request clarification or add additional context in comments.

1 Comment

options can no longer be nil in swift 2
1

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.

2 Comments

Where does gsub() come from? – Note that NSData already has methods to encode and decode Base64 encoded strings.
Sorry--gsub is a method I use for string replacement. I meant to take that out. I know that NSData has base64 encoding/decoding methods :). I just wanted to illustrate why simply converting the characters to utf isn't sufficient to decode a base64 string.
1

Here is simply version that work in swift 3 and that the code is not deprecated

if let nsdata1 = Data(base64Encoded: stringData, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) {

    let arr2 = nsdata1.withUnsafeBytes {
       Array(UnsafeBufferPointer<UInt8>(start: $0, count: nsdata1.count/MemoryLayout<UInt8>.size))
    }

    print("Array: ",arr2)  
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.