Archive

iPhoneで国際電話を全部ブロックしたかったが、CallKitで作るのをやめた

2026年7月11日

最近かかってくるのは、入管や中国の公安を名乗る電話だ。中国語の自動音声が流れることもあり、少なくとも私の周囲では、日本人に無差別にかかってくる種類の迷惑電話という印象ではなかった。

中国を旅行したとき、ホテルで連絡先を書いたことや、予約に電話番号を使ったことがある。そうしたどこかの経路から中国語圏の詐欺グループへ番号が渡ってしまったのかもしれない。

警察庁推奨アプリの詐欺・迷惑電話番号ブロックも有効にしていたが、この番号はすり抜けた。登録済みの危険な番号を照合する方式なので、新しい番号や未登録の番号まですべて止められるわけではない。2

私には海外から電話がかかってくる用事がほとんどない。国内の電話はそのまま受け、日本の国番号+81以外を拒否できれば分かりやすい。iPhoneで使えるアプリを探したが、海外着信だけを国番号で一括ブロックするものは見つからなかった。

なければ自分で作ればよいと思った。着信番号を文字列で受け取り、hasPrefixや正規表現で81以外を落とす。grepのような条件指定ができるものだと思っていたので、この時点では電話番号を全部列挙する発想はなかった。

Call Directoryを調べる

iOSのCall Directory app extensionでは、CXCallDirectoryProvider.beginRequest(with:)でブロックリストをシステムへ渡す。

電話番号の型はCXCallDirectoryPhoneNumber、実体はInt64だ。+や空白を含めず、国番号から始まる数字列にする。ブロック登録に使えるAPIは次の1件追加だけだった。

context.addBlockingEntry(
    withNextSequentialPhoneNumber: phoneNumber
)

前方一致、正規表現、国番号、番号範囲を渡すAPIはない。完全な番号を重複なし・昇順で追加し、最後にcompleteRequest()を呼ぶ。順序違反はentriesOutOfOrder、重複はduplicateEntries、多すぎればmaximumEntriesExceededになる。4

このextensionは着信ごとに起動しない。Appleの資料にも、識別情報は一括指定し、着信時にはWebサービスへ問い合わせられないとある。3 着信時にif !number.hasPrefix("81")を実行する場所がない。

Call Directoryの実行モデル

  1. 利用者がiOSの着信拒否設定と着信IDでextensionを有効にする。
  2. アプリがCXCallDirectoryManager.reloadExtension(withIdentifier:)を要求する。
  3. iOSがextensionを起動し、beginRequest(with:)へcontextを渡す。
  4. extensionが番号を順番に追加してcompleteRequest()を呼ぶ。
  5. 以後の着信照合はiOS側で行われる。

アプリが直接ブロック判定をするのではなく、事前にOSのCall Directoryへデータをロードする設計になっている。extensionの状態確認はgetEnabledStatusForExtension、設定画面への誘導はopenSettings()で行う。

context.isIncrementaltrueなら差分更新になり、removeBlockingEntry(withPhoneNumber:)で既存番号を削除できる。falseの通常ロード中に差分削除するとunexpectedIncrementalRemovalになる。ただし差分更新でも、範囲や条件式を登録できるようになるわけではない。

ここまで調べて、国際電話を全部止めるには対象番号を網羅してCall Directoryへ渡すしかないと分かった。国番号なら一つの条件で済むと思っていたので、必要なデータ量までは考えていなかった。

国際電話番号の形式は、ITU(国際電気通信連合)がE.164という規格で定めている。国番号を含めて最大15桁になる。1

割り当て済み番号だけを厳密に数えた値ではないが、15桁の空間を単純な上限として見ると約1015通りになる。+81を除いても桁はほぼ変わらない。Int64を並べるだけで約8PBとなり、索引などの持ち方によっては数十PB規模になる。全網羅は無理だと分かり、実際に着信が増えていた+1 833へ範囲を絞ることにした。

全網羅を諦め、+1 833だけに絞ってみた

+1は米国だけでなくカナダなども含む北米番号計画の国番号で、国番号の後ろは10桁だ。833はトールフリーのエリアコードなので、その後ろに残るのは8桁ではなく7桁。+1 833で始まる番号をすべて列挙すると、107 = 10,000,000件になる。

