# Secure message生成のサンプルデータとコード

このトピックでは、ビーコン端末に組み込むSecure message生成機能の開発中に利用できる、サンプルのデータとコードについて説明します。Secure message生成のアルゴリズムについては、「Secure messageを生成する」を参照してください。

# サンプルデータ

一連の表は、Secure messageの生成に必要なデータのサンプルと、そのデータを正しく計算した結果の値を示しています。ここで提供されているデータと比較して、Secure message生成機能が正しく動作しているかどうかを確認してください。

以下は、Secure messageの計算に使用するパラメータです。

フィールド
HWID 01deadbeef
Vendor key 5cf2a423
Lot key 8c194fe41d7fe34f
Battery level 0x01

HWID、Vendor key、およびLot keyは、バイト配列に変換してから計算します。

# Timestampが0の場合

Timestampの値が0とは、端末に初めて電源が入ったことを意味します。

dummy dummy
入力値 000000000000000001deadbeef5cf2a4238c194fe41d7fe34f01
SHA-256ハッシュ値 72de7eafe33a44f0094283e03c28ff8bf85230825616fa49b73edaa6be88a0a8
Message authentication code 037cf6f1
Secure message 037cf6f1000001

# Timestampが1の場合

Timestampの値が1とは、最初に電源を入れてから15秒経過したことを意味します。

dummy dummy
入力値 000000000000000101deadbeef5cf2a4238c194fe41d7fe34f01
SHA-256ハッシュ値 eba4633c394cf7d913a863f25e930e7b8d9227bd109c2019d9dfbb411366cc4f
Message authentication code c86489c6
Secure message c86489c6000101

# Timestampが65535の場合

Timestampの値が65535から65536にインクリメントされると、Secure messageのMasked timestampは0000にリセットされます。「Timestampが65536の場合」セクションも合わせて参照し、Masked timestampがffffから0000になることを確認してください。

dummy dummy
入力値 000000000000ffff01deadbeef5cf2a4238c194fe41d7fe34f01
SHA-256ハッシュ値 f435f408d2978130607a8af69da2e6f65b66c260796f03ce2a7daf9b468ae0b5
Message authentication code 958497b8
Secure message 958497b8ffff01

# Timestampが65536の場合

Timestampの値が65536にインクリメントされると、Secure messageのMasked timestampは0000にリセットされます。「Timestampが65535の場合」セクションも合わせて参照し、Masked timestampがffffから0000になることを確認してください。

dummy dummy
入力値 000000000001000001deadbeef5cf2a4238c194fe41d7fe34f01
SHA-256ハッシュ値 70b58ab690b63d519caf37359ce3d910e8e1d79a90f095462ee2d56ae0f035e4
Message authentication code 564cfb90
Secure message 564cfb90000001

# Timestampが9223372036854775807の場合

9223372036854775807は、符号付き64ビット整数型で格納できる最大値です。

dummy dummy
入力値 7fffffffffffffff01deadbeef5cf2a4238c194fe41d7fe34f01
SHA-256ハッシュ値 c626232a199b163c53ba70f4d493c8b34891532d9e8fbf2b788e7e1cf3d13dec
Message authentication code 05d522a7
Secure message 05d522a7ffff01

# Timestampが18446744073709551615の場合

18446744073709551615は、符号なし64ビット整数型で格納できる最大値です。

dummy dummy
入力値 ffffffffffffffff01deadbeef5cf2a4238c194fe41d7fe34f01
SHA-256ハッシュ値 53ab6c6874fc333398ae2186eb45ce5d5a77d10cd2d3fe3d933a12b33cb13090
Message authentication code 7393bd92
Secure message 7393bd92ffff01

# サンプルコード

以下は、Secure messageを生成するJavaのサンプルコードです。

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

import javax.xml.bind.DatatypeConverter;

public class LineBeacon {

    private static byte[] sha256(byte[] input) {
        try {
            return MessageDigest.getInstance("SHA-256").digest(input);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("SHA-256 is always supported in Java7+", e);
        }
    }

    private static byte[] xor(byte[] input, int xorCount) {
        if (xorCount == 0) {
            return input;
        }

        byte[] latterHalf = Arrays.copyOfRange(input, input.length / 2, input.length);
        for (int i = 0; i < latterHalf.length; i++) {
            latterHalf[i] ^= input[i];
        }
        return xor(latterHalf, xorCount - 1);
    }

    private static byte[] concat(byte[]...inputs) {
        int size = 0;
        for (byte[] in: inputs) {
            size += in .length;
        }
        ByteBuffer bb = ByteBuffer.allocate(size);
        for (byte[] in: inputs) {
            bb.put( in );
        }
        return bb.array();
    }

