将 base64 字符串转换为字节数组,如 C# 方法 Convert.FromBase64String

convert base64 string to byte array like C# method Convert.FromBase64String

我在 C# 中有这个示例方法:

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

我想在 Swift 中实现它。我试过这个:

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

但它 returns 不同的结果。我对什么 returns C# 方法有点困惑。这些方法的输入是这个字符串:

kc_jYgaSXyZ0c7oAAACQAQAAAO560uWYfkmUGkgU7Gbn7Cs=

C# 方法 returns 包含 35 个项目的字节数组。我在 swift 方法中得到了 48 个具有不同值的项目。我知道它不应该像 Base64 那样长,但我必须使用它。

为什么 .NET 方法 returns 项少?如何实现我的 swift 方法以获得与 C# 相同的结果?

正如 Martin R 在他的评论中提到的那样,我尝试了其他主题的选项,但我正在以错误的方式转换字节数组。这是 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
}

现在我得到了与在 C# 中相同的结果。

在Swift2中应该是:

if let nsdata = NSData(base64EncodedString: base64String, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) {
...

要将 base64 字符串转换为原始数据,仅将字符转换为它们的 UTF 等效项是不够的,您可以从位值数组(通常是 MIME 的 base64 实现)中获取字符索引:

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",
    "+", "/"
]

现在 base64 字符串中的每个字符代表 6 位数据。要转换为字节,您可以将字符分组为 4 个字符,每个字符提取 3 个字节的数据。例如,您的示例字符串 kc/j 转换为 MIME64 数组中的索引 [36, 28, 63, 35]。我们采用这些索引并通过将位移动到适当的位置来构建一个 24 位 Int:var int24 = (36 << 18)|(28 << 12)|(63 << 6)|35100100011100111111100011。该二进制文件可以分为 3 个不同的字节:100100011100111111100011,然后我们将其附加到我们的缓冲区。这就是为什么您的原始方法返回 48 字节而不是 35 字节数组的原因。这是实际的公式:

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
}

请注意,在我们完成位的聚合和重新拆分之后,可能会有一个不能被三整除的余数。这就是 = 的用武之地。这些被称为 'padding' 并代表编码字符串中的占位符字节。我们只需抓住剩下的任何内容并将这些字节添加到缓冲区的末尾。

对于=,我们抓取最左边的两个字节

byte24 & (mask8 << 16)) >> 16
byte24 & (mask8 << 8)) >> 8

对于==,我们只抓取最左边的字节:

byte24 & (mask8 << 16)) >> 16

当然,正如 Martin 和 Libor 已经提到的,在实践中最直接的方法是只使用 NSData 来转换您的字符串:

 NSData(base64EncodedString: base64String, options: nil)

然后使用NSDatagetBytes方法提取字节。

这里是在 swift 3 中工作的简单版本,并且代码没有被弃用

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

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

    print("Array: ",arr2)  
}