この記事はcoins Advent Calendar 2025の23日目の記事です。
※ 自分の学生証で、自分が入れる場所でだけ試してください。
※ 学内ルールや法律には各自で気をつけましょう。
筑波大学では学生証を扉の横のリーダーにかざすことで扉の鍵を開けることができます。
でも学生証持ち歩くのって面倒くさいですよね?
というわけで、学生証の代わりにAndroidを使って学内の鍵を開けてみたいと思います。
もちろんPixel WatchなんかのWear OS搭載のスマートウォッチでも同じことができます。
仕組み
筑波大学の学生証はFeliCaを使っています。
扉の横のリーダーは(すべてではないですが)学生証に割り当てられた固有のID(IDm)だけを見るカス of カスな認証方法をとっています。
見ているものはIDm だけ なのでそれだけどうにかすれば開けられちゃうわけです。
Android7以降の一部の端末には、HCE-F(Host Card Emulation F)と言う、FeliCaをエミュレートできる機能が搭載されています。
HCE-Fを使って学生証のIDmを喋らせるだけで鍵を開けることができます。
必要なもの
- HCE-Fに対応したroot化済みのAndroid端末(Wear OSでも可)
- 学内の鍵を開けられる学生証のIDm(自分のでやりましょう)
- Android開発環境
とりあえずやってみる
まずは学生証のシステムコードとIDmを確認しましょう。
NXPのTagInfoで学生証を読むと
0x8918筑波大学オリジナルのシステムコード 詳細は不明0xFE00FeliCa共通領域のシステムコード おそらくFCF
の二つのシステムが見つかりました。
いろいろ試してみた限り、学内のカードリーダーは0xFE00に対してポーリングしているものが多かったのでこちらを使います。
HCEF Unlocker
標準のAndroidでは、IDmだけを見る脆弱なシステムのためにHCE-Fで使えるIDmに制限がかけられています。
IDmとシステムコードが使用できるものかどうかをチェックする関数、isValidSystemCodeとisValidNfcid2をLSPosedを使ってフックすることでこの制限を回避できます。
先駆者がいたので使わせてもらいます。
本家のリポジトリは削除済みだったのでフォーク先のリンクを張っておきます。
アプリ開発
あとは通常のHCE-Fアプリと同じようにHCE-Fを実装すれば動作するようになります。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature
android:name="android.hardware.nfc.hcef"
android:required="true" />
<uses-permission android:name="android.permission.NFC" />
<!-- 中略 -->
<service
android:name=".presentation.HCEFService"
android:exported="true"
android:permission="android.permission.BIND_NFC_SERVICE">
<intent-filter>
<action android:name="android.nfc.cardemulation.action.HOST_NFCF_SERVICE" />
</intent-filter>
<meta-data
android:name="android.nfc.cardemulation.host_nfcf_service"
android:resource="@xml/host_nfcf_service" />
</service>
</application>
</manifest>今回はアプリ側で動的にIDmやシステムコードを変更できるようにしたかったのでxmlには適当な値を設定しておきます。
<?xml version="1.0" encoding="utf-8"?>
<host-nfcf-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/service_desc">
<nfcid2-filter android:name="02FE000000000000" />
<t3tPmm-filter android:name="0332FFFFFFFFFFFF"/>
<system-code-filter android:name="4000" />
</host-nfcf-service>setNfcid2ForService()、registerSystemCodeForService()を呼び出すことでIDmとシステムコードを設定できます。
val ServiceComponentName = ComponentName(
"ro.roro.felicaemulator",
HCEFService::class.java.name
)
nfcFCardEmulation = NfcFCardEmulation.getInstance(nfcAdapter)
nfcFCardEmulation.setNfcid2ForService(ServiceComponentName, idm)
nfcFCardEmulation.registerSystemCodeForService(ServiceComponentName, systemCode)制限事項
HCE-F自体がかなり制限が多く使い勝手が悪いためroot化したとしても思い通りに動いてくれないことが多いです。
まず、バックグラウンドでは強制的に動作が停止してしまうため、Wear OSを使う場合はWindowManager.LayoutParams.FLAG_KEEP_SCREEN_ONなどで暗転しないようにする必要があります。
また、0xFFFFのポーリングには反応しないため、2Bとかに設置してあるゲートを突破することはできません。(おサーフケータイ非搭載端末ならいけるかも)
Tips
Pixel Watchのroot化についてはこの記事がわかりやすいです。
Magiskなどのアプリは基本的にWear OSでの動作を想定していないのでUIが崩れまくっています。Pixel Watchの設定からフォントサイズを最小にしたり、scrcpyを使ってデスクトップから操作すると多少ましです。
あとがき
毎回財布から学生証を出してかざす必要がなくなったのでかなり便利です。(アプリを起動する必要があるけど...)
公式でモバイル版を出してほしいところですが、FeliCaは仕様の多くが非公開だったり、SDKが超高額だったり、秘密保持契約が必須だったりで、開発難易度が劇高です。正直、あまり期待はできなさそうです。
最近はFeliCa関連の脆弱性が見つかったりもしていて、いつまで生き残れるのか正直ちょっと怪しい気がしています。早めにNFC-A,B方式のカードに切り替えたほうがいいんじゃないですかね。