電話番号を64bit整数(1件8バイト)として並べるだけで、10,000,000 × 8 = 80,000,000バイト、約76.3MiBになる。実際には配列やデータベース、Call Directory側の索引も加わる。

約80MBなら、容量だけを見れば現実的だ。Appleが数値を公開していない登録上限やロード時間は別に検証する必要があるが、少なくともストレージ容量だけで断念する規模ではない。

Live Caller ID Lookup

Live Caller ID Lookupは着信時にサーバーを参照する新しい方式だ。ただしextensionが受け取るのは着信イベントではない。LiveCallerIDLookupProtocolで指定するのは、serviceURLtokenIssuerURLuserTierTokenの3つである。5

@main
struct LookupExtension: LiveCallerIDLookupProtocol {
    var context: LiveCallerIDLookupExtensionContext {
        .init(
            serviceURL: serviceURL,
            tokenIssuerURL: tokenIssuerURL,
            userTierToken: token
        )
    }
}

実際の照会はiOSが行う。通信経路は次のようになる。6

  1. Oblivious HTTPのリレーが利用者のIPアドレスを隠す。
  2. Privacy Passが利用者を匿名のまま認証する。
  3. KPIRが完全な電話番号を暗号化されたキーワードとして検索する。
  4. iOSがブロック情報や発信者情報を復号して利用する。

サービス側には3つのHTTP POSTエンドポイントが必要になる。通信形式はProtocol Buffersだ。7

Endpoint役割主なデータ
/configPIR設定と鍵の状態ConfigRequest / ConfigResponse
/key評価鍵のアップロードEvaluationKeys
/queries暗号化クエリの評価Requests / Responses

各リクエストには仮名のUser-IdentifierとPrivate Access Tokenを含むAuthorizationが付く。ブロック用payloadは1バイトで、0が許可、1がブロック。発信者情報はCallIdentityのProtocol Bufferで、名前、アイコン、カテゴリ、キャッシュ期限を返せる。同じ番号への応答は設定した期限までiOS側でキャッシュされる。8

サーバーは照会番号そのものを受け取らず、暗号化クエリをデータベースに対して評価する。着信番号をHTTP APIへ渡し、サーバー側でhasPrefix("81")を実行する構成にはできない。完全な番号をキーワードとするPIRデータセットが必要になる。

さらにAppleリレー用のエンドポイント検証とKPIRサーバー運用が必要だ。既知の迷惑電話データベースには適しているが、個人が国際電話を一括拒否するために使う仕組みとしては大きすぎる。

2つの方式の違い

Call DirectoryLive Caller ID Lookup
データ端末へ事前ロードサーバーのPIRデータセット
照合iOSがローカルで実行iOSが暗号化照会
番号の単位完全な電話番号完全な電話番号をキーとするエントリー
前方一致なし自由なサーバー判定はなし
更新extensionをreloadPIRデータセットと設定を更新

件数の計算

接頭辞と番号全体の桁数から、完全な番号の件数を計算する。

結論

Call Directoryは既知の完全な電話番号をOSへ渡す仕組みだった。Live Caller ID Lookupはサーバー上のデータを照会できるが、着信時に任意の条件式を実行するAPIではない。

+1 833だけなら、1,000万件と約80MBなので実装を試す余地はある。ただし防げるのはその接頭辞を使う電話だけだ。相手が別のトールフリー番号や別の国番号へ移れば、また範囲を調べ、番号を生成し、データを配布してextensionを更新することになる。

発信者番号は強い本人確認でもない。VoIPや電話転送などの経路では、着信画面へ表示する番号を偽装される場合がある。910 通信事業者による発信者番号認証もあるが、すべての国際電話経路で真正性を保証できるわけではない。番号を頻繁に変えたり偽装したりできる相手には、番号ベースのブロックだけでは効果が限られる。

相手は発信番号を変えればよいが、こちらはそのたびに完全な番号のリストを作り直す。+1 833だけなら技術的には可能だが、番号を変えられるたびに対応するのは面倒なのでやめた。

iOSには不明な発信者を消音する標準機能もある。ただし国内の未登録番号も対象になるため、海外着信だけを拒否するものではない。

参考資料