# 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
)
);
}
}