読者です 読者をやめる 読者になる 読者になる

ANDROID IDがどのように生成されているかざっくり調べた

Android

悪名高い??ANDROID IDがどのように生成されているのか気になったのでざっくり調べた。

悪名高いと言われる闇の話は置いておいて....とりあえず生成方法だけざっくり調べた。

どのような手順で生成されるのか?という疑問はバージョンによって実装が異なるっぽかったので調べない。

ANDROID IDとは

ドキュメント見てもらえばわかるけどざっくり以下のようなもの。

  • ランダムに生成される64ビットの数値を16進数文字列にしたもの
  • 端末の最初の設定時に生成される。その後、ファクトリーリセットするまではその時に生成された値が常に取得できる
  • ファクトリーリセットすることで値は再生成される
  • 4.2以上でmultiple usersが導入され、ユーザごとにANDROID IDが生成される

developer.android.com

ANDROID IDは以下のような感じで簡単に取得できる。

String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);

ANDROID IDを使用する際の気をつけていること

ざっくり以下かなー他にもなにかあれば教えてほしいかなー

  • 「ランダムに生成される64ビットの数値を16進数文字列にしたもの」なので文字数は固定ではない
  • どんなアプリでも同じ値がAPIを通して取得できる
  • ファクトリーリセットすることで値が変わる
  • 同じANDROID IDを持った端末が存在する可能性がある
  • multiple users環境ではユーザごとにANDROID IDが生成されるため、ANDROID IDは端末に1つではない

ANDROID IDがどのように生成されているのか

とりあえずAndroid Nのコードから見てみる。

ANDROID IDを生成しているのはSettingsProvider.javaensureSecureSettingAndroidIdSetLockedメソッドだね。

全体の処理は以下のような感じ。詳しくは見ないけどドキュメントどおりmultiple usersの想定もされてる実装になってる。

private void ensureSecureSettingAndroidIdSetLocked(SettingsState secureSettings) {
    Setting value = secureSettings.getSettingLocked(Settings.Secure.ANDROID_ID);

    if (!value.isNull()) {
        return;
    }

    final int userId = getUserIdFromKey(secureSettings.mKey);

    final UserInfo user;
    final long identity = Binder.clearCallingIdentity();
    try {
        user = mUserManager.getUserInfo(userId);
    } finally {
        Binder.restoreCallingIdentity(identity);
    }
    if (user == null) {
        // Can happen due to races when deleting users - treat as benign.
        return;
    }

    String androidId = Long.toHexString(new SecureRandom().nextLong());
    secureSettings.insertSettingLocked(Settings.Secure.ANDROID_ID, androidId,
            SettingsState.SYSTEM_PACKAGE_NAME);

    Slog.d(LOG_TAG, "Generated and saved new ANDROID_ID [" + androidId
            + "] for user " + userId);

    // Write a drop box entry if it's a restricted profile
    if (user.isRestricted()) {
        DropBoxManager dbm = (DropBoxManager) getContext().getSystemService(
                Context.DROPBOX_SERVICE);
        if (dbm != null && dbm.isTagEnabled(DROPBOX_TAG_USERLOG)) {
            dbm.addText(DROPBOX_TAG_USERLOG, System.currentTimeMillis()
                    + "," + DROPBOX_TAG_USERLOG + "," + androidId + "\n");
        }
    }
}

http://tools.oesf.biz/android-7.0.0_r1.0/xref/frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java#1915

ANDROID IDの生成処理は以下のような感じで実装されてる。

String androidId = Long.toHexString(new SecureRandom().nextLong());

SecureRandomでランダムなlong値を生成して、それをLong.toHexStringで16進数表現の文字列にする。

それがANDROID IDってことになるね。

ちなみにSecureRandom.nextLongの実装はRandom.nextLong)なんだけど、ドキュメントと実装見ればわかるけど、long 値の一部しか返さないっぽいんだよね。

なのでlong値の幅 -9223372036854775808~9223372036854775807の中の一部ってことだね。うーん...思ってた以上に範囲が狭い気もする...

あと、SecureRandom.nextLong生成される値によって16進数文字列にした時の文字列の長さは変わる

雑に以下のようなやつを実行してみればわかるけど、文字列の長さが16だったり、15だったり、14だったりする。

for (int i = 0; i < 100000; i++) {
    String androidId = Long.toHexString(new SecureRandom().nextLong());
    System.out.println(androidId.length());
}

これが最初の気にしておかなければいけない点で書いた「ランダムに生成される64ビットの数値を16進数文字列にしたもの」なので文字数は固定ではないってこと。

他のAndroidのバージョンではどう実装されているか

全部のバージョン見るのは面倒なので、いくつかのバージョンだけ。

4.0.1ではensureAndroidIdIsSetメソッドでANDROID IDが生成されてる。

もちろんSecureRandom.nextLong + Long.toHexStringANDROID IDが生成されてる。

てかSlog.dじゃなくて普通のLog.d使ってる...まあいいや...

private boolean ensureAndroidIdIsSet() {
    final Cursor c = query(Settings.Secure.CONTENT_URI,
            new String[] { Settings.NameValueTable.VALUE },
            Settings.NameValueTable.NAME + "=?",
            new String[] { Settings.Secure.ANDROID_ID }, null);
    try {
        final String value = c.moveToNext() ? c.getString(0) : null;
        if (value == null) {
            final SecureRandom random = new SecureRandom();
            final String newAndroidIdValue = Long.toHexString(random.nextLong());
            Log.d(TAG, "Generated and saved new ANDROID_ID [" + newAndroidIdValue + "]");
            final ContentValues values = new ContentValues();
            values.put(Settings.NameValueTable.NAME, Settings.Secure.ANDROID_ID);
            values.put(Settings.NameValueTable.VALUE, newAndroidIdValue);
            final Uri uri = insert(Settings.Secure.CONTENT_URI, values);
            if (uri == null) {
                return false;
            }
        }
        return true;
    } finally {
        c.close();
    }
}

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java#316

ちなみに、コードは割愛しますが2.3.7の実装はだいたい4.0.1と同じ。もちろんSecureRandom.nextLong + Long.toHexStringANDROID IDが生成されてる。

http://tools.oesf.biz/android-2.3.7_r1.0/xref/frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java#322

その他のバージョンもざっくり見たけど、SecureRandom.nextLong + Long.toHexStringANDROID IDが生成されていることは変わらない。当たり前か。

まとめ

Long.toHexString(new SecureRandom().nextLong())で生成されるものがANDROID IDだねーということ

思ってたよりANDROID IDの生成処理があっさりしたもので驚きだったかなー

モヤモヤするのはドキュメントに書いてある「 ランダムに生成される64ビットの数値を16進数文字列にしたもの」というのは表面上はあっているが、内部処理の話をすると「えー」と言いたくなる気分...

気になったら調べてみるもんだなー