    public static byte[] createSecureMessage(long timestamp, byte[] hwid, byte[] vendorKey, byte[] lotKey, byte batteryLevel) {
        if (hwid.length != 5) {
            throw new IllegalArgumentException("HWID must be 5 bytes long. " + hwid.length);
        }
        if (vendorKey.length != 4) {
            throw new IllegalArgumentException("Vendor key must be 4 bytes long. " + vendorKey.length);
        }
        if (lotKey.length != 8) {
            throw new IllegalArgumentException("Lot key must be 8 bytes long. " + lotKey.length);
        }
        if (batteryLevel < 0x00 || 0x0b < batteryLevel) {
            throw new IllegalArgumentException("Battery Level must be between 0x00 and 0x0b: " + batteryLevel);
        }

        byte[] rawTimestamp = ByteBuffer
            .allocate(8) // Timestamp of LINE Beacon is always 8 bytes long.
            .order(ByteOrder.BIG_ENDIAN) // LINE Beacon always uses big-endian.
            .putLong(timestamp)
            .array();
        byte[] input = concat(rawTimestamp, hwid, vendorKey, lotKey, new byte[] {
            batteryLevel
        });
        byte[] digest = sha256(input);
        byte[] messageAuthenticationCode = xor(digest, 3);
        byte[] secureMessage = ByteBuffer
            .allocate(7) // Current secureMessage is always 7 bytes long.
            .put(messageAuthenticationCode)
            .put(rawTimestamp, 6, 2) // Mask the upper 6 bytes of the timestamp.
            .put(batteryLevel)
            .array();

        System.out.printf("%20s\t%s\t%s\t%s\t%s\n",
            Long.toUnsignedString(timestamp),
            DatatypeConverter.printHexBinary(secureMessage),
            DatatypeConverter.printHexBinary(input),
            DatatypeConverter.printHexBinary(digest),
            DatatypeConverter.printHexBinary(messageAuthenticationCode)
        );
        return secureMessage;
    }
}

以下は、「サンプルデータ」セクションのデータを使って、上記のSecure message生成コードをテストするサンプルコードです。

import org.junit.Test;

import static org.junit.Assert.*;

import javax.xml.bind.DatatypeConverter;

public class LineBeaconTest {
    @Test
    public void testSecureMessage() {
        byte[] HWID_01deadbeef = DatatypeConverter.parseHexBinary("01deadbeef");
        byte[] VENDOR_KEY_5cf2a423 = DatatypeConverter.parseHexBinary("5cf2a423");
        byte[] LOTKEY_8c194fe41d7fe34f = DatatypeConverter.parseHexBinary("8c194fe41d7fe34f");
        byte BATTEY_LEVEL_0x01 = 0x01;

        assertArrayEquals(
            "initial timestamp",
            DatatypeConverter.parseHexBinary("037cf6f1000001"),
            LineBeacon.createSecureMessage(
                0,
                HWID_01deadbeef,
                VENDOR_KEY_5cf2a423,
                LOTKEY_8c194fe41d7fe34f,
                BATTEY_LEVEL_0x01

            )
        );
        assertArrayEquals(
            "timestamp after 15 sec",
            DatatypeConverter.parseHexBinary("c86489c6000101"),
            LineBeacon.createSecureMessage(
                1,
                HWID_01deadbeef,
                VENDOR_KEY_5cf2a423,
                LOTKEY_8c194fe41d7fe34f,
                BATTEY_LEVEL_0x01

            )
        );
        assertArrayEquals(
            "timestamp as UNSIGNED_SHORT_MAX_VALUE",
            DatatypeConverter.parseHexBinary("958497b8ffff01"),
            LineBeacon.createSecureMessage(
                0xffff,
                HWID_01deadbeef,
                VENDOR_KEY_5cf2a423,
                LOTKEY_8c194fe41d7fe34f,
                BATTEY_LEVEL_0x01

            )
        );
        assertArrayEquals(
            "carry-over test",
            DatatypeConverter.parseHexBinary("564cfb90000001"),
            LineBeacon.createSecureMessage(
                0x0001 _0000,
                HWID_01deadbeef,
                VENDOR_KEY_5cf2a423,
                LOTKEY_8c194fe41d7fe34f,
                BATTEY_LEVEL_0x01

            )
        );
        assertArrayEquals(
            "timestamp as SIGNED_LONG_MAX_VALUE",
            DatatypeConverter.parseHexBinary("05d522a7ffff01"),
            LineBeacon.createSecureMessage(
                9223372036854775807 L,
                HWID_01deadbeef,
                VENDOR_KEY_5cf2a423,
                LOTKEY_8c194fe41d7fe34f,
                BATTEY_LEVEL_0x01

            )
        );
        assertArrayEquals(
            "timestamp as UNSIGNED_LONG_MAX_VALUE",
            DatatypeConverter.parseHexBinary("7393bd92ffff01"),
            LineBeacon.createSecureMessage(
                Long.parseUnsignedLong("ffffffffffffffff", 16),
                HWID_01deadbeef,
                VENDOR_KEY_5cf2a423,
                LOTKEY_8c194fe41d7fe34f,
                BATTEY_LEVEL_0x01

            )
        );
    }
}