文字列のエンコードとデコード
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 から画像への変換 に貼り付けてください。