ファイルを選択
  1. base64hub
  2. 各言語での Base64
  3. Swift
コード

Swift での Base64

Swift では Foundation の Data で Base64 を扱います。エンコードは 1 つのメソッド、デコードは 1 つの失敗可能イニシャライザーです。以下のコードは Linux 上の Swift 6.1 で実行しました。Apple プラットフォームと同じ Foundation API です。

文字列のエンコードとデコード

String.utf8 で UTF-8 のバイト列が得られ、Data がそれをエンコードします。

import Foundation

let enc = Data("héllo wörld".utf8).base64EncodedString()
print(enc)   // aMOpbGxvIHfDtnJsZA==

if let data = Data(base64Encoded: enc),
   let s = String(data: data, encoding: .utf8) {
    print(s) // héllo wörld
}

デコードの経路には Optional が 2 つあります。Data(base64Encoded:) は不正な Base64 で nil になり、String(data:encoding:) はバイト列が有効な UTF-8 でないとき(たとえば [0xFF, 0xFE])に nil になります。nil より置換文字のほうがよければ、String(decoding: data, as: UTF8.self) を使ってください。

URLセーフな Base64

Foundation は URLセーフなアルファベットを知らないので、文字を自分で変換します。

let d = Data("<<???>>".utf8)
let b64url = d.base64EncodedString()
    .replacingOccurrences(of: "+", with: "-")
    .replacingOccurrences(of: "/", with: "_")
    .replacingOccurrences(of: "=", with: "")
print(b64url)   // PDw_Pz8-Pg

func base64URLDecode(_ s: String) -> Data? {
    var b = s.replacingOccurrences(of: "-", with: "+")
             .replacingOccurrences(of: "_", with: "/")
    b += String(repeating: "=", count: (4 - b.count % 4) % 4)
    return Data(base64Encoded: b)
}

print(String(decoding: base64URLDecode("PDw_Pz8-Pg")!, as: UTF8.self))  // <<???>>

パディングを戻す手順は必須です。Data(base64Encoded: "aGk") は nil を返します。JWT、Sign in with Apple の ID トークン、多くのプッシュ通知のペイロードはこのパディングなしの形式を使います。詳しくは Base64 と Base64URL の違い と パディングの解説 をご覧いただくか、JWT デコーダー でトークンを確認してください。

検証:デコードが nil を返すとき

不正な入力はどれも同じ結果、つまりエラーメッセージなしの nil になります。以下はすべて nil を返しました。

入力問題
PDw_Pz8-Pg==URLセーフの文字
aGkパディングの不足
aGk=!!パディングの後のゴミ
aGk==パディング過多
aあり得ない長さ
"aGVs\nbG8K"改行
" aGk="先頭のスペース

通ってしまうケースが 1 つあり、aGl=(0 でないパディングビット)は hi にデコードされます。したがって、有効性のチェックは Data(base64Encoded: s) != nil だけで済みます。文字列が失敗する理由を知りたいときは、Base64 バリデーター が位置と理由を報告し、エラーの解説 がそれぞれを説明しています。

改行や余計な文字を含む入力には、.ignoreUnknownCharacters オプションを使うとアルファベット外の文字を読み飛ばせます。

Data(base64Encoded: "aGVs\nbG8K", options: .ignoreUnknownCharacters)
// 6 bytes: "hello\n"

信頼できる MIME や PEM の本文に使ってください。任意の入力に使うとエラーが隠れます。URLセーフの文字も読み飛ばされ、結果が黙って誤ったものになるからです。

行の折り返しのオプション

base64EncodedString(options:) では、行を 64 文字または 76 文字で折り返せます。行末はデフォルトで CRLF です。

let x = Data(String(repeating: "x", count: 60).utf8)

x.base64EncodedString(options: .lineLength76Characters)
// "eHh4eHh4...eHh4\r\neHh4"    (76 chars, CRLF, rest)

x.base64EncodedString(options: .lineLength64Characters)
// 64 chars, "\r\n", 16 chars   (PEM-style)

Unix の改行にするには、長さのオプションに .endLineWithLineFeed だけを組み合わせます:[.lineLength76Characters, .endLineWithLineFeed]。オプションを指定しなければ出力は 1 行になり、JSON、URL、ヘッダーにはこれが適しています。メール向けには、MIME Base64 エンコーダー で 76 文字のレイアウトを確認できます。

ファイルのエンコードとデコード

let url = URL(fileURLWithPath: "hello.txt")
let b64 = try Data(contentsOf: url).base64EncodedString()
print(b64)   // aGVsbG8K

try Data(base64Encoded: "aGVsbG8K")!.write(to: URL(fileURLWithPath: "copy.txt"))

Playground なら強制アンラップでも構いませんが、アプリではデコードした Data を guard let で受け、エラーを報告してください。

大きなファイルをチャンク単位で処理する

Foundation にはストリーム用の Base64 エンコーダーがありません。途中にパディングが入らないよう、3 の倍数のサイズのチャンクでファイルを読み込みます。

func encodeFile(from src: URL, to dst: URL) throws {
    _ = FileManager.default.createFile(atPath: dst.path, contents: nil)
    let input = try FileHandle(forReadingFrom: src)
    let output = try FileHandle(forWritingTo: dst)
    defer { try? input.close(); try? output.close() }

    while let chunk = try input.read(upToCount: 3 * 64 * 1024), !chunk.isEmpty {
        try output.write(contentsOf: Data(chunk.base64EncodedString().utf8))
    }
}

300 KB のテストファイルをこの方法でエンコードすると、1 回の呼び出しでエンコードした場合と同じ文字列になりました。read(upToCount:) と write(contentsOf:) には macOS 10.15.4 / iOS 13.4 以降が必要です。出力が入力の約 4/3 になる理由は サイズ増加の解説 で説明しています。

落とし穴

  • エラーメッセージを期待する。 Data(base64Encoded:) は「だめ」としか言いません。デバッグには入力の長さと先頭の数文字をログに出してください。
  • Data URI の接頭辞。 Web ビューから来る文字列は data:image/png;base64, で始まることがよくあります。デコードの前に最初のカンマで切り取らないと、結果は nil になります。
  • String(describing: data)。 Data を print すると、内容ではなく 6 bytes のようなものが表示されます。base64EncodedString() か String(decoding:as:) を使ってください。
  • Base64 を安全な保存手段とみなす。 秘密情報には Keychain を使ってください。Base64 は誰でも読めます。エンコードと暗号化の違い をご覧ください。

よくある質問

Data(base64Encoded:) が nil を返すのはなぜですか?

このイニシャライザーは、不正な入力に対して例外をスローせず nil を返します。パディングの不足、URLセーフの - や _、スペース、改行、data: 接頭辞などが原因です。先に入力を直すか、折り返された入力なら options: .ignoreUnknownCharacters を渡してください。

Swift は Base64URL にネイティブ対応していますか?

Foundation に URLセーフのオプションはありません。エンコード後に + を - に、/ を _ に置き換えて = を取り除き、デコード前にはその逆を行ってパディングを戻してください。

Swift で UIImage を Base64 に変換するには?

まず image.pngData() や image.jpegData(compressionQuality: 0.8) などでバイト列を取得し、その結果に .base64EncodedString() を呼びます。出力を確認するには、Base64 から画像への変換 に貼り付けてください。

続けて読む