# Sample code and data for generating secure messages
Refer to the sample data and code provided here, when you develop a function to embed in beacon devices to generate secure messages. For the algorithm information on secure message generation, see Generate a secure message.
# Sample data
The series of tables show sample data required to generate secure messages and the resulting values when you compute the data correctly. Compare with the data provided here to see if your function for secure message generation behaves properly.
Here are the parameters we'll use to compute secure messages:
Field | Value |
---|---|
HWID | 01deadbeef |
Vendor key | 5cf2a423 |
Lot key | 8c194fe41d7fe34f |
Battery level | 0x01 |
Convert the value of each HWID, vendor key, and lot key to a byte array before calculation.
# Timestamp is zero
The timestamp value 0
means that the device is powered on for the first time.
dummy | dummy |
---|---|
Input value | 000000000000000001deadbeef5cf2a4238c194fe41d7fe34f01 |
SHA-256 hash value | 72de7eafe33a44f0094283e03c28ff8bf85230825616fa49b73edaa6be88a0a8 |
Message authentication code | 037cf6f1 |
Secure message | 037cf6f1000001 |
# Timestamp is one
The timestamp value 1
means that 15 seconds have passed since the initial power-on.
dummy | dummy |
---|---|
Input value | 000000000000000101deadbeef5cf2a4238c194fe41d7fe34f01 |
SHA-256 hash value | eba4633c394cf7d913a863f25e930e7b8d9227bd109c2019d9dfbb411366cc4f |
Message authentication code | c86489c6 |
Secure message | c86489c6000101 |
# Timestamp is 65535
If the timestamp value is incremented from 65535
to 65536
, the masked timestamp in the secure message is reset to 0000
. See also the Timestamp is 65536 section and check that the masked timestamp changes from ffff
to 0000
.
dummy | dummy |
---|---|
Input value | 000000000000ffff01deadbeef5cf2a4238c194fe41d7fe34f01 |
SHA-256 hash value | f435f408d2978130607a8af69da2e6f65b66c260796f03ce2a7daf9b468ae0b5 |
Message authentication code | 958497b8 |
Secure message | 958497b8ffff01 |
# Timestamp is 65536
When the timestamp value is incremented to 65536
, the masked timestamp in the secure message is reset to 0000
. See also the Timestamp is 65535 section and check that the masked timestamp changes from ffff
to 0000
.
dummy | dummy |
---|---|
Input value | 000000000001000001deadbeef5cf2a4238c194fe41d7fe34f01 |
SHA-256 hash value | 70b58ab690b63d519caf37359ce3d910e8e1d79a90f095462ee2d56ae0f035e4 |
Message authentication code | 564cfb90 |
Secure message | 564cfb90000001 |
# Timestamp is 9223372036854775807
The value 9223372036854775807
is the maximum value of a signed 64-bit integer.
dummy | dummy |
---|---|
Input value | 7fffffffffffffff01deadbeef5cf2a4238c194fe41d7fe34f01 |
SHA-256 hash value | c626232a199b163c53ba70f4d493c8b34891532d9e8fbf2b788e7e1cf3d13dec |
Message authentication code | 05d522a7 |
Secure message | 05d522a7ffff01 |
# Timestamp is 18446744073709551615
The value 18446744073709551615
is the maximum value of an unsigned 64-bit integer.
dummy | dummy |
---|---|
Input value | ffffffffffffffff01deadbeef5cf2a4238c194fe41d7fe34f01 |
SHA-256 hash value | 53ab6c6874fc333398ae2186eb45ce5d5a77d10cd2d3fe3d933a12b33cb13090 |
Message authentication code | 7393bd92 |
Secure message | 7393bd92ffff01 |
# Sample code
This sample Java code generates a secure message.
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;
}
}
This sample code tests the code above, that generates a secure message, with the data from the sample data section.
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
)
);
}
}