Swift Digest

base64 のエンコード/デコードに base64url とパディング省略のオプションを追加

Adding base64 urlencoding and omitting padding option to base64 encoding and decoding

Proposal
SF-0030
Authors
Fabian Fett
Status
Accepted

このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら

01 何が問題だったのか

Foundation には、Data を base64 でエンコード/デコードするための API として Data.Base64EncodingOptionsData.Base64DecodingOptions が用意されていました。しかし、これらは RFC4648 で定義されている base64url アルファベットや、末尾のパディング文字 = の省略には対応していませんでした。

base64url とパディング省略は、Web 関連の暗号仕様で広く使われています。代表例として、

などが挙げられます。これらを扱おうとすると、利用者は標準の base64 API をそのまま使えず、エンコード結果に対して replacingOccurrences(of:with:)+- に、/_ に置き換えたり、末尾の = を取り除いたりするラッパーを自作する必要がありました。この方法ではデータを何度も走査することになり、本来であれば 1 度のパスで済む処理を 3 回繰り返すことになって非効率です。

加えて、デコード側には ignoreUnknownCharacters オプションがありましたが、これは未知の文字をすべて読み飛ばすため、base64url の -_ を base64 のデコード時に許してしまうなど、アルファベットの取り違えを検出できないという課題もありました。空白文字(CR / LF / Tab / Space)だけを読み飛ばしたいユースケースにも、ちょうどよい選択肢がありませんでした。

02 どのように解決されるのか

Data.Base64EncodingOptionsData.Base64DecodingOptions のそれぞれに、base64url アルファベットを使う base64URLAlphabet と、末尾のパディング = を省略する omitPaddingCharacter が追加されます。これらを単独で、あるいは組み合わせて指定することで、JWT や Web Push などで使われる base64url 形式を 1 度のパスで読み書きできます。

extension Data.Base64EncodingOptions {
    /// Use the base64url alphabet to encode the data
    @available(FoundationPreview 6.3, *)
    public static var base64URLAlphabet: Base64EncodingOptions { get }

    /// Omit the `=` padding characters in the end of the base64 encoded result
    @available(FoundationPreview 6.3, *)
    public static var omitPaddingCharacter: Base64EncodingOptions { get }
}

エンコード時の使い方は次のとおりです。base64URLAlphabet は標準 base64 の +- に、/_ に置き換えたアルファベットでエンコードし、omitPaddingCharacter は末尾の = を出力しません。両方を指定すれば、JWT などで使う base64url 形式が一度のエンコードで得られます。

let data = Data([0xfb, 0xff, 0xbf])

// 標準の base64
data.base64EncodedString()
// "+/+/"

// base64url(+ → -, / → _)
data.base64EncodedString(options: .base64URLAlphabet)
// "-_-_"

// パディング省略(末尾の "=" が消える例)
Data([0x00]).base64EncodedString(options: .omitPaddingCharacter)
// "AA"(標準では "AA==")

// base64url + パディング省略(JWT などで使う形式)
data.base64EncodedString(options: [.base64URLAlphabet, .omitPaddingCharacter])
// "-_-_"

デコード側にも同じ 2 つのオプションが追加され、さらに空白文字だけを読み飛ばす ignoreWhitespaceCharacters も加わります。

extension Data.Base64DecodingOptions {
    /// Modify the decoding algorithm so that it ignores unknown non-Base-64 bytes,
    /// including line ending characters.
    public static let ignoreUnknownCharacters = Base64DecodingOptions(rawValue: 1 << 0)

    /// Modify the decoding algorithm so that it ignores whitespace characters
    /// (CR LF Tab and Space).
    @available(FoundationPreview 6.3, *)
    public static var ignoreWhitespaceCharacters: Base64DecodingOptions { get }

    /// Modify the decoding algorithm so that it expects base64 encoded data
    /// that uses base64url alphabet.
    @available(FoundationPreview 6.3, *)
    public static var base64URLAlphabet: Base64DecodingOptions { get }

    /// Modify the decoding algorithm so that it expects no padding characters
    /// at the end of the encoded data.
    @available(FoundationPreview 6.3, *)
    public static var omitPaddingCharacter: Base64DecodingOptions { get }
}

デコード時の omitPaddingCharacter は、末尾に = が現れた時点でデコードを失敗させます。同様に base64URLAlphabet を指定すると、標準 base64 の +/ が現れた時点で失敗します。これにより、想定しないアルファベットや書式のデータを取り違えて受け入れてしまう事故を防げます。

// base64url + パディング省略のデータをデコード
let decoded = Data(
    base64Encoded: "-_-_",
    options: [.base64URLAlphabet, .omitPaddingCharacter]
)
// Optional(Data([0xfb, 0xff, 0xbf]))

// パディング "=" があると omitPaddingCharacter で失敗
Data(
    base64Encoded: "-_-_=",
    options: [.base64URLAlphabet, .omitPaddingCharacter]
)
// nil

// 標準 base64 のアルファベットが混じっていると base64URLAlphabet で失敗
Data(
    base64Encoded: "+/+/",
    options: .base64URLAlphabet
)
// nil

入力に改行などが含まれることが想定される場合は ignoreWhitespaceCharacters を使うと、CR / LF / Tab / Space だけを読み飛ばし、それ以外の不正な文字はエラーとして扱えます。これまで使われていた ignoreUnknownCharacters は未知の文字をすべて読み飛ばすため、たとえば base64url の -_ が混じったデータを base64 として誤って受け入れてしまう恐れがあります。新しい ignoreWhitespaceCharacters は、より厳密にデコードしたいケースで望ましい選択肢になります。

なお、ignoreUnknownCharactersomitPaddingCharacter を同時に指定した場合、omitPaddingCharacter は無視されます(= も「未知でない文字」として読み飛ばされる側に倒れるため)。可能であれば ignoreWhitespaceCharacters と組み合わせる運用が推奨されます。

これらは追加 API のみの変更で、既存コードへの影響はありません。利用には FoundationPreview 6.3 以降が必要です。